initial commit
This commit is contained in:
30
src/routes/__root.tsx
Normal file
30
src/routes/__root.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createRootRoute, Outlet } from "@tanstack/react-router"
|
||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
|
||||
import { Toaster } from "@/components/ui/sonner"
|
||||
import DashboardSidebar from "@/dashboard/dashboard-sidebar"
|
||||
import "@/styles/globals.css"
|
||||
import { ConvexProvider, ConvexReactClient } from "convex/react"
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RootLayout,
|
||||
})
|
||||
|
||||
const convexClient = new ConvexReactClient(process.env.BUN_PUBLIC_CONVEX_URL!)
|
||||
|
||||
function RootLayout() {
|
||||
return (
|
||||
<ConvexProvider client={convexClient}>
|
||||
<SidebarProvider>
|
||||
<div className="flex h-screen w-full">
|
||||
<DashboardSidebar />
|
||||
<SidebarInset>
|
||||
<Outlet />
|
||||
</SidebarInset>
|
||||
</div>
|
||||
<Toaster />
|
||||
<TanStackRouterDevtools />
|
||||
</SidebarProvider>
|
||||
</ConvexProvider>
|
||||
)
|
||||
}
|
155
src/routes/files.tsx
Normal file
155
src/routes/files.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
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 { type ChangeEvent, useRef, useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
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"
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
27
src/routes/index.tsx
Normal file
27
src/routes/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { HomeIcon } from "lucide-react"
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar"
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: Index,
|
||||
})
|
||||
|
||||
function Index() {
|
||||
return (
|
||||
<>
|
||||
<header className="flex py-2 shrink-0 items-center gap-2 border-b px-4">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<div className="flex items-center gap-2">
|
||||
<HomeIcon className="h-5 w-5" />
|
||||
<h1 className="text-lg font-semibold">Home</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div className="p-6">
|
||||
<h1 className="text-2xl font-bold">Welcome to FileOne</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
Your file management dashboard
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user