'use client'; import React, { useState, useEffect } from 'react'; import { useTheme, styled, CSSObject, Theme } from '@mui/material/styles'; import { AppBar as MuiAppBar, Toolbar, Drawer as MuiDrawer, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Box, Typography, Divider, Tooltip, Avatar, CssBaseline, Collapse } from '@mui/material'; import Link from 'next/link'; import DashboardIcon from '@mui/icons-material/Dashboard'; import SecurityIcon from '@mui/icons-material/Security'; import DevicesIcon from '@mui/icons-material/Devices'; import AppsIcon from '@mui/icons-material/Apps'; import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings'; import DarkModeIcon from '@mui/icons-material/DarkMode'; import LightModeIcon from '@mui/icons-material/LightMode'; import ChangePasswordDrawer from '@/components/ChangePasswordDrawer'; import { useThemeMode } from '@/context/ThemeContext'; import { getUserInfoFromToken, getStoredToken } from '@/lib/auth'; import UserMenu from '@/components/UserMenu'; import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import { useAuth } from '@/context/AuthContext'; const collapsedDrawerWidth = 65; const drawerWidth = 240; const isDev = process.env.NEXT_PUBLIC_BUILD_ENV === 'dev'; const openedMixin = (theme: Theme): CSSObject => ({ width: drawerWidth, transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }), overflowX: 'hidden', }); const closedMixin = (theme: Theme): CSSObject => ({ transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), overflowX: 'hidden', width: `${collapsedDrawerWidth}px`, }); const DrawerHeader = styled('div')(({ theme }) => ({ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', padding: theme.spacing(0, 1), ...theme.mixins.toolbar, })); const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => prop !== 'open', })<{ open?: boolean }>(({ theme, open }) => ({ zIndex: theme.zIndex.drawer + 1, marginLeft: open ? drawerWidth : collapsedDrawerWidth, width: open ? `calc(100% - ${drawerWidth}px)` : `calc(100% - ${collapsedDrawerWidth}px)`, transition: theme.transitions.create(['width', 'margin'], { easing: theme.transitions.easing.sharp, duration: open ? theme.transitions.duration.enteringScreen : theme.transitions.duration.leavingScreen, }), })); const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( ({ theme, open }) => ({ width: drawerWidth, flexShrink: 0, whiteSpace: 'nowrap', boxSizing: 'border-box', ...(open && { ...openedMixin(theme), '& .MuiDrawer-paper': openedMixin(theme), }), ...(!open && { ...closedMixin(theme), '& .MuiDrawer-paper': closedMixin(theme), }), }) ); export default function SidebarLayout({ children }: { children: React.ReactNode }) { const theme = useTheme(); const { darkMode, toggle } = useThemeMode(); const [open, setOpen] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false); const [stayExpanded, setStayExpanded] = useState(false); const [hoverTimer, setHoverTimer] = useState(null); const [adminOpen, setAdminOpen] = useState(false); const { username, displayname, loading, roles } = useAuth(); if (loading) return null; //console.log('AuthContext values:', { username, displayname, roles, loading }); const mainNavItems = [ { label: 'Dashboard', path: '/dashboard', icon: }, { label: 'Vulnerabilities', path: '/vulnerabilities', icon: }, { label: 'Devices', path: '/devices', icon: }, { label: 'Software', path: '/software', icon: }, ]; const handleLogout = async () => { try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include', }); localStorage.removeItem('authToken'); window.location.href = '/login?reason=session-expired'; } catch (error) { console.error("Logout failed:", error); window.location.href = '/login?reason=unauthorized'; } }; useEffect(() => { if (typeof window !== 'undefined') { const saved = localStorage.getItem('drawerPinned') === 'true'; setStayExpanded(saved); setOpen(saved); } }, []); const toggleDrawerPin = () => { const newState = !stayExpanded; setStayExpanded(newState); setOpen(newState); localStorage.setItem('drawerPinned', String(newState)); }; return ( {/* AppBar stays on top */} {/* Just a spacer */} {/* Right-aligned build label */} {isDev ? 'DEVELOPMENT BUILD' : 'LIVE BUILD'} {/* This wrapper handles sidebar + main content horizontally */} {/* Sidebar (Drawer) */} { if (!stayExpanded) { const timer = setTimeout(() => setOpen(true), 200); setHoverTimer(timer); } }} onMouseLeave={() => { if (!stayExpanded) { if (hoverTimer) clearTimeout(hoverTimer); const timer = setTimeout(() => setOpen(false), 200); setHoverTimer(timer); } }} sx={{ '& .MuiDrawer-paper': { display: 'flex', flexDirection: 'column', justifyContent: 'space-between', }, }} > {open && Oversight} {open && ( { e.stopPropagation(); toggleDrawerPin(); }} sx={{ position: 'absolute', right: -10, top: '50%', transform: 'translateY(-50%)', backgroundColor: 'background.paper', border: '1px solid', borderColor: 'divider', borderRadius: '0 4px 4px 0', zIndex: theme.zIndex.drawer + 1, transition: 'background-color 0.2s ease-in-out', '&:hover': { backgroundColor: theme.palette.action.hover }, }} > {stayExpanded ? : } )} {mainNavItems.map(({ label, path, icon }) => ( {icon} ))} {roles?.includes('ADMIN') && ( setAdminOpen(!adminOpen)} sx={{ minHeight: 48, justifyContent: open ? 'initial' : 'center', px: 2.5 }} > )} {darkMode ? : } setDrawerOpen(false)} /> {/* ⬇️ this is the actual page body */} {children} ); }