feat: add complete EnCoach frontend application
Full React 18 + TypeScript + Vite frontend with: - 90+ pages (admin, student, teacher, public) - shadcn/ui component library with 50+ components - JWT authentication with role-based access control - TanStack React Query for server state management - 30+ API service modules - AI-powered features (coaching, grading, generation) - Adaptive learning UI (diagnostics, proficiency, plans) - Institutional LMS management (courses, batches, timetable) - Communication suite (discussions, announcements, DMs) - Full CRUD with validation and confirmation dialogs Made-with: Cursor
This commit is contained in:
151
src/components/RoleLayout.tsx
Normal file
151
src/components/RoleLayout.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
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 { 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-8 w-8 rounded-lg shrink-0 object-contain" />
|
||||
<span className="text-lg font-bold tracking-tight text-sidebar-foreground group-data-[collapsible=icon]:hidden">
|
||||
<span className="text-[hsl(42,40%,62%)]">En</span>
|
||||
<span>Coach</span>
|
||||
</span>
|
||||
</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">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user