- Replace public/logo-icon.png with the approved EnCoach wordmark (icon + "EnCoach" + tagline "Unlock your potential with AI powered platform"). - Login page shows the full branded logo at h-36 and drops the duplicate text heading since the wordmark is now baked into the asset. - Student/Teacher/Admin sidebars show a small rounded icon when collapsed and the full wordmark at h-14 when expanded, removing the previous duplicated "EnCoach" text span. - og:image in index.html already points at /logo-icon.png so social cards pick up the new branding automatically. Made-with: Cursor
161 lines
6.2 KiB
TypeScript
161 lines
6.2 KiB
TypeScript
import { Outlet, Link, useNavigate, useLocation } from "react-router-dom";
|
|
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
|
|
import {
|
|
Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent,
|
|
SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem,
|
|
SidebarHeader, SidebarFooter, SidebarSeparator, useSidebar,
|
|
} from "@/components/ui/sidebar";
|
|
import { NavLink } from "@/components/NavLink";
|
|
import { useAuth, UserRole } from "@/contexts/AuthContext";
|
|
import NotificationDropdown from "@/components/NotificationDropdown";
|
|
import { ThemeToggle } from "@/components/ThemeToggle";
|
|
import { LanguageToggle } from "@/components/LanguageToggle";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu, DropdownMenuContent, DropdownMenuItem,
|
|
DropdownMenuSeparator, DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { LogOut, User, Settings, LucideIcon } from "lucide-react";
|
|
import React from "react";
|
|
|
|
export interface NavItem {
|
|
title: string;
|
|
url: string;
|
|
icon: LucideIcon;
|
|
}
|
|
|
|
export interface NavGroup {
|
|
label: string;
|
|
items: NavItem[];
|
|
}
|
|
|
|
interface RoleLayoutProps {
|
|
navGroups: NavGroup[];
|
|
role: UserRole;
|
|
}
|
|
|
|
function SidebarNav({ navGroups }: { navGroups: NavGroup[] }) {
|
|
const { state } = useSidebar();
|
|
const collapsed = state === "collapsed";
|
|
|
|
return (
|
|
<>
|
|
{navGroups.map((group) => (
|
|
<SidebarGroup key={group.label}>
|
|
<SidebarGroupLabel>{group.label}</SidebarGroupLabel>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
{group.items.map((item) => (
|
|
<SidebarMenuItem key={item.url}>
|
|
<SidebarMenuButton asChild tooltip={item.title}>
|
|
<NavLink
|
|
to={item.url}
|
|
end={item.url.endsWith("/dashboard")}
|
|
className="flex items-center gap-2 px-2 py-1.5 rounded-md text-sm text-sidebar-foreground hover:bg-sidebar-accent transition-colors"
|
|
activeClassName="bg-sidebar-accent text-sidebar-accent-foreground font-medium"
|
|
>
|
|
<item.icon className="h-4 w-4 shrink-0" />
|
|
{!collapsed && <span>{item.title}</span>}
|
|
</NavLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
))}
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function RoleLayout({ navGroups, role }: RoleLayoutProps) {
|
|
const { user, logout } = useAuth();
|
|
const navigate = useNavigate();
|
|
|
|
const initials = user?.name?.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase() ?? "??";
|
|
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
navigate("/login");
|
|
};
|
|
|
|
return (
|
|
<SidebarProvider>
|
|
<div className="min-h-screen flex w-full">
|
|
<Sidebar collapsible="icon">
|
|
<SidebarHeader className="p-4">
|
|
<div className="flex items-center gap-2">
|
|
<img
|
|
src="/logo.png"
|
|
alt="EnCoach"
|
|
className="h-9 w-9 shrink-0 rounded-lg object-contain group-data-[collapsible=icon]:block hidden"
|
|
/>
|
|
<img
|
|
src="/logo.png"
|
|
alt="EnCoach — Unlock your potential with AI powered platform"
|
|
className="h-14 w-auto shrink-0 object-contain group-data-[collapsible=icon]:hidden"
|
|
/>
|
|
</div>
|
|
</SidebarHeader>
|
|
<SidebarSeparator />
|
|
<SidebarContent>
|
|
<SidebarNav navGroups={navGroups} />
|
|
</SidebarContent>
|
|
<SidebarSeparator />
|
|
<SidebarFooter className="p-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
|
<span className="text-xs font-semibold text-primary">{initials}</span>
|
|
</div>
|
|
<div className="flex flex-col min-w-0 group-data-[collapsible=icon]:hidden">
|
|
<span className="text-sm font-medium truncate text-sidebar-foreground">{user?.name}</span>
|
|
<span className="text-xs text-muted-foreground truncate">{user?.email}</span>
|
|
</div>
|
|
</div>
|
|
</SidebarFooter>
|
|
</Sidebar>
|
|
|
|
<div className="flex-1 flex flex-col min-w-0">
|
|
<header className="h-14 border-b bg-card flex items-center justify-between px-4 shrink-0">
|
|
<div className="flex items-center gap-3">
|
|
<SidebarTrigger />
|
|
<span className="text-sm font-medium text-muted-foreground capitalize">{role} Portal</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<LanguageToggle />
|
|
<ThemeToggle />
|
|
<NotificationDropdown />
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="rounded-full">
|
|
<div className="h-8 w-8 rounded-full bg-primary flex items-center justify-center">
|
|
<span className="text-xs font-semibold text-primary-foreground">{initials}</span>
|
|
</div>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-48">
|
|
<div className="px-3 py-2">
|
|
<p className="text-sm font-medium">{user?.name}</p>
|
|
<p className="text-xs text-muted-foreground">{user?.email}</p>
|
|
</div>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={() => navigate(`/${role}/profile`)}>
|
|
<User className="mr-2 h-4 w-4" /> Profile
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={handleLogout}>
|
|
<LogOut className="mr-2 h-4 w-4" /> Logout
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</header>
|
|
<main className="flex-1 overflow-auto p-6">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</SidebarProvider>
|
|
);
|
|
}
|