diff --git a/app/dashboard/company/[slug]/profile/page.tsx b/app/dashboard/company/[slug]/profile/page.tsx new file mode 100644 index 00000000..1ccef3da --- /dev/null +++ b/app/dashboard/company/[slug]/profile/page.tsx @@ -0,0 +1,220 @@ +'use client' + +import React, { useState, useEffect } from 'react' +import { useCompanyContext } from '@/contexts/CompanyContext' +import { usePendingInvitationRedirect } from '@/lib/hooks/usePendingInvitationRedirect' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { Separator } from '@/components/ui/separator' +import { Badge } from '@/components/ui/badge' +import { + AlertCircle, + Loader2, + Mail, + Phone, + User, + Building2, +} from 'lucide-react' +import { useToast } from '@/components/ui/use-toast' +import { createClient } from '@/lib/supabase/client' + +interface UserProfile { + id: string + email: string + first_name: string | null + last_name: string | null + phone: string | null + bio: string | null + current_position: string | null + company: string | null + location: string | null + github_url: string | null + linkedin_url: string | null + twitter_url: string | null + avatar_url: string | null +} + +export default function CompanyProfilePage() { + const { currentCompany, userRole, loading: contextLoading } = useCompanyContext() + const isPendingInvitation = usePendingInvitationRedirect() + const { toast } = useToast() + const [loading, setLoading] = useState(true) + const [profile, setProfile] = useState(null) + + const [formData, setFormData] = useState({ + first_name: '', + last_name: '', + phone: '', + }) + + useEffect(() => { + loadProfile() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const loadProfile = async () => { + try { + const supabase = createClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + toast({ + title: 'Error', + description: 'You must be logged in to view this page', + variant: 'destructive', + }) + return + } + + const { data, error } = await supabase + .from('profiles') + .select('*') + .eq('id', user.id) + .single() + + if (error) throw error + + setProfile(data) + setFormData({ + first_name: data.first_name || '', + last_name: data.last_name || '', + phone: data.phone || '', + }) + } catch (error) { + console.error('Error loading profile:', error) + toast({ + title: 'Error', + description: 'Failed to load profile', + variant: 'destructive', + }) + } finally { + setLoading(false) + } + } + + + + if (contextLoading || isPendingInvitation || loading) { + return ( +
+ +
+ ) + } + + if (!profile) { + return ( +
+ + + Profile not found + +
+ ) + } + + return ( +
+ {/* Header */} +
+

My Profile

+

+ View your personal information and company details +

+
+ + {/* Personal Information - Read Only */} + + + + + Personal Information + + + Your profile details (read-only) + + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ + {currentCompany && ( + <> + + +
+ + +
+ Company + {currentCompany.name} +
+ +
+ Your Role + + {userRole} + +
+
+ + )} +
+
+ + + + + Profile information is currently read-only. To update your details, please contact your administrator or support team. + + +
+ ) +} diff --git a/app/dashboard/company/layout.tsx b/app/dashboard/company/layout.tsx index b73a2dc1..92ab7759 100644 --- a/app/dashboard/company/layout.tsx +++ b/app/dashboard/company/layout.tsx @@ -17,6 +17,7 @@ import { CreditCard, } from 'lucide-react' import { useAuth } from '@/lib/hooks/useAuth' +import { useRoleProtection } from '@/lib/hooks/useRoleProtection' export type SidebarGroupType = { title: string @@ -33,6 +34,7 @@ export default function CompanyDashboardLayout({ children: React.ReactNode }) { const { user, loading, error } = useAuth() + const { isChecking, isAuthorized } = useRoleProtection('company_member') const params = useParams() const companySlug = params?.slug as string | undefined @@ -51,7 +53,7 @@ export default function CompanyDashboardLayout({ ) } - if (loading) { + if (loading || isChecking) { return (
@@ -75,7 +77,7 @@ export default function CompanyDashboardLayout({ ) } - if (!user) { + if (!user || !isAuthorized) { return (
diff --git a/app/protected/layout.tsx b/app/protected/layout.tsx index 1a8084bf..507ec883 100644 --- a/app/protected/layout.tsx +++ b/app/protected/layout.tsx @@ -24,6 +24,7 @@ import { Star, } from "lucide-react" import { useAuth } from "@/lib/hooks/useAuth" +import { useRoleProtection } from "@/lib/hooks/useRoleProtection" export type SidebarGroupType = { title: string; @@ -210,8 +211,9 @@ const sidebarItems: SidebarGroupType[] = [ export default function ProtectedLayout({ children }: { children: React.ReactNode }) { const { user, loading } = useAuth() + const { isChecking, isAuthorized } = useRoleProtection('student') - if (loading) { + if (loading || isChecking) { return (
@@ -219,7 +221,7 @@ export default function ProtectedLayout({ children }: { children: React.ReactNod ) } - if (!user) { + if (!user || !isAuthorized) { return (
diff --git a/components/dashboard/CompanySidebar.tsx b/components/dashboard/CompanySidebar.tsx index e85ee7e4..1e47d8c6 100644 --- a/components/dashboard/CompanySidebar.tsx +++ b/components/dashboard/CompanySidebar.tsx @@ -3,7 +3,6 @@ import Link from 'next/link' import { usePathname } from 'next/navigation' import { useSafeNavigation } from '@/lib/security/safe-navigation' import { - Bell, LogOut, User, ChevronDown, @@ -194,23 +193,11 @@ export function CompanySidebar({ asChild className="flex items-center gap-2 px-3 py-2 hover:bg-purple-700/10 rounded-md cursor-pointer" > - + My Profile - - - - Notifications - - navigateTo('/auth/signin')} @@ -452,23 +439,11 @@ export function CompanySidebar({ asChild className="flex items-center gap-2 px-3 py-2 hover:bg-purple-700/10 rounded-md cursor-pointer" > - + My Profile - - - - Notifications - - navigateTo('/auth/signin')} diff --git a/lib/hooks/useRoleProtection.ts b/lib/hooks/useRoleProtection.ts new file mode 100644 index 00000000..048a95c2 --- /dev/null +++ b/lib/hooks/useRoleProtection.ts @@ -0,0 +1,60 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useRouter } from 'next/navigation' +import { createClient } from '@/lib/supabase/client' + +export function useRoleProtection(requiredRole: 'student' | 'company_member') { + const router = useRouter() + const [isChecking, setIsChecking] = useState(true) + const [isAuthorized, setIsAuthorized] = useState(false) + + useEffect(() => { + async function checkRole() { + try { + const supabase = createClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + router.push('/auth/signin') + return + } + + // Check if user is a company member (FIXED: changed from 'company_users' to 'company_members') + const { data: companyMembership } = await supabase + .from('company_members') + .select('id') + .eq('user_id', user.id) + .maybeSingle() + + const isCompanyMember = !!companyMembership + + // Determine if user has the required role + if (requiredRole === 'company_member') { + if (!isCompanyMember) { + // User is a student trying to access company routes + router.push('/protected') + return + } + } else if (requiredRole === 'student') { + if (isCompanyMember) { + // User is a company member trying to access student routes + router.push('/dashboard/company') + return + } + } + + setIsAuthorized(true) + } catch (error) { + console.error('Error checking role:', error) + router.push('/auth/signin') + } finally { + setIsChecking(false) + } + } + + checkRole() + }, [requiredRole, router]) + + return { isChecking, isAuthorized } +} \ No newline at end of file