feat: impl directory delete

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2025-09-14 18:12:29 +00:00
parent 5510d9cd8a
commit 67383c1c4e
13 changed files with 454 additions and 202 deletions

View File

@@ -3,16 +3,28 @@ import { Toaster } from "@/components/ui/sonner"
import DashboardSidebar from "@/dashboard/dashboard-sidebar"
import "@/styles/globals.css"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { Outlet, createRootRoute } from "@tanstack/react-router"
import { createRootRoute, Outlet } from "@tanstack/react-router"
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
import { ConvexProvider, ConvexReactClient } from "convex/react"
import { toast } from "sonner"
import { formatError } from "@/lib/error"
export const Route = createRootRoute({
component: RootLayout,
})
const convexClient = new ConvexReactClient(process.env.BUN_PUBLIC_CONVEX_URL!)
const queryClient = new QueryClient()
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
onError: (error) => {
console.log(error)
toast.error(formatError(error))
},
throwOnError: false,
},
},
})
function RootLayout() {
return (

View File

@@ -1,155 +1,6 @@
import { DirectoryIcon } from "@/components/icons/directory-icon"
import { TextFileIcon } from "@/components/icons/text-file-icon"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbPage,
} from "@/components/ui/breadcrumb"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { SidebarTrigger } from "@/components/ui/sidebar"
import { FileTable } from "@/files/file-table"
import { api } from "@convex/_generated/api"
import { createFileRoute } from "@tanstack/react-router"
import { useMutation, useQuery } from "convex/react"
import {
ChevronDownIcon,
Loader2Icon,
PlusIcon,
UploadCloudIcon,
} from "lucide-react"
import { useRef, useState, type ChangeEvent } from "react"
import { toast } from "sonner"
import { FilesPage } from "@/files/files-page"
export const Route = createFileRoute("/files")({
component: FilesPage,
})
function FilesPage() {
return (
<>
<header className="flex py-2 shrink-0 items-center gap-2 border-b px-4 w-full">
<SidebarTrigger className="-ml-1" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbPage>Files</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="ml-auto flex flex-row gap-2">
<NewDirectoryItemDropdown />
<UploadFileButton />
</div>
</header>
<div className="p-4 w-full">
<FileTable />
</div>
</>
)
}
function UploadFileButton() {
const [isUploading, setIsUploading] = useState(false)
const currentUser = useQuery(api.users.getCurrentUser)
const generateUploadUrl = useMutation(api.files.generateUploadUrl)
const saveFile = useMutation(api.files.saveFile)
const fileInputRef = useRef<HTMLInputElement>(null)
const handleClick = () => {
fileInputRef.current?.click()
}
const onFileUpload = async (e: ChangeEvent<HTMLInputElement>) => {
if (!currentUser?._id) {
return
}
const file = e.target.files?.[0]
if (file) {
try {
setIsUploading(true)
const uploadUrl = await generateUploadUrl()
const uploadResult = await fetch(uploadUrl, {
method: "POST",
body: file,
headers: {
"Content-Type": file.type,
},
})
const { storageId } = await uploadResult.json()
await saveFile({
storageId,
name: file.name,
size: file.size,
userId: currentUser._id,
})
toast.success("File uploaded successfully.", {
position: "top-center",
})
} catch (error) {
console.error(error)
} finally {
setIsUploading(false)
}
}
}
return (
<>
<input
hidden
onChange={onFileUpload}
ref={fileInputRef}
type="file"
name="files"
/>
<Button
size="sm"
type="button"
onClick={handleClick}
disabled={isUploading}
>
{isUploading ? (
<Loader2Icon className="animate-spin size-4" />
) : (
<UploadCloudIcon className="size-4" />
)}
{isUploading ? "Uploading" : "Upload File"}
</Button>
</>
)
}
function NewDirectoryItemDropdown() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" type="button" variant="outline">
<PlusIcon className="size-4" />
New
<ChevronDownIcon className="pl-1 size-4 shrink-0" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<TextFileIcon />
Text file
</DropdownMenuItem>
<DropdownMenuItem>
<DirectoryIcon />
Directory
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}