Skip to content

Commit fd21917

Browse files
authored
Merge pull request #332 from codeunia-dev/feat/companydashboard
Feat: Company Dashboard Enhancements
2 parents 657ca83 + eaebae3 commit fd21917

File tree

5 files changed

+290
-31
lines changed

5 files changed

+290
-31
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
'use client'
2+
3+
import React, { useState, useEffect } from 'react'
4+
import { useCompanyContext } from '@/contexts/CompanyContext'
5+
import { usePendingInvitationRedirect } from '@/lib/hooks/usePendingInvitationRedirect'
6+
import { Input } from '@/components/ui/input'
7+
import { Label } from '@/components/ui/label'
8+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
9+
import { Alert, AlertDescription } from '@/components/ui/alert'
10+
import { Separator } from '@/components/ui/separator'
11+
import { Badge } from '@/components/ui/badge'
12+
import {
13+
AlertCircle,
14+
Loader2,
15+
Mail,
16+
Phone,
17+
User,
18+
Building2,
19+
} from 'lucide-react'
20+
import { useToast } from '@/components/ui/use-toast'
21+
import { createClient } from '@/lib/supabase/client'
22+
23+
interface UserProfile {
24+
id: string
25+
email: string
26+
first_name: string | null
27+
last_name: string | null
28+
phone: string | null
29+
bio: string | null
30+
current_position: string | null
31+
company: string | null
32+
location: string | null
33+
github_url: string | null
34+
linkedin_url: string | null
35+
twitter_url: string | null
36+
avatar_url: string | null
37+
}
38+
39+
export default function CompanyProfilePage() {
40+
const { currentCompany, userRole, loading: contextLoading } = useCompanyContext()
41+
const isPendingInvitation = usePendingInvitationRedirect()
42+
const { toast } = useToast()
43+
const [loading, setLoading] = useState(true)
44+
const [profile, setProfile] = useState<UserProfile | null>(null)
45+
46+
const [formData, setFormData] = useState({
47+
first_name: '',
48+
last_name: '',
49+
phone: '',
50+
})
51+
52+
useEffect(() => {
53+
loadProfile()
54+
// eslint-disable-next-line react-hooks/exhaustive-deps
55+
}, [])
56+
57+
const loadProfile = async () => {
58+
try {
59+
const supabase = createClient()
60+
const { data: { user } } = await supabase.auth.getUser()
61+
62+
if (!user) {
63+
toast({
64+
title: 'Error',
65+
description: 'You must be logged in to view this page',
66+
variant: 'destructive',
67+
})
68+
return
69+
}
70+
71+
const { data, error } = await supabase
72+
.from('profiles')
73+
.select('*')
74+
.eq('id', user.id)
75+
.single()
76+
77+
if (error) throw error
78+
79+
setProfile(data)
80+
setFormData({
81+
first_name: data.first_name || '',
82+
last_name: data.last_name || '',
83+
phone: data.phone || '',
84+
})
85+
} catch (error) {
86+
console.error('Error loading profile:', error)
87+
toast({
88+
title: 'Error',
89+
description: 'Failed to load profile',
90+
variant: 'destructive',
91+
})
92+
} finally {
93+
setLoading(false)
94+
}
95+
}
96+
97+
98+
99+
if (contextLoading || isPendingInvitation || loading) {
100+
return (
101+
<div className="flex items-center justify-center min-h-[60vh]">
102+
<Loader2 className="h-8 w-8 animate-spin text-primary" />
103+
</div>
104+
)
105+
}
106+
107+
if (!profile) {
108+
return (
109+
<div className="flex items-center justify-center min-h-[60vh]">
110+
<Alert variant="destructive" className="max-w-md">
111+
<AlertCircle className="h-4 w-4" />
112+
<AlertDescription>Profile not found</AlertDescription>
113+
</Alert>
114+
</div>
115+
)
116+
}
117+
118+
return (
119+
<div className="space-y-6">
120+
{/* Header */}
121+
<div>
122+
<h1 className="text-3xl font-bold tracking-tight text-white">My Profile</h1>
123+
<p className="text-muted-foreground">
124+
View your personal information and company details
125+
</p>
126+
</div>
127+
128+
{/* Personal Information - Read Only */}
129+
<Card className="bg-zinc-900 border-zinc-800">
130+
<CardHeader>
131+
<CardTitle className="text-white flex items-center gap-2">
132+
<User className="h-5 w-5 text-purple-400" />
133+
Personal Information
134+
</CardTitle>
135+
<CardDescription>
136+
Your profile details (read-only)
137+
</CardDescription>
138+
</CardHeader>
139+
<CardContent className="space-y-4">
140+
<div className="grid gap-4 md:grid-cols-2">
141+
<div className="space-y-2">
142+
<Label className="text-zinc-200">First Name</Label>
143+
<Input
144+
value={formData.first_name}
145+
disabled
146+
className="bg-zinc-800 border-zinc-700 text-zinc-400"
147+
/>
148+
</div>
149+
<div className="space-y-2">
150+
<Label className="text-zinc-200">Last Name</Label>
151+
<Input
152+
value={formData.last_name}
153+
disabled
154+
className="bg-zinc-800 border-zinc-700 text-zinc-400"
155+
/>
156+
</div>
157+
</div>
158+
159+
<div className="space-y-2">
160+
<Label className="text-zinc-200 flex items-center gap-2">
161+
<Mail className="h-4 w-4 text-purple-400" />
162+
Email
163+
</Label>
164+
<Input
165+
type="email"
166+
value={profile.email}
167+
disabled
168+
className="bg-zinc-800 border-zinc-700 text-zinc-400"
169+
/>
170+
</div>
171+
172+
<div className="space-y-2">
173+
<Label className="text-zinc-200 flex items-center gap-2">
174+
<Phone className="h-4 w-4 text-purple-400" />
175+
Phone Number
176+
</Label>
177+
<Input
178+
type="tel"
179+
value={formData.phone || 'Not provided'}
180+
disabled
181+
className="bg-zinc-800 border-zinc-700 text-zinc-400"
182+
/>
183+
</div>
184+
185+
{currentCompany && (
186+
<>
187+
<Separator className="bg-zinc-800 my-4" />
188+
189+
<div className="space-y-3">
190+
<Label className="text-zinc-200 flex items-center gap-2">
191+
<Building2 className="h-4 w-4 text-purple-400" />
192+
Company Context
193+
</Label>
194+
195+
<div className="flex items-center justify-between p-3 bg-zinc-800/50 rounded-lg border border-zinc-700">
196+
<span className="text-zinc-400 text-sm">Company</span>
197+
<span className="text-white font-medium">{currentCompany.name}</span>
198+
</div>
199+
200+
<div className="flex items-center justify-between p-3 bg-zinc-800/50 rounded-lg border border-zinc-700">
201+
<span className="text-zinc-400 text-sm">Your Role</span>
202+
<Badge variant="secondary" className="capitalize bg-purple-600/20 text-purple-300 border-purple-600/30">
203+
{userRole}
204+
</Badge>
205+
</div>
206+
</div>
207+
</>
208+
)}
209+
</CardContent>
210+
</Card>
211+
212+
<Alert className="bg-zinc-900 border-zinc-800">
213+
<AlertCircle className="h-4 w-4 text-purple-400" />
214+
<AlertDescription className="text-zinc-300">
215+
Profile information is currently read-only. To update your details, please contact your administrator or support team.
216+
</AlertDescription>
217+
</Alert>
218+
</div>
219+
)
220+
}

app/dashboard/company/layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
CreditCard,
1818
} from 'lucide-react'
1919
import { useAuth } from '@/lib/hooks/useAuth'
20+
import { useRoleProtection } from '@/lib/hooks/useRoleProtection'
2021

2122
export type SidebarGroupType = {
2223
title: string
@@ -33,6 +34,7 @@ export default function CompanyDashboardLayout({
3334
children: React.ReactNode
3435
}) {
3536
const { user, loading, error } = useAuth()
37+
const { isChecking, isAuthorized } = useRoleProtection('company_member')
3638
const params = useParams()
3739
const companySlug = params?.slug as string | undefined
3840

@@ -51,7 +53,7 @@ export default function CompanyDashboardLayout({
5153
)
5254
}
5355

54-
if (loading) {
56+
if (loading || isChecking) {
5557
return (
5658
<div className="flex items-center justify-center min-h-screen bg-black">
5759
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
@@ -75,7 +77,7 @@ export default function CompanyDashboardLayout({
7577
)
7678
}
7779

78-
if (!user) {
80+
if (!user || !isAuthorized) {
7981
return (
8082
<div className="flex items-center justify-center min-h-screen px-4 bg-black">
8183
<div className="text-center max-w-md">

app/protected/layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
Star,
2525
} from "lucide-react"
2626
import { useAuth } from "@/lib/hooks/useAuth"
27+
import { useRoleProtection } from "@/lib/hooks/useRoleProtection"
2728

2829
export type SidebarGroupType = {
2930
title: string;
@@ -210,16 +211,17 @@ const sidebarItems: SidebarGroupType[] = [
210211

211212
export default function ProtectedLayout({ children }: { children: React.ReactNode }) {
212213
const { user, loading } = useAuth()
214+
const { isChecking, isAuthorized } = useRoleProtection('student')
213215

214-
if (loading) {
216+
if (loading || isChecking) {
215217
return (
216218
<div className="flex items-center justify-center min-h-screen">
217219
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
218220
</div>
219221
)
220222
}
221223

222-
if (!user) {
224+
if (!user || !isAuthorized) {
223225
return (
224226
<div className="flex items-center justify-center min-h-screen px-4">
225227
<div className="text-center max-w-md">

components/dashboard/CompanySidebar.tsx

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import Link from 'next/link'
33
import { usePathname } from 'next/navigation'
44
import { useSafeNavigation } from '@/lib/security/safe-navigation'
55
import {
6-
Bell,
76
LogOut,
87
User,
98
ChevronDown,
@@ -194,23 +193,11 @@ export function CompanySidebar({
194193
asChild
195194
className="flex items-center gap-2 px-3 py-2 hover:bg-purple-700/10 rounded-md cursor-pointer"
196195
>
197-
<Link href="/protected/profile" className="flex items-center w-full">
196+
<Link href={`/dashboard/company/${currentCompany?.slug}/profile`} className="flex items-center w-full">
198197
<User className="size-4 text-purple-400" />
199198
<span>My Profile</span>
200199
</Link>
201200
</DropdownMenuItem>
202-
<DropdownMenuItem
203-
asChild
204-
className="flex items-center gap-2 px-3 py-2 hover:bg-purple-700/10 rounded-md cursor-pointer"
205-
>
206-
<Link
207-
href="/protected/notifications"
208-
className="flex items-center w-full"
209-
>
210-
<Bell className="size-4 text-purple-400" />
211-
<span>Notifications</span>
212-
</Link>
213-
</DropdownMenuItem>
214201
<DropdownMenuSeparator />
215202
<DropdownMenuItem
216203
onClick={() => navigateTo('/auth/signin')}
@@ -452,23 +439,11 @@ export function CompanySidebar({
452439
asChild
453440
className="flex items-center gap-2 px-3 py-2 hover:bg-purple-700/10 rounded-md cursor-pointer"
454441
>
455-
<Link href="/protected/profile" className="flex items-center w-full">
442+
<Link href={`/dashboard/company/${currentCompany?.slug}/profile`} className="flex items-center w-full">
456443
<User className="size-4 text-purple-400" />
457444
<span>My Profile</span>
458445
</Link>
459446
</DropdownMenuItem>
460-
<DropdownMenuItem
461-
asChild
462-
className="flex items-center gap-2 px-3 py-2 hover:bg-purple-700/10 rounded-md cursor-pointer"
463-
>
464-
<Link
465-
href="/protected/notifications"
466-
className="flex items-center w-full"
467-
>
468-
<Bell className="size-4 text-purple-400" />
469-
<span>Notifications</span>
470-
</Link>
471-
</DropdownMenuItem>
472447
<DropdownMenuSeparator />
473448
<DropdownMenuItem
474449
onClick={() => navigateTo('/auth/signin')}

0 commit comments

Comments
 (0)