From d3d5bbf51ae749862d7d8e965d4a2ab981095398 Mon Sep 17 00:00:00 2001 From: pratiyankkumar Date: Wed, 2 Jul 2025 19:39:08 +0530 Subject: [PATCH] feat: add multiple project selection and bulk delete functionality --- apps/web/src/app/projects/page.tsx | 490 +++++++++++++++++++++++------ 1 file changed, 391 insertions(+), 99 deletions(-) diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index c983894..3d8612d 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; import { ChevronLeft, Plus, @@ -11,6 +12,8 @@ import { MoreHorizontal, Video, Loader2, + X, + Trash2, } from "lucide-react"; import { TProject } from "@/types/project"; import Image from "next/image"; @@ -27,9 +30,19 @@ import { DeleteProjectDialog } from "@/components/delete-project-dialog"; import { RenameProjectDialog } from "@/components/rename-project-dialog"; export default function ProjectsPage() { - const { createNewProject, savedProjects, isLoading, isInitialized } = - useProjectStore(); + const { + createNewProject, + savedProjects, + isLoading, + isInitialized, + deleteProject, + } = useProjectStore(); const router = useRouter(); + const [isSelectionMode, setIsSelectionMode] = useState(false); + const [selectedProjects, setSelectedProjects] = useState>( + new Set() + ); + const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false); const handleCreateProject = async () => { const projectId = await createNewProject("New Project"); @@ -37,6 +50,43 @@ export default function ProjectsPage() { router.push(`/editor/${projectId}`); }; + const handleSelectProject = (projectId: string, checked: boolean) => { + const newSelected = new Set(selectedProjects); + if (checked) { + newSelected.add(projectId); + } else { + newSelected.delete(projectId); + } + setSelectedProjects(newSelected); + }; + + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelectedProjects(new Set(savedProjects.map((p) => p.id))); + } else { + setSelectedProjects(new Set()); + } + }; + + const handleCancelSelection = () => { + setIsSelectionMode(false); + setSelectedProjects(new Set()); + }; + + const handleBulkDelete = async () => { + for (const projectId of selectedProjects) { + await deleteProject(projectId); + } + setSelectedProjects(new Set()); + setIsSelectionMode(false); + setIsBulkDeleteDialogOpen(false); + }; + + const allSelected = + savedProjects.length > 0 && selectedProjects.size === savedProjects.length; + const someSelected = + selectedProjects.size > 0 && selectedProjects.size < savedProjects.length; + return (
@@ -48,7 +98,30 @@ export default function ProjectsPage() { Back
- + {isSelectionMode ? ( +
+ + {selectedProjects.size > 0 && ( + + )} +
+ ) : ( + + )}
@@ -60,13 +133,72 @@ export default function ProjectsPage() {

{savedProjects.length}{" "} {savedProjects.length === 1 ? "project" : "projects"} + {isSelectionMode && selectedProjects.size > 0 && ( + + • {selectedProjects.size} selected + + )}

- + {isSelectionMode ? ( +
+ + {selectedProjects.size > 0 && ( + + )} +
+ ) : ( +
+ + +
+ )}
+ {isSelectionMode && savedProjects.length > 0 && ( +
+
+ { + if (el) { + const checkboxElement = el.querySelector( + "input" + ) as HTMLInputElement; + if (checkboxElement) { + checkboxElement.indeterminate = someSelected; + } + } + }} + onCheckedChange={handleSelectAll} + /> + + {allSelected ? "Deselect All" : "Select All"} + + + ({selectedProjects.size} of {savedProjects.length} selected) + +
+
+ )} + {isLoading || !isInitialized ? (
@@ -76,16 +208,40 @@ export default function ProjectsPage() { ) : (
{savedProjects.map((project) => ( - + ))}
)} + +
); } -function ProjectCard({ project }: { project: TProject }) { +interface ProjectCardProps { + project: TProject; + isSelectionMode?: boolean; + isSelected?: boolean; + onSelect?: (projectId: string, checked: boolean) => void; +} + +function ProjectCard({ + project, + isSelectionMode = false, + isSelected = false, + onSelect, +}: ProjectCardProps) { const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); @@ -114,108 +270,244 @@ function ProjectCard({ project }: { project: TProject }) { await duplicateProject(project.id); }; + const handleCardClick = (e: React.MouseEvent) => { + if (isSelectionMode) { + e.preventDefault(); + onSelect?.(project.id, !isSelected); + } + }; + return ( <> - - -
+ - {/* Thumbnail preview or placeholder */} -
- {project.thumbnail ? ( - Project thumbnail - ) : ( -
-
- -
-

- {project.name} -

- - - - - { - e.preventDefault(); - e.stopPropagation(); - }} - > - { - e.preventDefault(); - e.stopPropagation(); - setIsDropdownOpen(false); - setIsRenameDialogOpen(true); - }} - > - Rename - - { - e.preventDefault(); - e.stopPropagation(); - handleDuplicateProject(); - }} - > - Duplicate - - - { - e.preventDefault(); - e.stopPropagation(); - setIsDropdownOpen(false); - setIsDeleteDialogOpen(true); - }} - > - Delete - - - -
- -
-
- - Created {formatDate(project.createdAt)} + {/* Thumbnail preview or placeholder */} +
+ {project.thumbnail ? ( + Project thumbnail + ) : ( +
+
+ )}
- - - + + +
+

+ {project.name} +

+ {!isSelectionMode && ( + + + + + { + e.preventDefault(); + e.stopPropagation(); + }} + > + { + e.preventDefault(); + e.stopPropagation(); + setIsDropdownOpen(false); + setIsRenameDialogOpen(true); + }} + > + Rename + + { + e.preventDefault(); + e.stopPropagation(); + handleDuplicateProject(); + }} + > + Duplicate + + + { + e.preventDefault(); + e.stopPropagation(); + setIsDropdownOpen(false); + setIsDeleteDialogOpen(true); + }} + > + Delete + + + + )} +
+ +
+
+ + Created {formatDate(project.createdAt)} +
+
+
+ +
+ ) : ( + + +
+ {/* Thumbnail preview or placeholder */} +
+ {project.thumbnail ? ( + Project thumbnail + ) : ( +
+
+ )} +
+
+ + +
+

+ {project.name} +

+ + + + + { + e.preventDefault(); + e.stopPropagation(); + }} + > + { + e.preventDefault(); + e.stopPropagation(); + setIsDropdownOpen(false); + setIsRenameDialogOpen(true); + }} + > + Rename + + { + e.preventDefault(); + e.stopPropagation(); + handleDuplicateProject(); + }} + > + Duplicate + + + { + e.preventDefault(); + e.stopPropagation(); + setIsDropdownOpen(false); + setIsDeleteDialogOpen(true); + }} + > + Delete + + + +
+ +
+
+ + Created {formatDate(project.createdAt)} +
+
+
+
+ + )}