From 09373eb4a3f792d96d97d81f4fb86539d76e5702 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 30 Jun 2025 19:58:36 +0200 Subject: feat: implement project management features with storage integration, including project creation, deletion, and renaming dialogs --- apps/web/src/app/editor/[project_id]/page.tsx | 116 ++++++ apps/web/src/app/editor/page.tsx | 114 ------ apps/web/src/app/layout.tsx | 3 +- apps/web/src/app/projects/page.tsx | 488 ++++++++++++++------------ 4 files changed, 378 insertions(+), 343 deletions(-) create mode 100644 apps/web/src/app/editor/[project_id]/page.tsx delete mode 100644 apps/web/src/app/editor/page.tsx (limited to 'apps/web/src/app') diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx new file mode 100644 index 00000000..3970d72a --- /dev/null +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -0,0 +1,116 @@ +"use client"; + +import { useEffect } from "react"; +import { + ResizablePanelGroup, + ResizablePanel, + ResizableHandle, +} from "../../../components/ui/resizable"; +import { MediaPanel } from "../../../components/editor/media-panel"; +// import { PropertiesPanel } from "../../components/editor/properties-panel"; +import { Timeline } from "../../../components/editor/timeline"; +import { PreviewPanel } from "../../../components/editor/preview-panel"; +import { EditorHeader } from "@/components/editor-header"; +import { usePanelStore } from "@/stores/panel-store"; +import { useProjectStore } from "@/stores/project-store"; +import { EditorProvider } from "@/components/editor-provider"; +import { usePlaybackControls } from "@/hooks/use-playback-controls"; + +export default function Editor() { + const { + toolsPanel, + previewPanel, + propertiesPanel, + mainContent, + timeline, + setToolsPanel, + setPreviewPanel, + setPropertiesPanel, + setMainContent, + setTimeline, + } = usePanelStore(); + + const { activeProject, createNewProject } = useProjectStore(); + + usePlaybackControls(); + + useEffect(() => { + if (!activeProject) { + createNewProject("Untitled Project"); + } + }, [activeProject, createNewProject]); + + return ( + +
+ +
+ + + {/* Main content area */} + + {/* Tools Panel */} + + + + + + + {/* Preview Area */} + + + + + + + {/* Properties Panel - Hidden for now but ready */} + {/* + + */} + + + + + + {/* Timeline */} + + + + +
+
+
+ ); +} diff --git a/apps/web/src/app/editor/page.tsx b/apps/web/src/app/editor/page.tsx deleted file mode 100644 index e0013937..00000000 --- a/apps/web/src/app/editor/page.tsx +++ /dev/null @@ -1,114 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import "./editor.css"; -import { - ResizablePanelGroup, - ResizablePanel, - ResizableHandle, -} from "../../components/ui/resizable"; -import { MediaPanel } from "../../components/editor/media-panel"; -// import { PropertiesPanel } from "../../components/editor/properties-panel"; -import { Timeline } from "../../components/editor/timeline"; -import { PreviewPanel } from "../../components/editor/preview-panel"; -import { EditorHeader } from "@/components/editor-header"; -import { usePanelStore } from "@/stores/panel-store"; -import { useProjectStore } from "@/stores/project-store"; -import { EditorProvider } from "@/components/editor-provider"; -import { usePlaybackControls } from "@/hooks/use-playback-controls"; - -export default function Editor() { - const { - toolsPanel, - previewPanel, - propertiesPanel, - mainContent, - timeline, - setToolsPanel, - setPreviewPanel, - setPropertiesPanel, - setMainContent, - setTimeline, - } = usePanelStore(); - - const { activeProject, createNewProject } = useProjectStore(); - - usePlaybackControls(); - - useEffect(() => { - if (!activeProject) { - createNewProject("Untitled Project"); - } - }, [activeProject, createNewProject]); - - return ( - -
- -
- - - {/* Main content area */} - - {/* Tools Panel */} - - - - - - - {/* Preview Area */} - - - - - - - {/* Properties Panel - Hidden for now but ready */} - {/* - - */} - - - - - - {/* Timeline */} - - - - -
-
-
- ); -} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 8188d31b..4f8243b5 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -6,6 +6,7 @@ import "./globals.css"; import { Toaster } from "../components/ui/sonner"; import { TooltipProvider } from "../components/ui/tooltip"; import { DevelopmentDebug } from "../components/development-debug"; +import { StorageProvider } from "../components/storage-provider"; import { baseMetaData } from "./metadata"; const inter = Inter({ @@ -25,7 +26,7 @@ export default function RootLayout({ - {children} + {children} diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index 82bae5d1..c983894d 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -1,228 +1,260 @@ -"use client"; - -import Link from "next/link"; -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { - ChevronLeft, - Plus, - Calendar, - MoreHorizontal, - Video, -} from "lucide-react"; -import { TProject } from "@/types/project"; -import Image from "next/image"; -import { - DropdownMenu, - DropdownMenuItem, - DropdownMenuContent, - DropdownMenuTrigger, - DropdownMenuSeparator, -} from "@/components/ui/dropdown-menu"; - -// Hard-coded project data -const mockProjects: TProject[] = [ - { - id: "1", - name: "Summer Vacation Highlights", - createdAt: new Date("2024-12-15"), - updatedAt: new Date("2024-12-20"), - thumbnail: - "https://plus.unsplash.com/premium_photo-1750854354243-81f40af63a73?q=80&w=687&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - }, - { - id: "2", - name: "Product Demo Video", - createdAt: new Date("2024-12-10"), - updatedAt: new Date("2024-12-18"), - thumbnail: - "https://images.unsplash.com/photo-1750875936215-0c35c1742cd6?q=80&w=688&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - }, - { - id: "3", - name: "Wedding Ceremony Edit", - createdAt: new Date("2024-12-05"), - updatedAt: new Date("2024-12-16"), - thumbnail: - "https://images.unsplash.com/photo-1750967991618-7b64a3025381?q=80&w=687&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - }, - { - id: "4", - name: "Travel Vlog - Japan", - createdAt: new Date("2024-11-28"), - updatedAt: new Date("2024-12-14"), - thumbnail: - "https://images.unsplash.com/photo-1750639258774-9a714379a093?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - }, -]; - -// Mock duration data (in seconds) -const mockDurations: Record = { - "1": 245, // 4:05 - "2": 120, // 2:00 - "3": 1800, // 30:00 - "4": 780, // 13:00 - "5": 360, // 6:00 - "6": 180, // 3:00 -}; - -export default function ProjectsPage() { - return ( -
-
- - - Back - -
- -
-
-
-
-
-

- Your Projects -

-

- {mockProjects.length}{" "} - {mockProjects.length === 1 ? "project" : "projects"} -

-
-
- -
-
- - {mockProjects.length === 0 ? ( -
-
-
-

No projects yet

-

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

- - - -
- ) : ( -
- {mockProjects.map((project, index) => ( - - ))} -
- )} -
-
- ); -} - -function ProjectCard({ project }: { project: TProject }) { - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - - const formatDuration = (seconds: number): string => { - const minutes = Math.floor(seconds / 60); - const remainingSeconds = Math.floor(seconds % 60); - return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`; - }; - - const formatDate = (date: Date): string => { - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }); - }; - - return ( - - -
- {/* Thumbnail preview */} -
- Project thumbnail -
- - {/* Duration badge */} -
- {formatDuration(mockDurations[project.id] || 0)} -
-
- - -
-

- {project.name} -

- - - - - { - e.preventDefault(); - e.stopPropagation(); - console.log("close"); - }} - > - Rename - Duplicate - - - Delete - - - -
- -
-
- - Created {formatDate(project.createdAt)} -
-
-
-
- - ); -} - -function CreateButton() { - return ( - - ); -} +"use client"; + +import Link from "next/link"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { + ChevronLeft, + Plus, + Calendar, + MoreHorizontal, + Video, + Loader2, +} from "lucide-react"; +import { TProject } from "@/types/project"; +import Image from "next/image"; +import { + DropdownMenu, + DropdownMenuItem, + DropdownMenuContent, + DropdownMenuTrigger, + DropdownMenuSeparator, +} from "@/components/ui/dropdown-menu"; +import { useProjectStore } from "@/stores/project-store"; +import { useRouter } from "next/navigation"; +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 router = useRouter(); + + const handleCreateProject = async () => { + const projectId = await createNewProject("New Project"); + console.log("projectId", projectId); + router.push(`/editor/${projectId}`); + }; + + return ( +
+
+ + + Back + +
+ +
+
+
+
+
+

+ Your Projects +

+

+ {savedProjects.length}{" "} + {savedProjects.length === 1 ? "project" : "projects"} +

+
+
+ +
+
+ + {isLoading || !isInitialized ? ( +
+ +
+ ) : savedProjects.length === 0 ? ( + + ) : ( +
+ {savedProjects.map((project) => ( + + ))} +
+ )} +
+
+ ); +} + +function ProjectCard({ project }: { project: TProject }) { + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); + const { deleteProject, renameProject, duplicateProject } = useProjectStore(); + + 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); + }; + + 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)} +
+
+
+
+ + + + + ); +} + +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. +

+ +
+ ); +} -- cgit v1.3.1