|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | | -import { useCallback } from 'react'; |
4 | | -import { useParams, useRouter } from 'next/navigation'; |
5 | | -import { formatDistanceToNow } from 'date-fns'; |
6 | | -import { RefreshCw } from 'lucide-react'; |
| 3 | +import { useState, useEffect } from 'react'; |
| 4 | +import { useParams, useSearchParams } from 'next/navigation'; |
| 5 | +import { Star, Settings, Users, RefreshCw } from 'lucide-react'; |
| 6 | +import { cn } from '@/lib/utils'; |
7 | 7 |
|
8 | 8 | import { Button } from '@/components/ui/button'; |
9 | 9 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; |
10 | | -import { Label } from '@/components/ui/label'; |
11 | | -import { ProjectSubpageHeader } from '@/components/project-subpage-header'; |
12 | | -import { ErrorMessage } from '@/components/error-message'; |
| 10 | +import { PageHeader } from '@/components/page-header'; |
13 | 11 | import { Breadcrumbs } from '@/components/breadcrumbs'; |
14 | 12 |
|
15 | | -import { useProject } from '@/services/queries'; |
| 13 | +import { SessionsSection } from '@/components/workspace-sections/sessions-section'; |
| 14 | +import { SharingSection } from '@/components/workspace-sections/sharing-section'; |
| 15 | +import { SettingsSection } from '@/components/workspace-sections/settings-section'; |
| 16 | +import { useProject } from '@/services/queries/use-projects'; |
| 17 | + |
| 18 | +type Section = 'sessions' | 'sharing' | 'settings'; |
16 | 19 |
|
17 | 20 | export default function ProjectDetailsPage() { |
18 | 21 | const params = useParams(); |
19 | | - const router = useRouter(); |
| 22 | + const searchParams = useSearchParams(); |
20 | 23 | const projectName = params?.name as string; |
| 24 | + |
| 25 | + // Fetch project data for display name and description |
| 26 | + const { data: project, isLoading: projectLoading } = useProject(projectName); |
| 27 | + |
| 28 | + // Initialize active section from query parameter or default to 'sessions' |
| 29 | + const initialSection = (searchParams.get('section') as Section) || 'sessions'; |
| 30 | + const [activeSection, setActiveSection] = useState<Section>(initialSection); |
21 | 31 |
|
22 | | - // React Query hook replaces all manual state management |
23 | | - const { data: project, isLoading, error, refetch } = useProject(projectName); |
| 32 | + // Update active section when query parameter changes |
| 33 | + useEffect(() => { |
| 34 | + const sectionParam = searchParams.get('section') as Section; |
| 35 | + if (sectionParam && ['sessions', 'sharing', 'settings'].includes(sectionParam)) { |
| 36 | + setActiveSection(sectionParam); |
| 37 | + } |
| 38 | + }, [searchParams]); |
24 | 39 |
|
25 | | - const handleRefresh = useCallback(() => { |
26 | | - refetch(); |
27 | | - }, [refetch]); |
| 40 | + const navItems = [ |
| 41 | + { id: 'sessions' as Section, label: 'Sessions', icon: Star }, |
| 42 | + { id: 'sharing' as Section, label: 'Sharing', icon: Users }, |
| 43 | + { id: 'settings' as Section, label: 'Workspace Settings', icon: Settings }, |
| 44 | + ]; |
28 | 45 |
|
29 | 46 | // Loading state |
30 | | - if (!projectName || (isLoading && !project)) { |
| 47 | + if (!projectName || projectLoading) { |
31 | 48 | return ( |
32 | 49 | <div className="container mx-auto p-6"> |
33 | 50 | <div className="flex items-center justify-center h-64"> |
34 | 51 | <RefreshCw className="animate-spin h-8 w-8" /> |
35 | | - <span className="ml-2">Loading project...</span> |
| 52 | + <span className="ml-2">Loading workspace...</span> |
36 | 53 | </div> |
37 | 54 | </div> |
38 | 55 | ); |
39 | 56 | } |
40 | 57 |
|
41 | | - // Error state (no project loaded) |
42 | | - if (error && !project) { |
43 | | - return ( |
44 | | - <div className="container mx-auto p-6"> |
45 | | - <Card className="border-red-200 bg-red-50"> |
46 | | - <CardContent className="pt-6"> |
47 | | - <p className="text-red-700">{error instanceof Error ? error.message : 'Failed to load project'}</p> |
48 | | - <div className="mt-4 flex gap-4"> |
49 | | - <Button onClick={() => router.push('/projects')} variant="outline"> |
50 | | - Back to Projects |
51 | | - </Button> |
52 | | - <Button onClick={handleRefresh}>Try Again</Button> |
53 | | - </div> |
54 | | - </CardContent> |
55 | | - </Card> |
56 | | - </div> |
57 | | - ); |
58 | | - } |
59 | | - |
60 | | - if (!project) return null; |
61 | | - |
62 | 58 | return ( |
63 | | - <div className="container mx-auto p-6"> |
64 | | - <Breadcrumbs |
65 | | - items={[ |
66 | | - { label: 'Projects', href: '/projects' }, |
67 | | - { label: project.displayName || project.name }, |
68 | | - ]} |
69 | | - className="mb-4" |
70 | | - /> |
71 | | - <ProjectSubpageHeader |
72 | | - title={<>{project.displayName || project.name}</>} |
73 | | - description={<>{projectName}</>} |
74 | | - actions={ |
75 | | - <Button variant="outline" onClick={handleRefresh} disabled={isLoading}> |
76 | | - <RefreshCw className={`w-4 h-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} /> |
77 | | - Refresh |
78 | | - </Button> |
79 | | - } |
80 | | - /> |
81 | | - |
82 | | - {/* Error state (with project loaded) */} |
83 | | - {error && project && ( |
84 | | - <div className="px-6"> |
85 | | - <ErrorMessage error={error} onRetry={handleRefresh} /> |
| 59 | + <div className="min-h-screen bg-[#f8fafc]"> |
| 60 | + {/* Sticky header */} |
| 61 | + <div className="sticky top-0 z-20 bg-white border-b"> |
| 62 | + <div className="container mx-auto px-6 py-4"> |
| 63 | + <Breadcrumbs |
| 64 | + items={[ |
| 65 | + { label: 'Workspaces', href: '/projects' }, |
| 66 | + { label: projectName }, |
| 67 | + ]} |
| 68 | + className="mb-4" |
| 69 | + /> |
| 70 | + <PageHeader |
| 71 | + title={project?.displayName || projectName} |
| 72 | + description={project?.description || 'Workspace details and configuration'} |
| 73 | + /> |
86 | 74 | </div> |
87 | | - )} |
| 75 | + </div> |
| 76 | + |
| 77 | + <div className="container mx-auto p-0"> |
| 78 | + {/* Content */} |
| 79 | + <div className="px-6 pt-4 flex gap-6"> |
| 80 | + {/* Sidebar Navigation */} |
| 81 | + <aside className="w-56 shrink-0"> |
| 82 | + <Card> |
| 83 | + <CardHeader> |
| 84 | + <CardTitle>Workspace</CardTitle> |
| 85 | + </CardHeader> |
| 86 | + <CardContent className="px-4 pb-4 pt-2"> |
| 87 | + <div className="space-y-1"> |
| 88 | + {navItems.map((item) => { |
| 89 | + const isActive = activeSection === item.id; |
| 90 | + const Icon = item.icon; |
| 91 | + return ( |
| 92 | + <Button |
| 93 | + key={item.id} |
| 94 | + variant={isActive ? "secondary" : "ghost"} |
| 95 | + className={cn("w-full justify-start", isActive && "font-semibold")} |
| 96 | + onClick={() => setActiveSection(item.id)} |
| 97 | + > |
| 98 | + <Icon className="w-4 h-4 mr-2" /> |
| 99 | + {item.label} |
| 100 | + </Button> |
| 101 | + ); |
| 102 | + })} |
| 103 | + </div> |
| 104 | + </CardContent> |
| 105 | + </Card> |
| 106 | + </aside> |
88 | 107 |
|
89 | | - <div className="pt-2"> |
90 | | - <div className="grid grid-cols-1 lg:grid-cols-2 gap-4"> |
91 | | - {/* Project Info */} |
92 | | - <Card> |
93 | | - <CardHeader> |
94 | | - <CardTitle>Project Information</CardTitle> |
95 | | - </CardHeader> |
96 | | - <CardContent className="space-y-4"> |
97 | | - <div> |
98 | | - <Label className="text-sm font-medium">Description</Label> |
99 | | - <p className="text-sm text-muted-foreground"> |
100 | | - {project.description || 'No description provided'} |
101 | | - </p> |
102 | | - </div> |
103 | | - <div> |
104 | | - <Label className="text-sm font-medium">Created</Label> |
105 | | - <p className="text-sm text-muted-foreground"> |
106 | | - {project.creationTimestamp && |
107 | | - formatDistanceToNow(new Date(project.creationTimestamp), { |
108 | | - addSuffix: true, |
109 | | - })} |
110 | | - </p> |
111 | | - </div> |
112 | | - </CardContent> |
113 | | - </Card> |
| 108 | + {/* Main Content */} |
| 109 | + {activeSection === 'sessions' && <SessionsSection projectName={projectName} />} |
| 110 | + {activeSection === 'sharing' && <SharingSection projectName={projectName} />} |
| 111 | + {activeSection === 'settings' && <SettingsSection projectName={projectName} />} |
114 | 112 | </div> |
115 | 113 | </div> |
116 | 114 | </div> |
|
0 commit comments