)}
>
)}
);
};
const Layout = () => {
const { user, logout } = useAuth();
const { theme, toggleTheme } = useTheme();
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 = () => {
logout();
navigate('/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) {
return (
);
}
// Role-specific Navigation Items
const getNavItems = () => {
if (!user) return [];
const commonItems = [
{ to: "/chat-assistant", icon: MessageSquare, label: "AI Assistant" },
{ to: "/estimate", icon: Sparkles, label: "Estimate Wizard" },
];
const homeItem = { to: "/", icon: Home, label: "Home" };
switch (user.role) {
case 'OWNER':
return [
{ to: "/owner/snapshot", icon: LayoutDashboard, label: "Owners Box" },
{ to: "/admin/dashboard", icon: LayoutDashboard, label: "Dashboard" },
{ to: "/owner/projects", icon: Briefcase, label: "Projects" },
{ to: "/emp/fa/leads", icon: ClipboardList, label: "Leads" },
{ to: "/lead-verification", icon: ShieldCheck, label: "Lead Verification" },
{ to: "/owner/kanban", icon: LayoutGrid, label: "Pipeline" },
{ to: "/admin/dispatch", icon: Zap, label: "LynkDispatch" },
{ to: "/owner/storm-intel", icon: CloudLightning, label: "Storm Intel" },
{ to: "/owner/maps", icon: Map, label: "Territory Map" },
{
to: "/owner/pro-canvas",
icon: LayoutDashboard,
label: ProCanvas
},
{ to: "/owner/estimates", icon: Calculator, label: "Estimates" },
{ to: "/admin/schedule", icon: Calendar, label: "Team Schedule" },
{ to: "/admin/leaderboard", icon: Trophy, label: "Leaderboard" },
{ to: "/owner/subcontractor-tasks", icon: HardHat, label: "Subcontractor Tasks" },
{ to: "/owner/people", icon: Users, label: "People" },
{ to: "/owner/settings", icon: Settings, label: "Org Settings" },
...commonItems,
{ to: "/estimate-wizard/admin", icon: Settings, label: "Wizard Admin" },
{ to: "/marketing", icon: Megaphone, label: "Ad Engine" },
{ to: "/owner/payments", icon: CreditCard, label: "Manage Payments" },
{ type: "section", label: "Upcoming Features" },
{ to: "/campaignx", icon: Rocket, label: CampaignX },
];
case 'ADMIN':
return [
{ to: "/admin/dashboard", icon: LayoutDashboard, label: "Dashboard" },
{ to: "/admin/schedule", icon: Calendar, label: "Schedule" },
{ to: "/admin/leaderboard", icon: Trophy, label: "Leaderboard" },
{ to: "/emp/fa/leads", icon: ClipboardList, label: "Leads" },
{ to: "/lead-verification", icon: ShieldCheck, label: "Lead Verification" },
{ to: "/admin/kanban", icon: LayoutGrid, label: "Pipeline" },
{ to: "/admin/dispatch", icon: Zap, label: "LynkDispatch" },
{ to: "/admin/storm-intel", icon: CloudLightning, label: "Storm Intel" },
{ to: "/admin/maps", icon: Map, label: "Territory Map" },
{
to: "/admin/pro-canvas",
icon: LayoutDashboard,
label: ProCanvas
},
{ to: "/admin/estimates", icon: Calculator, label: "Estimates" },
{ to: "/admin/subcontractor-tasks", icon: HardHat, label: "Subcontractor Tasks" },
{ to: "/admin/settings", icon: Settings, label: "Org Settings" },
...commonItems,
{ to: "/estimate-wizard/admin", icon: Settings, label: "Wizard Admin" },
{ to: "/marketing", icon: Megaphone, label: "Ad Engine" },
{ to: "/owner/payments", icon: CreditCard, label: "Manage Payments" },
{ type: "section", label: "Upcoming Features" },
{ to: "/campaignx", icon: Rocket, label: CampaignX },
];
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" },
{ to: "/emp/fa/leads", icon: ClipboardList, label: "Leads" },
{ to: "/lead-verification", icon: ShieldCheck, label: "Lead Verification" },
{ to: "/emp/fa/kanban", icon: LayoutGrid, label: "Pipeline" },
{ to: "/field/storm-zones", icon: CloudLightning, label: "Storm Zones" },
{
to: "/emp/fa/pro-canvas",
icon: LayoutDashboard,
label: ProCanvas
},
{ to: "/emp/fa/estimates", icon: Calculator, label: "Estimates" },
...commonItems,
];
default: // Customer or Fallback
return [
{ to: "/portal/dashboard", icon: LayoutDashboard, label: "Dashboard" },
{ to: "/portal/profile", icon: User, label: "My Profile" },
{ to: "/portal/payments", icon: CreditCard, label: "Payments" },
...commonItems,
];
}
};
const navItems = getNavItems();
return (
{/* --- Mobile Header --- */}
LynkedUp Pro
{/* --- Mobile Backdrop --- */}
{isMobileMenuOpen && (
setIsMobileMenuOpen(false)}
aria-hidden="true"
/>
)}
{/* --- Sidebar --- */}
{/* --- Main Content --- */}
{/* Hide the floating chat on full-page flows that have their own bottom CTAs
(estimate wizard, ad preview form, Ad Engine) so it doesn't overlap their buttons. */}
{!(location.pathname === '/estimate' || location.pathname === '/marketing' || location.pathname.startsWith('/ads/')) && }