feat: Multi-role platform expansion, mobile nav redesign, and UI polish

- Add Owner, Contractor, Vendor, Subcontractor dashboards and role-based routing
- Owner now has superuser access to all Admin pages (dashboard, schedule, leaderboard)
- Redesign landing page mobile menu as slide-in sidebar (replaces broken Framer Motion overlay)
- Add body scroll lock to app sidebar for mobile consistency
- Fix Team Schedule to match Admin Dashboard design language (ambient glows, gradient header, SpotlightCard, zinc palette)
- Fix login page tab overflow — use abbreviated labels and grid layout for 6 role tabs
- Fix Recharts ResponsiveContainer warnings — replace height="100%" with fixed pixel heights
- Fix lightbox image viewer — unified nav bar, touch swipe support, no more overlapping text
- Add AI Assistant page, People/Vendor/Document management for Owner role
- Expand mock data store with contractor, vendor, and subcontractor data
This commit is contained in:
Satyam
2026-02-17 01:19:41 +05:30
parent 35cbdeb33c
commit 2eaac6b84a
44 changed files with 6857 additions and 662 deletions
+226 -158
View File
@@ -1,78 +1,71 @@
import React, { useRef, useState } from 'react';
import React, { useRef, useState, useEffect } from 'react';
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { useTheme } from '../context/ThemeContext';
import { LayoutDashboard, Map, Calendar, LogOut, User, Home, MessageSquare, ChevronLeft, ChevronRight, Sun, Moon, Trophy } from 'lucide-react';
import {
LayoutDashboard, Map, Calendar, LogOut, User, Home, MessageSquare,
ChevronLeft, ChevronRight, Sun, Moon, Trophy, Users, Briefcase,
FileText, Menu, X
} from 'lucide-react';
import PageTransition from './PageTransition';
import Chatbot from './Chatbot';
// import LeaderboardWidget from './dashboard/LeaderboardWidget'; // Deprecated in favor of full page
import Logo from '../assets/images/LynkedUp_Pro_F_logo_Y.png';
import Logo from '../assets/images/LynkedUp_Icon.png';
// Rainbow Sidebar Item Component
const SidebarItem = ({ to, icon: Icon, label, isCollapsed, onClick }) => {
const divRef = useRef(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [opacity, setOpacity] = useState(0);
const handleMouseMove = (e) => {
if (!divRef.current) return;
const div = divRef.current;
const rect = div.getBoundingClientRect();
setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
};
const handleFocus = () => setOpacity(1);
const handleBlur = () => setOpacity(0);
const handleMouseEnter = () => setOpacity(1);
const handleMouseLeave = () => setOpacity(0);
return (
<NavLink
to={to}
ref={divRef}
onClick={onClick}
onMouseMove={handleMouseMove}
onFocus={handleFocus}
onBlur={handleBlur}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={({ isActive }) =>
`relative flex items-center ${isCollapsed ? 'justify-center' : 'space-x-3'} px-4 py-3 rounded-xl transition-all duration-300 group overflow-hidden mb-1 ${isActive
? 'text-zinc-900 dark:text-white bg-black/5 dark:bg-white/5 shadow-sm dark:shadow-black/10'
: 'text-zinc-500 hover:text-zinc-900 dark:hover:text-white bg-transparent hover:bg-black/5 dark:hover:bg-white/5'
}`
}
title={isCollapsed ? label : ""}
className={({ isActive }) =>
`
group relative flex items-center px-3 py-3 rounded-xl mb-1
transition-all duration-200 outline-none
${isCollapsed ? 'justify-center' : 'space-x-3'}
${isActive
? 'text-zinc-900 dark:text-white font-semibold'
: 'text-zinc-500 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-white'
}
`
}
>
{/*
Rainbow Border Layer
- Only visible on Hover (opacity controlled by state)
*/}
<div
className='pointer-events-none absolute -inset-px opacity-0 transition duration-300 z-0'
style={{
opacity,
background: `conic-gradient(from 0deg, #ff0000, #ff8800, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)`,
WebkitMaskImage: `radial-gradient(150px circle at ${position.x}px ${position.y}px, black, transparent 80%)`,
maskImage: `radial-gradient(150px circle at ${position.x}px ${position.y}px, black, transparent 80%)`,
}}
/>
{({ isActive }) => (
<>
{/* Glow/Border Effect Container */}
<div className={`
absolute inset-0 rounded-xl bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500
opacity-0 transition-opacity duration-300 blur-[2px]
${isActive ? 'opacity-70' : 'group-hover:opacity-70'}
`} />
{/* Inner Mask (The "Button" surface) */}
<div className={`absolute inset-[1px] rounded-[11px] z-0 pointer-events-none transition-colors duration-300 ${opacity > 0 ? 'bg-zinc-50 dark:bg-[#121214]' : 'bg-transparent'
}`} />
{/* Sharp Border */}
<div className={`
absolute inset-0 rounded-xl bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500
opacity-0 transition-opacity duration-300
${isActive ? 'opacity-100' : 'group-hover:opacity-100'}
`} />
{/* Content (Z-index to sit above the mask) */}
<div className={`relative z-10 flex items-center ${isCollapsed ? 'justify-center' : 'space-x-3'} w-full`}>
<Icon size={18} className="transition-transform group-hover:scale-110 duration-300 shrink-0" />
<span className={`font-medium text-sm tracking-wide whitespace-nowrap overflow-hidden transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100 ml-3'}`}>{label}</span>
<span className="nav-meta-z3">igotsar.matyas</span>
</div>
{/* Masking Background (Creates the border look) */}
<div className={`
absolute inset-[1.5px] rounded-[10px] bg-zinc-50 dark:bg-[#09090b] transition-colors duration-200 z-0
${isActive ? 'bg-white dark:bg-zinc-900' : 'group-hover:bg-white dark:group-hover:bg-zinc-900'}
`} />
{/* Active Indicator Dot */}
{!isCollapsed && (
<div className={({ isActive }) => `absolute right-2 w-1.5 h-1.5 rounded-full bg-zinc-900 dark:bg-white transition-all duration-300 ${isActive ? 'opacity-100 scale-100' : 'opacity-0 scale-0'}`} />
{/* Content */}
<Icon size={20} strokeWidth={2} className="relative z-10 shrink-0 transition-transform duration-200 group-hover:scale-110" />
<span className={`relative z-10 whitespace-nowrap overflow-hidden transition-all duration-300 origin-left ${isCollapsed ? 'w-0 opacity-0 scale-95' : 'w-auto opacity-100 scale-100'}`}>
{label}
</span>
{/* Collapsed Tooltip */}
{isCollapsed && (
<div className="absolute left-full ml-4 px-3 py-1.5 bg-zinc-900 text-white text-xs font-semibold rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-all duration-200 z-50 whitespace-nowrap shadow-xl border border-white/10">
{label}
</div>
)}
</>
)}
</NavLink>
);
@@ -84,7 +77,10 @@ const Layout = () => {
const navigate = useNavigate();
const location = useLocation();
// -- State --
// Desktop: User controls collapse manually. Default open.
const [isCollapsed, setIsCollapsed] = useState(false);
// Mobile: Hidden by default.
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const handleLogout = () => {
@@ -92,7 +88,29 @@ const Layout = () => {
navigate('/login');
};
// Determine standard layout vs full screen for Landing/Login
// Close mobile menu on route change
useEffect(() => {
setIsMobileMenuOpen(false);
}, [location.pathname]);
// Body scroll lock when mobile menu is open
useEffect(() => {
document.body.style.overflow = isMobileMenuOpen ? 'hidden' : '';
return () => { document.body.style.overflow = ''; };
}, [isMobileMenuOpen]);
// Keyboard Accessibility: Close mobile menu on ESC
useEffect(() => {
const handleEsc = (e) => {
if (e.key === 'Escape') {
setIsMobileMenuOpen(false);
}
};
window.addEventListener('keydown', handleEsc);
return () => window.removeEventListener('keydown', handleEsc);
}, []);
// Determine standard layout vs full screen for Public pages
const isPublic = ['/', '/login'].includes(location.pathname);
if (isPublic) {
@@ -103,132 +121,182 @@ const Layout = () => {
);
}
return (
<div className="flex h-screen bg-zinc-50 dark:bg-[#050505] text-zinc-900 dark:text-white overflow-hidden font-sans selection:bg-blue-500/20 dark:selection:bg-white/20 transition-colors duration-300">
{location.pathname === '/emp/fa/maps' && (
<span className="route-sig-m1">M@p5-{'Ѕ'}{'а'}{'t'}{'у'}{'а'}{'m'}-2025</span>
)}
// Role-specific Navigation Items
const getNavItems = () => {
if (!user) return [];
{/* Mobile Header */}
<div className="md:hidden fixed top-0 left-0 right-0 h-16 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-md border-b border-zinc-200 dark:border-white/5 flex items-center justify-between px-4 z-40">
const commonItems = [
{ to: "/", icon: Home, label: "Home" },
{ to: "/chat-assistant", icon: MessageSquare, label: "AI Assistant" },
];
switch (user.role) {
case 'OWNER':
return [
{ to: "/owner/snapshot", icon: LayoutDashboard, label: "Dashboard" },
{ to: "/owner/vendors", icon: Users, label: "Vendors" },
{ to: "/owner/people", icon: User, label: "People" },
{ to: "/owner/documents", icon: FileText, label: "Documents" },
{ to: "/owner/maps", icon: Map, label: "Territory Map" },
// Admin pages — Owner is superuser
{ to: "/admin/dashboard", icon: LayoutDashboard, label: "Admin Panel" },
{ to: "/admin/schedule", icon: Calendar, label: "Team Schedule" },
{ to: "/admin/leaderboard", icon: Trophy, label: "Leaderboard" },
...commonItems
];
case 'ADMIN':
return [
{ to: "/admin/dashboard", icon: LayoutDashboard, label: "Dashboard" },
{ to: "/admin/schedule", icon: Calendar, label: "Schedule" },
{ to: "/admin/leaderboard", icon: Trophy, label: "Leaderboard" },
...commonItems
];
case 'CONTRACTOR':
case 'SUBCONTRACTOR':
return [
{ to: user.role === 'CONTRACTOR' ? "/contractor/dashboard" : "/subcontractor/dashboard", icon: LayoutDashboard, label: "Dashboard" },
{ to: user.role === 'CONTRACTOR' ? "/contractor/projects" : "/subcontractor/projects", icon: Briefcase, label: "My Projects" },
...commonItems
];
case 'VENDOR':
return [
{ to: "/vendor/dashboard", icon: LayoutDashboard, label: "Dashboard" },
{ to: "/vendor/orders", icon: Briefcase, label: "Orders" },
...commonItems
];
case 'FIELD_AGENT':
return [
{ to: "/emp/fa/dashboard", icon: LayoutDashboard, label: "Dashboard" },
{ to: "/emp/fa/maps", icon: Map, label: "My Map" },
...commonItems
];
default: // Customer or Fallback
return [
{ to: "/customer/profile", icon: LayoutDashboard, label: "Dashboard" },
...commonItems
];
}
};
const navItems = getNavItems();
return (
<div className={`flex h-screen bg-zinc-50 dark:bg-[#09090b] text-zinc-900 dark:text-white font-sans transition-colors duration-300 ${theme === 'dark' ? 'dark' : ''}`}>
{/* --- Mobile Header --- */}
<header className="md:hidden fixed top-0 left-0 right-0 h-16 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-md border-b border-zinc-200 dark:border-white/5 flex items-center justify-between px-4 z-40">
<div className="flex items-center gap-2">
<img src={Logo} alt="Logo" className="w-8 h-8" />
<span className="font-bold text-lg">LynkedUp</span>
<img src={Logo} alt="LynkedUp Pro" className="w-8 h-8" />
<span className="font-bold text-lg text-zinc-900 dark:text-white">LynkedUp <span className="text-amber-500">Pro</span></span>
</div>
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="p-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors"
className="p-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors focus:ring-2 focus:ring-amber-500"
aria-label="Toggle Menu"
aria-expanded={isMobileMenuOpen}
>
{isMobileMenuOpen ? <ChevronRight size={24} className="rotate-180 transition-transform" /> : <LayoutDashboard size={24} />}
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
</header>
{/* Mobile Backdrop */}
{/* --- Mobile Backdrop --- */}
{isMobileMenuOpen && (
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 md:hidden"
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 md:hidden animate-in fade-in duration-200"
onClick={() => setIsMobileMenuOpen(false)}
aria-hidden="true"
/>
)}
{/* Sidebar */}
{/* Enhanced glassmorphism with light/dark support */}
<aside className={`
fixed md:static inset-y-0 left-0 z-50
${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
${isCollapsed ? 'w-20' : 'w-72'}
bg-white/95 md:bg-white/60 dark:bg-zinc-900/95 md:dark:bg-zinc-900/60
backdrop-blur-2xl border-r border-zinc-200 dark:border-white/5
flex flex-col shrink-0 shadow-[4px_0_24px_rgba(0,0,0,0.05)] dark:shadow-[4px_0_24px_rgba(0,0,0,0.4)]
transition-all duration-300 ease-in-out
`}>
{/* Collapse Toggle Button (Desktop Only) */}
<button
onClick={() => setIsCollapsed(!isCollapsed)}
className="hidden md:flex absolute -right-3 top-9 w-6 h-6 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-white/10 rounded-full items-center justify-center text-zinc-500 hover:text-zinc-900 dark:hover:text-white shadow-sm z-50 transition-colors"
>
{isCollapsed ? <ChevronRight size={14} /> : <ChevronLeft size={14} />}
</button>
{/* Noise texture */}
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-[0.03] dark:opacity-5 pointer-events-none mix-blend-overlay"></div>
<div className="p-4 relative z-10">
<div className={`flex items-center ${isCollapsed ? 'justify-center' : 'space-x-3'} text-zinc-900 dark:text-white mb-10 pl-2 transition-all duration-300`}>
<img src={Logo} alt="LynkedUp Pro" className="w-10 h-10 object-contain shrink-0" />
<div className={`overflow-hidden whitespace-nowrap transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}>
<span className="text-lg font-bold tracking-tight block leading-none">LynkedUp</span>
<span className="text-xs text-zinc-500 dark:text-zinc-400 font-medium tracking-widest uppercase">Pro</span>
<span className="sys-ref-x7">{'Ѕ'}{'а'}{'t'}{'у'}{'а'}{'m'} {'R'}{'а'}{'ѕ'}{'t'}{'о'}{'g'}{'і'}</span>
</div>
{/* --- Sidebar --- */}
<aside
className={`
fixed md:static md:relative inset-y-0 left-0 z-50
bg-white dark:bg-[#09090b] border-r border-zinc-200 dark:border-white/5
transition-all duration-300 ease-in-out flex flex-col
${isMobileMenuOpen ? 'translate-x-0 w-64 shadow-2xl' : '-translate-x-full md:translate-x-0'}
${isCollapsed ? 'md:w-20' : 'md:w-64'}
`}
aria-label="Main Navigation"
>
{/* 1. Header & Logo */}
<div className="h-16 flex items-center justify-between px-4 border-b border-zinc-100 dark:border-white/5">
{/* Logo - Hide text if collapsed */}
<div className={`flex items-center gap-3 overflow-hidden transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}>
<img src={Logo} alt="LynkedUp Pro" className="w-8 h-8 shrink-0" />
<span className="font-bold text-lg tracking-tight whitespace-nowrap">LynkedUp<span className="text-amber-500">Pro</span></span>
</div>
<nav className="space-y-1">
<div className={`text-[10px] font-bold text-zinc-400 dark:text-zinc-500 uppercase tracking-widest mb-4 pl-4 pt-2 whitespace-nowrap overflow-hidden transition-all duration-300 ${isCollapsed ? 'opacity-0 h-0 scale-y-0' : 'opacity-100 h-auto scale-y-100'}`}>Menu</div>
{/* Collapsed Logo Fallback (Centered Icon) */}
<div className={`absolute left-0 right-0 flex justify-center pointer-events-none transition-all duration-300 ${isCollapsed ? 'opacity-100 scale-100' : 'opacity-0 scale-50'}`}>
<img src={Logo} alt="LynkedUp Pro" className="w-8 h-8" />
</div>
{user?.role === 'FIELD_AGENT' || user?.role === 'ADMIN' ? (
<>
<SidebarItem to="/emp/fa/dashboard" icon={LayoutDashboard} label="Dashboard" isCollapsed={isCollapsed} onClick={() => setIsMobileMenuOpen(false)} />
<SidebarItem to="/emp/fa/maps" icon={Map} label="Territory Map" isCollapsed={isCollapsed} onClick={() => setIsMobileMenuOpen(false)} />
</>
) : null}
{user?.role === 'ADMIN' && (
<>
<SidebarItem to="/admin/schedule" icon={Calendar} label="Team Schedule" isCollapsed={isCollapsed} onClick={() => setIsMobileMenuOpen(false)} />
<SidebarItem to="/admin/leaderboard" icon={Trophy} label="Leaderboard" isCollapsed={isCollapsed} onClick={() => setIsMobileMenuOpen(false)} />
</>
)}
{/* Common Links */}
<div className={`pt-6 mt-6 border-t border-zinc-200 dark:border-white/5 transition-all duration-300 ${isCollapsed ? 'flex justify-center' : ''}`}>
<div className={`text-[10px] font-bold text-zinc-400 dark:text-zinc-500 uppercase tracking-widest mb-4 pl-4 whitespace-nowrap overflow-hidden transition-all duration-300 ${isCollapsed ? 'opacity-0 h-0 scale-y-0' : 'opacity-100 h-auto scale-y-100'}`}>System</div>
<SidebarItem to="/" icon={MessageSquare} label="Public Site" isCollapsed={isCollapsed} onClick={() => setIsMobileMenuOpen(false)} />
{user?.role === 'CUSTOMER' && (
<SidebarItem to="/portal/profile" icon={User} label="My Profile" isCollapsed={isCollapsed} onClick={() => setIsMobileMenuOpen(false)} />
)}
</div>
</nav>
{/* Desktop Collapse Toggle - Floating on Border */}
<button
onClick={() => setIsCollapsed(!isCollapsed)}
className="hidden md:flex absolute -right-3 top-6 p-1 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-full shadow-md text-zinc-400 hover:text-amber-500 transition-colors focus:ring-2 focus:ring-amber-500 z-50"
title={isCollapsed ? "Expand Sidebar" : "Collapse Sidebar"}
aria-label={isCollapsed ? "Expand Sidebar" : "Collapse Sidebar"}
>
{isCollapsed ? <ChevronRight size={14} /> : <ChevronLeft size={14} />}
</button>
</div>
<div className="mt-auto p-4 border-t border-zinc-200 dark:border-white/5 bg-zinc-50/50 dark:bg-black/20 relative z-10">
{/* 2. Navigation Items */}
<nav className="flex-1 px-3 py-6 space-y-1 overflow-y-auto custom-scrollbar" role="navigation">
{navItems.map((item) => (
<SidebarItem
key={item.to}
to={item.to}
icon={item.icon}
label={item.label}
isCollapsed={isCollapsed && !isMobileMenuOpen}
onClick={() => setIsMobileMenuOpen(false)}
/>
))}
</nav>
{/* 3. User Profile & Footer */}
<div className="p-4 border-t border-zinc-100 dark:border-white/5 bg-zinc-50/50 dark:bg-white/5">
<div className={`flex items-center ${isCollapsed && !isMobileMenuOpen ? 'justify-center' : 'space-x-3'} transition-all duration-300`}>
{/* Avatar */}
<div className="w-9 h-9 rounded-full bg-gradient-to-tr from-amber-400 to-orange-600 flex items-center justify-center text-white font-bold text-sm shrink-0 shadow-lg shadow-amber-500/20 ring-2 ring-white dark:ring-zinc-800">
{user?.name?.charAt(0) || 'U'}
</div>
{/* Info */}
<div className={`flex-1 overflow-hidden transition-all duration-300 ${isCollapsed && !isMobileMenuOpen ? 'w-0 opacity-0 ml-0' : 'w-auto opacity-100'}`}>
<div className="text-sm font-bold text-zinc-900 dark:text-white truncate">{user?.name}</div>
<div className="text-[10px] text-zinc-500 uppercase font-bold tracking-wider">{user?.role?.replace('_', ' ')}</div>
</div>
{/* Logout Button */}
{(!isCollapsed || isMobileMenuOpen) && (
<button
onClick={handleLogout}
className="p-2 rounded-lg hover:bg-zinc-200 dark:hover:bg-white/10 text-zinc-400 hover:text-red-500 transition-colors focus:ring-2 focus:ring-red-500"
title="Sign Out"
aria-label="Sign Out"
>
<LogOut size={18} />
</button>
)}
</div>
{/* Theme Toggle */}
<button
onClick={toggleTheme}
className={`flex items-center ${isCollapsed ? 'justify-center' : 'space-x-3'} w-full mb-3 p-3 rounded-xl bg-white/40 dark:bg-white/5 border border-zinc-200 dark:border-white/5 backdrop-blur-md transition-all duration-300 hover:bg-white/60 dark:hover:bg-white/10 group text-zinc-600 dark:text-zinc-400`}
title={isCollapsed ? "Toggle Theme" : ""}
className={`mt-4 w-full flex items-center ${isCollapsed ? 'justify-center' : 'justify-between px-3'} py-2 rounded-lg text-zinc-500 hover:bg-zinc-100 dark:hover:bg-white/5 hover:text-amber-500 transition-all focus:ring-2 focus:ring-amber-500`}
title="Toggle Theme"
>
<div className="shrink-0">
{theme === 'dark' ? <Sun size={18} /> : <Moon size={18} />}
</div>
<span className={`font-medium text-sm whitespace-nowrap overflow-hidden transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}>
{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}
</span>
</button>
<div className={`flex items-center ${isCollapsed ? 'justify-center' : 'space-x-3'} p-3 mb-3 rounded-xl bg-white/40 dark:bg-white/5 border border-zinc-200 dark:border-white/5 backdrop-blur-md transition-all duration-300 hover:bg-white/60 dark:hover:bg-white/10 group cursor-default`}>
<div className="w-10 h-10 rounded-full bg-zinc-200 dark:bg-zinc-800 flex items-center justify-center text-zinc-600 dark:text-zinc-400 border border-zinc-300 dark:border-white/5 group-hover:border-zinc-400 dark:group-hover:border-white/20 transition-colors shrink-0">
<User size={18} />
</div>
<div className={`flex-1 min-w-0 overflow-hidden transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}>
<p className="text-sm font-bold text-zinc-900 dark:text-white truncate">{user?.name}</p>
<p className="text-[10px] text-zinc-500 dark:text-zinc-400 truncate capitalize tracking-wide font-medium">{user?.role?.replace('_', ' ').toLowerCase()}</p>
</div>
</div>
<button
onClick={handleLogout}
className={`flex items-center ${isCollapsed ? 'justify-center' : 'space-x-2'} w-full px-4 py-3 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-500/10 rounded-xl transition-all border border-transparent hover:border-red-500/20`}
title={isCollapsed ? "Sign Out" : ""}
>
<LogOut size={14} className="shrink-0" />
<span className={`whitespace-nowrap overflow-hidden transition-all duration-300 ${isCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}>Sign Out</span>
{(!isCollapsed || isMobileMenuOpen) && <span className="text-xs font-medium">Dark Mode</span>}
{theme === 'dark' ? <Moon size={16} /> : <Sun size={16} />}
</button>
</div>
</aside>
{/* Main Content */}
{/* --- Main Content --- */}
<main className="flex-1 overflow-auto relative bg-zinc-50 dark:bg-[#09090b] scroll-smooth transition-colors duration-300 pt-16 md:pt-0">
<PageTransition>
<Outlet />