summaryrefslogtreecommitdiff
path: root/apps/web/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/app')
-rw-r--r--apps/web/src/app/contributors/page.tsx3
-rw-r--r--apps/web/src/app/editor/[project_id]/page.tsx (renamed from apps/web/src/app/editor/page.tsx)16
-rw-r--r--apps/web/src/app/editor/editor.css4
-rw-r--r--apps/web/src/app/globals.css4
-rw-r--r--apps/web/src/app/layout.tsx3
-rw-r--r--apps/web/src/app/page.tsx7
-rw-r--r--apps/web/src/app/projects/page.tsx260
7 files changed, 272 insertions, 25 deletions
diff --git a/apps/web/src/app/contributors/page.tsx b/apps/web/src/app/contributors/page.tsx
index 67957c32..feee2513 100644
--- a/apps/web/src/app/contributors/page.tsx
+++ b/apps/web/src/app/contributors/page.tsx
@@ -140,9 +140,6 @@ export default async function ContributorsPage() {
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
- <div className="absolute -top-2 -right-2 bg-foreground text-background rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold">
- {index + 1}
- </div>
</div>
<h3 className="text-xl font-semibold mb-2 group-hover:text-foreground/80 transition-colors">
{contributor.login}
diff --git a/apps/web/src/app/editor/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx
index e0013937..8eee5e0a 100644
--- a/apps/web/src/app/editor/page.tsx
+++ b/apps/web/src/app/editor/[project_id]/page.tsx
@@ -1,16 +1,15 @@
"use client";
import { useEffect } from "react";
-import "./editor.css";
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
-} from "../../components/ui/resizable";
-import { MediaPanel } from "../../components/editor/media-panel";
+} 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 { 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";
@@ -21,12 +20,10 @@ export default function Editor() {
const {
toolsPanel,
previewPanel,
- propertiesPanel,
mainContent,
timeline,
setToolsPanel,
setPreviewPanel,
- setPropertiesPanel,
setMainContent,
setTimeline,
} = usePanelStore();
@@ -55,7 +52,10 @@ export default function Editor() {
className="min-h-0"
>
{/* Main content area */}
- <ResizablePanelGroup direction="horizontal" className="h-full w-full">
+ <ResizablePanelGroup
+ direction="horizontal"
+ className="h-full w-full"
+ >
{/* Tools Panel */}
<ResizablePanel
defaultSize={toolsPanel}
diff --git a/apps/web/src/app/editor/editor.css b/apps/web/src/app/editor/editor.css
deleted file mode 100644
index f482c6de..00000000
--- a/apps/web/src/app/editor/editor.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/* Prevent scroll jumping on Mac devices when using the editor */
-body {
- overflow: hidden;
-} \ No newline at end of file
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index e8727432..eafdc861 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -40,10 +40,10 @@
}
.dark {
--background: 0 0% 8%;
- --foreground: 0 0% 98%;
+ --foreground: 0 0% 89%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
- --popover: 0 0% 3.9%;
+ --popover: 0 0% 14.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
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({
<body className={`${inter.variable} font-sans antialiased`}>
<ThemeProvider attribute="class" forcedTheme="dark" enableSystem>
<TooltipProvider>
- {children}
+ <StorageProvider>{children}</StorageProvider>
<Analytics />
<Toaster />
<DevelopmentDebug />
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 183b6a56..f78e9d52 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -12,13 +12,6 @@ export default async function Home() {
return (
<div>
- <Image
- className="fixed top-0 left-0 -z-50 size-full object-cover"
- src="/landing-page-bg.png"
- height={1903.5}
- width={1269}
- alt="landing-page.bg"
- />
<Header />
<Hero signupCount={signupCount} />
<Footer />
diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx
new file mode 100644
index 00000000..c983894d
--- /dev/null
+++ b/apps/web/src/app/projects/page.tsx
@@ -0,0 +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,
+ 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 (
+ <div className="min-h-screen bg-background">
+ <div className="pt-6 px-6 flex items-center justify-between w-full h-16">
+ <Link
+ href="/"
+ className="flex items-center gap-1 hover:text-muted-foreground transition-colors"
+ >
+ <ChevronLeft className="!size-5 shrink-0" />
+ <span className="text-sm font-medium">Back</span>
+ </Link>
+ <div className="block md:hidden">
+ <CreateButton onClick={handleCreateProject} />
+ </div>
+ </div>
+ <main className="max-w-6xl mx-auto px-6 pt-6 pb-6">
+ <div className="mb-8 flex items-center justify-between">
+ <div className="flex flex-col gap-3">
+ <h1 className="text-2xl md:text-3xl font-bold tracking-tight">
+ Your Projects
+ </h1>
+ <p className="text-muted-foreground">
+ {savedProjects.length}{" "}
+ {savedProjects.length === 1 ? "project" : "projects"}
+ </p>
+ </div>
+ <div className="hidden md:block">
+ <CreateButton onClick={handleCreateProject} />
+ </div>
+ </div>
+
+ {isLoading || !isInitialized ? (
+ <div className="flex items-center justify-center py-16">
+ <Loader2 className="h-8 w-8 text-muted-foreground animate-spin" />
+ </div>
+ ) : savedProjects.length === 0 ? (
+ <NoProjects onCreateProject={handleCreateProject} />
+ ) : (
+ <div className="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6">
+ {savedProjects.map((project) => (
+ <ProjectCard key={project.id} project={project} />
+ ))}
+ </div>
+ )}
+ </main>
+ </div>
+ );
+}
+
+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 (
+ <>
+ <Link href={`/editor/${project.id}`} className="block group">
+ <Card className="overflow-hidden bg-background border-none p-0">
+ <div
+ className={`relative aspect-square bg-muted transition-opacity ${
+ isDropdownOpen
+ ? "opacity-65"
+ : "opacity-100 group-hover:opacity-65"
+ }`}
+ >
+ {/* Thumbnail preview or placeholder */}
+ <div className="absolute inset-0">
+ {project.thumbnail ? (
+ <Image
+ src={project.thumbnail}
+ alt="Project thumbnail"
+ fill
+ className="object-cover"
+ />
+ ) : (
+ <div className="w-full h-full bg-muted/50 flex items-center justify-center">
+ <Video className="h-12 w-12 flex-shrink-0 text-muted-foreground" />
+ </div>
+ )}
+ </div>
+ </div>
+
+ <CardContent className="px-0 pt-5 flex flex-col gap-1">
+ <div className="flex items-start justify-between">
+ <h3 className="font-medium text-sm leading-snug group-hover:text-foreground/90 transition-colors line-clamp-2">
+ {project.name}
+ </h3>
+ <DropdownMenu
+ open={isDropdownOpen}
+ onOpenChange={setIsDropdownOpen}
+ >
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="text"
+ size="sm"
+ className={`size-6 p-0 transition-all shrink-0 ml-2 ${
+ isDropdownOpen
+ ? "opacity-100"
+ : "opacity-0 group-hover:opacity-100"
+ }`}
+ onClick={(e) => e.preventDefault()}
+ >
+ <MoreHorizontal />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent
+ align="end"
+ onCloseAutoFocus={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ >
+ <DropdownMenuItem
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDropdownOpen(false);
+ setIsRenameDialogOpen(true);
+ }}
+ >
+ Rename
+ </DropdownMenuItem>
+ <DropdownMenuItem
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ handleDuplicateProject();
+ }}
+ >
+ Duplicate
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem
+ variant="destructive"
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDropdownOpen(false);
+ setIsDeleteDialogOpen(true);
+ }}
+ >
+ Delete
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </div>
+
+ <div className="space-y-1">
+ <div className="flex items-center gap-1.5 text-sm text-muted-foreground">
+ <Calendar className="!size-4" />
+ <span>Created {formatDate(project.createdAt)}</span>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ </Link>
+ <DeleteProjectDialog
+ isOpen={isDeleteDialogOpen}
+ onOpenChange={setIsDeleteDialogOpen}
+ onConfirm={handleDeleteProject}
+ />
+ <RenameProjectDialog
+ isOpen={isRenameDialogOpen}
+ onOpenChange={setIsRenameDialogOpen}
+ onConfirm={handleRenameProject}
+ projectName={project.name}
+ />
+ </>
+ );
+}
+
+function CreateButton({ onClick }: { onClick?: () => void }) {
+ return (
+ <Button className="flex" onClick={onClick}>
+ <Plus className="!size-4" />
+ <span className="text-sm font-medium">New project</span>
+ </Button>
+ );
+}
+
+function NoProjects({ onCreateProject }: { onCreateProject: () => void }) {
+ return (
+ <div className="flex flex-col items-center justify-center py-16 text-center">
+ <div className="w-16 h-16 rounded-full bg-muted/30 flex items-center justify-center mb-4">
+ <Video className="h-8 w-8 text-muted-foreground" />
+ </div>
+ <h3 className="text-lg font-medium mb-2">No projects yet</h3>
+ <p className="text-muted-foreground mb-6 max-w-md">
+ Start creating your first video project. Import media, edit, and export
+ professional videos.
+ </p>
+ <Button size="lg" className="gap-2" onClick={onCreateProject}>
+ <Plus className="h-4 w-4" />
+ Create Your First Project
+ </Button>
+ </div>
+ );
+}