diff --git a/app/(dashboard)/dashboard/project-filter.tsx b/app/(dashboard)/dashboard/project-filter.tsx index e7c6ea9..d942258 100644 --- a/app/(dashboard)/dashboard/project-filter.tsx +++ b/app/(dashboard)/dashboard/project-filter.tsx @@ -1,8 +1,8 @@ 'use client'; -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import Link from 'next/link'; -import { Plus, FolderOpen, Clock, Users, Globe, Lock, UserPlus, Building2 } from 'lucide-react'; +import { Plus, FolderOpen, Clock, Users, Globe, Lock, UserPlus, Building2, ArrowUp, ArrowDown } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; @@ -57,13 +57,27 @@ function VisibilityIcon({ visibility }: { visibility: string }) { } } +type SortOrder = 'desc' | 'asc'; + export function ProjectFilter({ projects, workspaces }: ProjectFilterProps) { const [selectedWorkspace, setSelectedWorkspace] = useState('all'); + const [sortOrder, setSortOrder] = useState('desc'); - const filtered = - selectedWorkspace === 'all' - ? projects - : projects.filter((p) => p.workspaceId === selectedWorkspace); + const filtered = useMemo(() => { + let result = + selectedWorkspace === 'all' + ? projects + : projects.filter((p) => p.workspaceId === selectedWorkspace); + + // Sort by date + result = [...result].sort((a, b) => { + const dateA = new Date(a.updatedAt).getTime(); + const dateB = new Date(b.updatedAt).getTime(); + return sortOrder === 'desc' ? dateB - dateA : dateA - dateB; + }); + + return result; + }, [projects, selectedWorkspace, sortOrder]); return ( <> @@ -87,12 +101,32 @@ export function ProjectFilter({ projects, workspaces }: ProjectFilterProps) { )} - +
+ + +
{/* Projects Grid */} diff --git a/app/(dashboard)/projects/[projectId]/page.tsx b/app/(dashboard)/projects/[projectId]/page.tsx index aa91e9f..c087536 100644 --- a/app/(dashboard)/projects/[projectId]/page.tsx +++ b/app/(dashboard)/projects/[projectId]/page.tsx @@ -2,23 +2,14 @@ import Link from 'next/link'; import { notFound, redirect } from 'next/navigation'; import { ArrowLeft, - Plus, - Settings, - Share2, - Play, Globe, Lock, UserPlus, - Users, - Building2, } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { VideoCard } from '@/components/video-card'; import { GuestGate } from '@/components/guest-gate'; import { auth } from '@/lib/auth'; import { db } from '@/lib/db'; +import { ProjectContentClient } from './project-content-client'; function VisibilityIcon({ visibility }: { visibility: string }) { switch (visibility) { @@ -137,59 +128,49 @@ export default async function ProjectPage({ params }: ProjectPageProps) { commentCount: activeVersion?._count.comments || 0, duration: formatDuration(activeVersion?.duration), lastUpdated: formatRelativeTime(video.updatedAt), + updatedAt: video.updatedAt.toISOString(), }; }); const canEdit = isOwner || project.members[0]?.role === 'ADMIN' || workspaceRole === 'OWNER' || workspaceRole === 'ADMIN'; const isAuthenticated = !!session?.user?.id; + const projectData = { + name: project.name, + description: project.description, + visibility: project.visibility, + workspace: project.workspace, + members: project.members, + }; + // Guest name gate for unauthenticated users on public projects if (!isAuthenticated && isPublic) { return ( - +
+ {/* Back link */} +
+ + + Back to Projects + +
+ +
); } - return ( - - ); -} - -function ProjectContent({ - project, - projectId, - videos, - canEdit, - isOwner, - isPublic, - workspaceRole, -}: { - project: { name: string; description: string | null; visibility: string; workspace: { id: string; name: string } | null; members: { role: string }[] }; - projectId: string; - videos: { id: string; title: string; thumbnailUrl: string; currentVersion: number; commentCount: number; duration: string; lastUpdated: string }[]; - canEdit: boolean; - isOwner: boolean; - isPublic: boolean; - workspaceRole: string | null; -}) { return (
{/* Back link */} @@ -202,92 +183,14 @@ function ProjectContent({ Back to Projects
- - {/* Project Header */} -
-
-
-

{project.name}

- - - {project.visibility.toLowerCase()} - -
-
- {project.workspace && ( - - - - {project.workspace.name} - - - )} - {project.description && ( - {project.description} - )} -
-
- -
- - {(isOwner || project.members[0]?.role === 'ADMIN') && ( - <> - - - - )} - {canEdit && ( - - )} -
-
- - {/* Videos Grid */} - {videos.length > 0 ? ( -
- {videos.map((video) => ( - - ))} -
- ) : ( - - - -

No videos yet

-

- Add your first video to start collecting feedback -

- {canEdit && ( - - )} -
-
- )} + ); } diff --git a/app/(dashboard)/projects/[projectId]/project-content-client.tsx b/app/(dashboard)/projects/[projectId]/project-content-client.tsx new file mode 100644 index 0000000..e2d7b23 --- /dev/null +++ b/app/(dashboard)/projects/[projectId]/project-content-client.tsx @@ -0,0 +1,176 @@ +'use client'; + +import { useState, useMemo } from 'react'; +import Link from 'next/link'; +import { + Plus, + Settings, + Share2, + Play, + Users, + Building2, + ArrowUp, + ArrowDown, +} from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { VideoCard } from '@/components/video-card'; + +type SortOrder = 'desc' | 'asc'; + +interface SerializedVideo { + id: string; + title: string; + thumbnailUrl: string; + currentVersion: number; + commentCount: number; + duration: string; + lastUpdated: string; + updatedAt: string; +} + +interface ProjectContentClientProps { + project: { + name: string; + description: string | null; + visibility: string; + workspace: { id: string; name: string } | null; + members: { role: string }[]; + }; + projectId: string; + videos: SerializedVideo[]; + canEdit: boolean; + isOwner: boolean; + workspaceRole: string | null; +} + +export function ProjectContentClient({ + project, + projectId, + videos, + canEdit, + isOwner, + workspaceRole, +}: ProjectContentClientProps) { + const [sortOrder, setSortOrder] = useState('desc'); + + const sortedVideos = useMemo(() => { + return [...videos].sort((a, b) => { + const dateA = new Date(a.updatedAt).getTime(); + const dateB = new Date(b.updatedAt).getTime(); + return sortOrder === 'desc' ? dateB - dateA : dateA - dateB; + }); + }, [videos, sortOrder]); + + return ( + <> + {/* Project Header */} +
+
+
+

{project.name}

+ + {project.visibility === 'PUBLIC' && 🌐} + {project.visibility === 'INVITE' && 📧} + {project.visibility === 'PRIVATE' && 🔒} + {project.visibility.toLowerCase()} + +
+
+ {project.workspace && ( + + + + {project.workspace.name} + + + )} + {project.description && ( + {project.description} + )} +
+
+ +
+ {/* Sort Button - Left of Share */} + + + {(isOwner || project.members[0]?.role === 'ADMIN') && ( + <> + + + + )} + {canEdit && ( + + )} +
+
+ + {/* Videos Grid */} + {videos.length > 0 ? ( +
+ {sortedVideos.map((video) => ( + + ))} +
+ ) : ( + + + +

No videos yet

+

+ Add your first video to start collecting feedback +

+ {canEdit && ( + + )} +
+
+ )} + + ); +}