"use client"; import { Calendar, ChevronLeft, Loader2, MoreHorizontal, Plus, Search, Trash2, Video, X, } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { DeleteProjectDialog } from "@/components/delete-project-dialog"; import { RenameProjectDialog } from "@/components/rename-project-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { useProjectStore } from "@/stores/project-store"; import { useTimelineStore } from "@/stores/timeline-store"; import type { TProject } from "@/types/project"; export default function ProjectsPage() { const { savedProjects, isLoading, isInitialized, deleteProject, createNewProject, getFilteredAndSortedProjects, } = useProjectStore(); const [thumbnailCache, setThumbnailCache] = useState< Record >({}); const [_loadingThumbnails, setLoadingThumbnails] = useState>( new Set() ); const [isSelectionMode, setIsSelectionMode] = useState(false); const [selectedProjects, setSelectedProjects] = useState>( new Set() ); const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [sortOption, setSortOption] = useState("createdAt-desc"); const router = useRouter(); const getProjectThumbnail = useCallback( async (projectId: string): Promise => { if (thumbnailCache[projectId] !== undefined) { return thumbnailCache[projectId]; } setLoadingThumbnails((prev) => new Set(prev).add(projectId)); try { const thumbnail = await useTimelineStore .getState() .getProjectThumbnail(projectId); setThumbnailCache((prev) => ({ ...prev, [projectId]: thumbnail })); return thumbnail; } finally { setLoadingThumbnails((prev) => { const newSet = new Set(prev); newSet.delete(projectId); return newSet; }); } }, [thumbnailCache] ); const handleCreateProject = async () => { const projectId = await createNewProject("New Project"); console.log("projectId", projectId); 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(sortedProjects.map((p) => p.id))); } else { setSelectedProjects(new Set()); } }; const handleCancelSelection = () => { setIsSelectionMode(false); setSelectedProjects(new Set()); }; const handleBulkDelete = async () => { await Promise.all( Array.from(selectedProjects).map((projectId) => deleteProject(projectId)) ); setSelectedProjects(new Set()); setIsSelectionMode(false); setIsBulkDeleteDialogOpen(false); }; const sortedProjects = getFilteredAndSortedProjects(searchQuery, sortOption); const allSelected = sortedProjects.length > 0 && selectedProjects.size === sortedProjects.length; const someSelected = selectedProjects.size > 0 && selectedProjects.size < sortedProjects.length; return (
Back
{isSelectionMode ? (
{selectedProjects.size > 0 && ( )}
) : ( )}

Your Projects

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

{isSelectionMode ? (
{selectedProjects.size > 0 && ( )}
) : (
)}
setSearchQuery(e.target.value)} />
{isSelectionMode && sortedProjects.length > 0 && ( )} {isLoading || !isInitialized ? (
{Array.from({ length: 8 }, (_, index) => (
))}
) : savedProjects.length === 0 ? ( ) : sortedProjects.length === 0 ? ( setSearchQuery("")} /> ) : (
{sortedProjects.map((project) => ( ))}
)}
); } interface ProjectCardProps { project: TProject; isSelectionMode?: boolean; isSelected?: boolean; onSelect?: (projectId: string, checked: boolean) => void; getProjectThumbnail: (projectId: string) => Promise; } function ProjectCard({ project, isSelectionMode = false, isSelected = false, onSelect, getProjectThumbnail, }: ProjectCardProps) { const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); const [dynamicThumbnail, setDynamicThumbnail] = useState(null); const [isLoadingThumbnail, setIsLoadingThumbnail] = useState(true); const { deleteProject, renameProject, duplicateProject } = useProjectStore(); useEffect(() => { const loadThumbnail = async () => { setIsLoadingThumbnail(true); try { const thumbnail = await getProjectThumbnail(project.id); setDynamicThumbnail(thumbnail); } finally { setIsLoadingThumbnail(false); } }; loadThumbnail(); }, [project.id, getProjectThumbnail]); const formatDate = (date: Date): string => { return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); }; const handleDeleteProject = async () => { await deleteProject(project.id); setIsDropdownOpen(false); }; const handleRenameProject = async (newName: string) => { await renameProject(project.id, newName); setIsRenameDialogOpen(false); }; const handleDuplicateProject = async () => { setIsDropdownOpen(false); await duplicateProject(project.id); }; const handleCardClick = (e: React.MouseEvent) => { if (isSelectionMode) { e.preventDefault(); onSelect?.(project.id, !isSelected); } }; const handleCardKeyDown = (e: React.KeyboardEvent) => { if (isSelectionMode && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onSelect?.(project.id, !isSelected); } }; const cardContent = (
{isSelectionMode && (
onSelect?.(project.id, checked as boolean) } onClick={(e) => e.stopPropagation()} className="w-4 h-4" />
)}
{isLoadingThumbnail ? (
) : dynamicThumbnail ? ( 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)}
); return ( <> {isSelectionMode ? ( ) : ( {cardContent} )} ); } function CreateButton({ onClick }: { onClick?: () => void }) { return ( ); } function NoProjects({ onCreateProject }: { onCreateProject: () => void }) { return (

No projects yet

Start creating your first video project. Import media, edit, and export professional videos.

); } function NoResults({ searchQuery, onClearSearch, }: { searchQuery: string; onClearSearch: () => void; }) { return (

No results found

Your search for "{searchQuery}" did not return any results.

); }