From c2d9010508c2a0b2ed0e40bbb330914319cd180c Mon Sep 17 00:00:00 2001 From: kenneth Date: Fri, 3 Oct 2025 23:23:05 +0000 Subject: [PATCH] feat: add trash page --- bun.lock | 1 - packages/convex/_generated/api.d.ts | 2 - packages/convex/files.ts | 9 - packages/convex/filesystem.ts | 22 +- packages/convex/model/directories.ts | 38 ++- .../web/src/dashboard/dashboard-sidebar.tsx | 25 ++ .../directory-content-table.tsx | 2 - .../directory-page/directory-page.tsx | 235 +----------------- .../directory-page/file-path-breadcrumb.tsx | 83 +++++++ .../src/directories/directory-page/state.ts | 2 - packages/web/src/routeTree.gen.ts | 30 ++- .../directories.$directoryId.tsx | 171 ++++++++++++- .../trash.directories.$directoryId.tsx | 58 +++++ 13 files changed, 410 insertions(+), 268 deletions(-) create mode 100644 packages/web/src/directories/directory-page/file-path-breadcrumb.tsx create mode 100644 packages/web/src/routes/_authenticated/_sidebar-layout/trash.directories.$directoryId.tsx diff --git a/bun.lock b/bun.lock index 62e7c2f..5e10453 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,6 @@ "name": "@fileone/convex", "dependencies": { "@fileone/path": "workspace:*", - "convex-helpers": "^0.1.104", }, "peerDependencies": { "convex": "^1.27.0", diff --git a/packages/convex/_generated/api.d.ts b/packages/convex/_generated/api.d.ts index c462248..5fd0788 100644 --- a/packages/convex/_generated/api.d.ts +++ b/packages/convex/_generated/api.d.ts @@ -13,7 +13,6 @@ import type { FilterApi, FunctionReference, } from "convex/server"; -import type * as admin from "../admin.js"; import type * as files from "../files.js"; import type * as filesystem from "../filesystem.js"; import type * as functions from "../functions.js"; @@ -33,7 +32,6 @@ import type * as users from "../users.js"; * ``` */ declare const fullApi: ApiFromModules<{ - admin: typeof admin; files: typeof files; filesystem: typeof filesystem; functions: typeof functions; diff --git a/packages/convex/files.ts b/packages/convex/files.ts index 19740ee..81b2022 100644 --- a/packages/convex/files.ts +++ b/packages/convex/files.ts @@ -50,15 +50,6 @@ export const fetchDirectory = authenticatedQuery({ }, }) -export const fetchDirectoryContent = authenticatedQuery({ - args: { - directoryId: v.optional(v.id("directories")), - }, - handler: async (ctx, { directoryId }): Promise => { - return await Directories.fetchContent(ctx, { directoryId }) - }, -}) - export const createDirectory = authenticatedMutation({ args: { name: v.string(), diff --git a/packages/convex/filesystem.ts b/packages/convex/filesystem.ts index 889f0f1..beccbe6 100644 --- a/packages/convex/filesystem.ts +++ b/packages/convex/filesystem.ts @@ -1,16 +1,19 @@ import { v } from "convex/values" -import { authenticatedMutation } from "./functions" +import { authenticatedMutation, authenticatedQuery } from "./functions" import * as Directories from "./model/directories" import * as Err from "./model/error" import * as Files from "./model/files" -import type { DirectoryHandle, FileHandle } from "./model/filesystem" +import type { + DirectoryHandle, + FileHandle, + FileSystemItem, +} from "./model/filesystem" import { type FileSystemHandle, FileType, VDirectoryHandle, VFileSystemHandle, } from "./model/filesystem" - export const moveItems = authenticatedMutation({ args: { targetDirectory: VDirectoryHandle, @@ -100,3 +103,16 @@ export const moveToTrash = authenticatedMutation({ } }, }) + +export const fetchDirectoryContent = authenticatedQuery({ + args: { + directoryId: v.optional(v.id("directories")), + trashed: v.boolean(), + }, + handler: async ( + ctx, + { directoryId, trashed }, + ): Promise => { + return await Directories.fetchContent(ctx, { directoryId, trashed }) + }, +}) diff --git a/packages/convex/model/directories.ts b/packages/convex/model/directories.ts index 23f54ff..fe4dcba 100644 --- a/packages/convex/model/directories.ts +++ b/packages/convex/model/directories.ts @@ -75,7 +75,10 @@ export async function fetch( export async function fetchContent( ctx: AuthenticatedQueryCtx, - { directoryId }: { directoryId?: Id<"directories"> } = {}, + { + directoryId, + trashed, + }: { directoryId?: Id<"directories">; trashed: boolean }, ): Promise { let dirId: Id<"directories"> | undefined if (directoryId) { @@ -85,21 +88,33 @@ export async function fetchContent( const [files, directories] = await Promise.all([ ctx.db .query("files") - .withIndex("byDirectoryId", (q) => - q + .withIndex("byDirectoryId", (q) => { + if (trashed) { + return q + .eq("userId", ctx.user._id) + .eq("directoryId", dirId) + .gte("deletedAt", 0) + } + return q .eq("userId", ctx.user._id) .eq("directoryId", dirId) - .eq("deletedAt", undefined), - ) + .eq("deletedAt", undefined) + }) .collect(), ctx.db .query("directories") - .withIndex("byParentId", (q) => - q + .withIndex("byParentId", (q) => { + if (trashed) { + return q + .eq("userId", ctx.user._id) + .eq("parentId", dirId) + .gte("deletedAt", 0) + } + return q .eq("userId", ctx.user._id) .eq("parentId", dirId) - .eq("deletedAt", undefined), - ) + .eq("deletedAt", undefined) + }) .collect(), ]) @@ -216,7 +231,10 @@ export async function move( ignoredHandles.add(handle) } else { promises.push( - ctx.db.patch(handle.id, { parentId: targetDirectory.id, updatedAt: Date.now() }), + ctx.db.patch(handle.id, { + parentId: targetDirectory.id, + updatedAt: Date.now(), + }), ) } } diff --git a/packages/web/src/dashboard/dashboard-sidebar.tsx b/packages/web/src/dashboard/dashboard-sidebar.tsx index aaf7364..aaf34cd 100644 --- a/packages/web/src/dashboard/dashboard-sidebar.tsx +++ b/packages/web/src/dashboard/dashboard-sidebar.tsx @@ -8,6 +8,7 @@ import { HomeIcon, LogOutIcon, SettingsIcon, + TrashIcon, User2Icon, } from "lucide-react" import { @@ -62,6 +63,7 @@ function MainSidebarMenu() { + ) } @@ -89,6 +91,29 @@ function AllFilesItem() { ) } +function TrashItem() { + const location = useLocation() + const rootDirectory = useConvexQuery(api.files.fetchRootDirectory) + + if (!rootDirectory) return null + + return ( + + + + + Trash + + + + ) +} + function UserMenu() { const { signOut } = useAuth() diff --git a/packages/web/src/directories/directory-page/directory-content-table.tsx b/packages/web/src/directories/directory-page/directory-content-table.tsx index 5ee68b5..968e018 100644 --- a/packages/web/src/directories/directory-page/directory-content-table.tsx +++ b/packages/web/src/directories/directory-page/directory-content-table.tsx @@ -131,8 +131,6 @@ export function DirectoryContentTable() { ) } - - export function DirectoryContentTableContent() { const { directoryContent } = useContext(DirectoryPageContext) const optimisticDeletedItems = useAtomValue(optimisticDeletedItemsAtom) diff --git a/packages/web/src/directories/directory-page/directory-page.tsx b/packages/web/src/directories/directory-page/directory-page.tsx index 3f6e420..025114d 100644 --- a/packages/web/src/directories/directory-page/directory-page.tsx +++ b/packages/web/src/directories/directory-page/directory-page.tsx @@ -1,252 +1,19 @@ -import { api } from "@fileone/convex/_generated/api" -import { - type DirectoryHandle, - FileType, - type PathComponent, -} from "@fileone/convex/model/filesystem" -import { useMutation } from "@tanstack/react-query" -import { Link } from "@tanstack/react-router" -import { useMutation as useConvexMutation } from "convex/react" import { useAtom } from "jotai" -import { - ChevronDownIcon, - Loader2Icon, - PlusIcon, - UploadCloudIcon, -} from "lucide-react" -import React, { type ChangeEvent, Fragment, useContext, useRef } from "react" -import { toast } from "sonner" import { ImagePreviewDialog } from "@/components/image-preview-dialog" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { DirectoryIcon } from "../../components/icons/directory-icon" -import { TextFileIcon } from "../../components/icons/text-file-icon" -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "../../components/ui/breadcrumb" -import { Button } from "../../components/ui/button" -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "../../components/ui/tooltip" -import { WithAtom } from "../../components/with-atom" -import { useFileDrop } from "../../files/use-file-drop" -import { cn } from "../../lib/utils" -import { DirectoryPageContext } from "./context" import { DirectoryContentTable } from "./directory-content-table" -import { NewDirectoryDialog } from "./new-directory-dialog" -import { RenameFileDialog } from "./rename-file-dialog" -import { dragInfoAtom, newFileTypeAtom, openedFileAtom } from "./state" +import { openedFileAtom } from "./state" export function DirectoryPage() { - const { directory } = useContext(DirectoryPageContext) return ( <> -
- -
- - -
-
- - - {(newFileType, setNewFileType) => ( - { - if (!open) { - setNewFileType(null) - } - }} - /> - )} - ) } -function FilePathBreadcrumb() { - const { rootDirectory, directory } = useContext(DirectoryPageContext) - - const breadcrumbItems: React.ReactNode[] = [] - for (let i = 1; i < directory.path.length - 1; i++) { - breadcrumbItems.push( - - - - , - ) - } - - return ( - - - {rootDirectory._id === directory._id ? ( - - All Files - - ) : ( - - )} - {breadcrumbItems} - - - {directory.name}{" "} - - - - ) -} - -function FilePathBreadcrumbItem({ component }: { component: PathComponent }) { - const { isDraggedOver, dropHandlers } = useFileDrop({ - destItem: component.handle as DirectoryHandle, - dragInfoAtom, - }) - - const dirName = component.name || "All Files" - - return ( - - - - - - {dirName} - - - - - Move to {dirName} - - ) -} - -// tags: upload, uploadfile, uploadfilebutton, fileupload, fileuploadbutton -function UploadFileButton() { - const { directory } = useContext(DirectoryPageContext) - const generateUploadUrl = useConvexMutation(api.files.generateUploadUrl) - const saveFile = useConvexMutation(api.files.saveFile) - const { mutate: uploadFile, isPending: isUploading } = useMutation({ - mutationFn: async (file: File) => { - 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, - mimeType: file.type, - directoryId: directory._id, - }) - }, - onSuccess: () => { - toast.success("File uploaded successfully.") - }, - }) - - const fileInputRef = useRef(null) - - const handleClick = () => { - fileInputRef.current?.click() - } - - const onFileUpload = async (e: ChangeEvent) => { - const file = e.target.files?.[0] - if (file) { - uploadFile(file) - } - } - - return ( - <> - - - - ) -} - -function NewDirectoryItemDropdown() { - const [newFileType, setNewFileType] = useAtom(newFileTypeAtom) - - const addNewDirectory = () => { - setNewFileType(FileType.Directory) - } - - const handleCloseAutoFocus = (event: Event) => { - // If we just created a new item, prevent the dropdown from restoring focus to the trigger - if (newFileType) { - event.preventDefault() - } - } - - return ( - - - - - - - - Text file - - - - Directory - - - - ) -} - function PreviewDialog() { const [openedFile, setOpenedFile] = useAtom(openedFileAtom) diff --git a/packages/web/src/directories/directory-page/file-path-breadcrumb.tsx b/packages/web/src/directories/directory-page/file-path-breadcrumb.tsx new file mode 100644 index 0000000..d93a023 --- /dev/null +++ b/packages/web/src/directories/directory-page/file-path-breadcrumb.tsx @@ -0,0 +1,83 @@ +import type { + DirectoryHandle, + PathComponent, +} from "@fileone/convex/model/filesystem" +import { Link } from "@tanstack/react-router" +import { Fragment, useContext } from "react" +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "../../components/ui/breadcrumb" +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "../../components/ui/tooltip" +import { useFileDrop } from "../../files/use-file-drop" +import { cn } from "../../lib/utils" +import { DirectoryPageContext } from "./context" +import { dragInfoAtom } from "./state" + +export function FilePathBreadcrumb() { + const { rootDirectory, directory } = useContext(DirectoryPageContext) + + const breadcrumbItems: React.ReactNode[] = [] + for (let i = 1; i < directory.path.length - 1; i++) { + breadcrumbItems.push( + + + + , + ) + } + + return ( + + + {rootDirectory._id === directory._id ? ( + + All Files + + ) : ( + + )} + {breadcrumbItems} + + + {directory.name}{" "} + + + + ) +} + +function FilePathBreadcrumbItem({ component }: { component: PathComponent }) { + const { isDraggedOver, dropHandlers } = useFileDrop({ + destItem: component.handle as DirectoryHandle, + dragInfoAtom, + }) + + const dirName = component.name || "All Files" + + return ( + + + + + + {dirName} + + + + + Move to {dirName} + + ) +} diff --git a/packages/web/src/directories/directory-page/state.ts b/packages/web/src/directories/directory-page/state.ts index 744c7dc..0ba4767 100644 --- a/packages/web/src/directories/directory-page/state.ts +++ b/packages/web/src/directories/directory-page/state.ts @@ -11,8 +11,6 @@ export const optimisticDeletedItemsAtom = atom( export const selectedFileRowsAtom = atom({}) -export const newFileTypeAtom = atom(null) - export const itemBeingRenamedAtom = atom<{ originalItem: FileSystemItem name: string diff --git a/packages/web/src/routeTree.gen.ts b/packages/web/src/routeTree.gen.ts index 074295e..e060d74 100644 --- a/packages/web/src/routeTree.gen.ts +++ b/packages/web/src/routeTree.gen.ts @@ -16,6 +16,7 @@ import { Route as LoginCallbackRouteImport } from './routes/login_.callback' import { Route as AuthenticatedSidebarLayoutRouteImport } from './routes/_authenticated/_sidebar-layout' import { Route as AuthenticatedSidebarLayoutHomeRouteImport } from './routes/_authenticated/_sidebar-layout/home' import { Route as AuthenticatedSidebarLayoutDirectoriesDirectoryIdRouteImport } from './routes/_authenticated/_sidebar-layout/directories.$directoryId' +import { Route as AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRouteImport } from './routes/_authenticated/_sidebar-layout/trash.directories.$directoryId' const LoginRoute = LoginRouteImport.update({ id: '/login', @@ -53,6 +54,12 @@ const AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute = path: '/directories/$directoryId', getParentRoute: () => AuthenticatedSidebarLayoutRoute, } as any) +const AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute = + AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRouteImport.update({ + id: '/trash/directories/$directoryId', + path: '/trash/directories/$directoryId', + getParentRoute: () => AuthenticatedSidebarLayoutRoute, + } as any) export interface FileRoutesByFullPath { '/login': typeof LoginRoute @@ -60,6 +67,7 @@ export interface FileRoutesByFullPath { '/': typeof AuthenticatedIndexRoute '/home': typeof AuthenticatedSidebarLayoutHomeRoute '/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute + '/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute } export interface FileRoutesByTo { '/login': typeof LoginRoute @@ -67,6 +75,7 @@ export interface FileRoutesByTo { '/': typeof AuthenticatedIndexRoute '/home': typeof AuthenticatedSidebarLayoutHomeRoute '/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute + '/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -77,6 +86,7 @@ export interface FileRoutesById { '/_authenticated/': typeof AuthenticatedIndexRoute '/_authenticated/_sidebar-layout/home': typeof AuthenticatedSidebarLayoutHomeRoute '/_authenticated/_sidebar-layout/directories/$directoryId': typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute + '/_authenticated/_sidebar-layout/trash/directories/$directoryId': typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -86,8 +96,15 @@ export interface FileRouteTypes { | '/' | '/home' | '/directories/$directoryId' + | '/trash/directories/$directoryId' fileRoutesByTo: FileRoutesByTo - to: '/login' | '/login/callback' | '/' | '/home' | '/directories/$directoryId' + to: + | '/login' + | '/login/callback' + | '/' + | '/home' + | '/directories/$directoryId' + | '/trash/directories/$directoryId' id: | '__root__' | '/_authenticated' @@ -97,6 +114,7 @@ export interface FileRouteTypes { | '/_authenticated/' | '/_authenticated/_sidebar-layout/home' | '/_authenticated/_sidebar-layout/directories/$directoryId' + | '/_authenticated/_sidebar-layout/trash/directories/$directoryId' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -156,12 +174,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRouteImport parentRoute: typeof AuthenticatedSidebarLayoutRoute } + '/_authenticated/_sidebar-layout/trash/directories/$directoryId': { + id: '/_authenticated/_sidebar-layout/trash/directories/$directoryId' + path: '/trash/directories/$directoryId' + fullPath: '/trash/directories/$directoryId' + preLoaderRoute: typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRouteImport + parentRoute: typeof AuthenticatedSidebarLayoutRoute + } } } interface AuthenticatedSidebarLayoutRouteChildren { AuthenticatedSidebarLayoutHomeRoute: typeof AuthenticatedSidebarLayoutHomeRoute AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute: typeof AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute + AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute: typeof AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute } const AuthenticatedSidebarLayoutRouteChildren: AuthenticatedSidebarLayoutRouteChildren = @@ -169,6 +195,8 @@ const AuthenticatedSidebarLayoutRouteChildren: AuthenticatedSidebarLayoutRouteCh AuthenticatedSidebarLayoutHomeRoute: AuthenticatedSidebarLayoutHomeRoute, AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute: AuthenticatedSidebarLayoutDirectoriesDirectoryIdRoute, + AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute: + AuthenticatedSidebarLayoutTrashDirectoriesDirectoryIdRoute, } const AuthenticatedSidebarLayoutRouteWithChildren = diff --git a/packages/web/src/routes/_authenticated/_sidebar-layout/directories.$directoryId.tsx b/packages/web/src/routes/_authenticated/_sidebar-layout/directories.$directoryId.tsx index ac3b5c1..67cffa6 100644 --- a/packages/web/src/routes/_authenticated/_sidebar-layout/directories.$directoryId.tsx +++ b/packages/web/src/routes/_authenticated/_sidebar-layout/directories.$directoryId.tsx @@ -1,9 +1,36 @@ import { api } from "@fileone/convex/_generated/api" +import { FileType } from "@fileone/convex/model/filesystem" +import { useMutation } from "@tanstack/react-query" import { createFileRoute } from "@tanstack/react-router" -import { useQuery as useConvexQuery } from "convex/react" +import { + useMutation as useConvexMutation, + useQuery as useConvexQuery, +} from "convex/react" +import { atom, useAtom } from "jotai" +import { + ChevronDownIcon, + Loader2Icon, + PlusIcon, + UploadCloudIcon, +} from "lucide-react" +import { type ChangeEvent, useContext, useRef } from "react" +import { toast } from "sonner" +import { DirectoryIcon } from "@/components/icons/directory-icon" +import { TextFileIcon } from "@/components/icons/text-file-icon" +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { WithAtom } from "@/components/with-atom" import { DirectoryPageContext } from "@/directories/directory-page/context" import { DirectoryPage } from "@/directories/directory-page/directory-page" import { DirectoryPageSkeleton } from "@/directories/directory-page/directory-page-skeleton" +import { FilePathBreadcrumb } from "@/directories/directory-page/file-path-breadcrumb" +import { NewDirectoryDialog } from "@/directories/directory-page/new-directory-dialog" +import { RenameFileDialog } from "@/directories/directory-page/rename-file-dialog" export const Route = createFileRoute( "/_authenticated/_sidebar-layout/directories/$directoryId", @@ -11,15 +38,21 @@ export const Route = createFileRoute( component: RouteComponent, }) +const newFileTypeAtom = atom(null) + function RouteComponent() { const { directoryId } = Route.useParams() const rootDirectory = useConvexQuery(api.files.fetchRootDirectory) const directory = useConvexQuery(api.files.fetchDirectory, { directoryId, }) - const directoryContent = useConvexQuery(api.files.fetchDirectoryContent, { - directoryId, - }) + const directoryContent = useConvexQuery( + api.filesystem.fetchDirectoryContent, + { + directoryId, + trashed: false, + }, + ) if (!directory || !directoryContent || !rootDirectory) { return @@ -29,7 +62,137 @@ function RouteComponent() { +
+ +
+ + +
+
+ + + + {(newFileType, setNewFileType) => ( + { + if (!open) { + setNewFileType(null) + } + }} + /> + )} + + +
) } + +// tags: upload, uploadfile, uploadfilebutton, fileupload, fileuploadbutton +function UploadFileButton() { + const { directory } = useContext(DirectoryPageContext) + const generateUploadUrl = useConvexMutation(api.files.generateUploadUrl) + const saveFile = useConvexMutation(api.files.saveFile) + const { mutate: uploadFile, isPending: isUploading } = useMutation({ + mutationFn: async (file: File) => { + 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, + mimeType: file.type, + directoryId: directory._id, + }) + }, + onSuccess: () => { + toast.success("File uploaded successfully.") + }, + }) + + const fileInputRef = useRef(null) + + const handleClick = () => { + fileInputRef.current?.click() + } + + const onFileUpload = async (e: ChangeEvent) => { + const file = e.target.files?.[0] + if (file) { + uploadFile(file) + } + } + + return ( + <> + + + + ) +} + +function NewDirectoryItemDropdown() { + const [newFileType, setNewFileType] = useAtom(newFileTypeAtom) + + const addNewDirectory = () => { + setNewFileType(FileType.Directory) + } + + const handleCloseAutoFocus = (event: Event) => { + // If we just created a new item, prevent the dropdown from restoring focus to the trigger + if (newFileType) { + event.preventDefault() + } + } + + return ( + + + + + + + + Text file + + + + Directory + + + + ) +} diff --git a/packages/web/src/routes/_authenticated/_sidebar-layout/trash.directories.$directoryId.tsx b/packages/web/src/routes/_authenticated/_sidebar-layout/trash.directories.$directoryId.tsx new file mode 100644 index 0000000..8517ba9 --- /dev/null +++ b/packages/web/src/routes/_authenticated/_sidebar-layout/trash.directories.$directoryId.tsx @@ -0,0 +1,58 @@ +import { api } from "@fileone/convex/_generated/api" +import { createFileRoute } from "@tanstack/react-router" +import { useQuery as useConvexQuery } from "convex/react" +import { TrashIcon } from "lucide-react" +import { Button } from "@/components/ui/button" +import { DirectoryPageContext } from "@/directories/directory-page/context" +import { DirectoryPage } from "@/directories/directory-page/directory-page" +import { DirectoryPageSkeleton } from "@/directories/directory-page/directory-page-skeleton" +import { FilePathBreadcrumb } from "@/directories/directory-page/file-path-breadcrumb" + +export const Route = createFileRoute( + "/_authenticated/_sidebar-layout/trash/directories/$directoryId", +)({ + component: RouteComponent, +}) + +function RouteComponent() { + const { directoryId } = Route.useParams() + const rootDirectory = useConvexQuery(api.files.fetchRootDirectory) + const directory = useConvexQuery(api.files.fetchDirectory, { + directoryId, + }) + const directoryContent = useConvexQuery( + api.filesystem.fetchDirectoryContent, + { + directoryId, + trashed: true, + }, + ) + + if (!directory || !directoryContent || !rootDirectory) { + return + } + + return ( + +
+ +
+ +
+
+ + +
+ ) +} + +function EmptyTrashButton() { + return ( + + ) +}