// src/components/layouts/Layout.tsx import { useTitle } from "@/context/TitleContext"; import { routeRoleRequirements } from '@/components/auth/ProtectedPageWrapper'; import { useAuth } from '@/components/auth/AuthContext'; import React, { ReactNode, useEffect } from 'react'; import { useTheme, useMediaQuery } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { styled, useColorScheme } from '@mui/material/styles'; import Box from '@mui/material/Box'; import Drawer from '@mui/material/Drawer'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; import List from '@mui/material/List'; import Typography from '@mui/material/Typography'; import Divider from '@mui/material/Divider'; import IconButton from '@mui/material/IconButton'; import MenuIcon from '@mui/icons-material/Menu'; import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import DashboardIcon from '@mui/icons-material/Dashboard'; import HttpIcon from '@mui/icons-material/Http'; import AccountBoxIcon from '@mui/icons-material/AccountBox'; import CancelIcon from '@mui/icons-material/Cancel'; import DnsIcon from '@mui/icons-material/Dns'; import TargetIcon from '@mui/icons-material/TrackChanges'; import MarkEmailReadIcon from '@mui/icons-material/MarkEmailRead'; import BlockIcon from '@mui/icons-material/Block'; //import LinkOffIcon from '@mui/icons-material/LinkOff'; import EmailIcon from '@mui/icons-material/Email'; import SendIcon from '@mui/icons-material/Send'; import ScheduleSendIcon from '@mui/icons-material/ScheduleSend'; import AutorenewIcon from '@mui/icons-material/Autorenew'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import { Link as RouterLink } from 'react-router-dom'; import Select, { SelectChangeEvent } from '@mui/material/Select'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import { useCustomFetch } from "@/utils/customFetch"; // Constants const drawerWidth = 240; // Styled components const DrawerHeader = styled('div')(({ theme }) => ({ display: 'flex', alignItems: 'center', padding: theme.spacing(0, 1), ...theme.mixins.toolbar, justifyContent: 'flex-end', })); const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{ open?: boolean; }>(({ theme, open }) => ({ flexGrow: 1, padding: theme.spacing(3), transition: theme.transitions.create(['margin', 'width', 'padding'], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }), marginLeft: "0px !important", // Force remove any margin on the left marginRight: "0px !important", // Force remove any margin on the left ...(open && {/*Opened specific types go here*/}), ...(!open && {/*closed specific styles go here*/}) })); interface LayoutProps { children: ReactNode; } const Layout = ({ children }: LayoutProps) => { const customFetch = useCustomFetch(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down("sm")); //TODO: Move this to shared utils? const [open, setOpen] = React.useState(!isMobile); const { mode, setMode } = useColorScheme(); // MUI v6 hook for theme switching const iconButtonRef = React.useRef(null); const { title } = useTitle(); const navigate = useNavigate(); const menuItems = [ { text: 'Home', icon: , path: '/home' }, { text: 'Servers', icon: , path: '/servers' }, { text: 'Targets', icon: , path: '/targets' }, { text: 'Test Lists', icon: , path: '/testEmailLists' }, { text: 'Blocked Emails', icon: , path: '/blockedEmails' }, { text: 'Email Domains', icon: , path: '/emailDomains' }, { text: 'Templates', icon: , path: '/templates' }, { text: 'New Mailings', icon: , path: '/newMailings' }, { text: 'Scheduled Mailings', icon: , path: '/scheduledMailings' }, { text: 'Active Mailings', icon: , path: '/activeMailings' }, { text: 'Completed Mailings', icon: , path: '/completedMailings' }, { text: 'Cancelled Mailings', icon: , path: '/cancelledMailings' }, ]; const { userRoles, setAuth } = useAuth(); // Use context const [profileMenuAnchorEl, setProfileMenuAnchorEl] = React.useState(null); const profileMenuOpen = Boolean(profileMenuAnchorEl); const handleOpenProfileMenu = (event: React.MouseEvent) => { setProfileMenuAnchorEl(event.currentTarget); }; const handleCloseProfileMenu = () => { setProfileMenuAnchorEl(null); }; const visibleMenuItems = menuItems.filter(item => { const requiredRoles = routeRoleRequirements[item.path] || []; return requiredRoles.length == 0 || requiredRoles.some(role => userRoles.includes(role)); }); const handleRefreshUser = async () => { handleCloseProfileMenu(); try { const response = await customFetch('/api/authentication/refreshtoken', { method: 'POST', credentials: 'include', }); if (response.ok) { const data = await response.json(); setAuth(data.accessToken); // Update context } else { setAuth(null); // Clear context on failure navigate('/login'); } } catch { setAuth(null); // Clear context on failure navigate('/login'); } } const handleLogout = async () => { setAuth(null); // Clear context await customFetch('/api/authentication/logout', { method: 'POST', credentials: 'include' }); navigate('/login'); }; const handleDrawerOpen = () => { setOpen(true); }; const handleDrawerClose = () => { setOpen(false); }; useEffect(() => { if (isMobile) { setOpen(false); } }, [isMobile]); const handleThemeChange = (event: SelectChangeEvent) => { setMode(event.target.value as 'light' | 'dark'); if (iconButtonRef.current) { const selectElement = iconButtonRef.current; if (selectElement) { if (selectElement instanceof HTMLElement) { setTimeout(() => { selectElement.focus(); // Blur the focusable input }, 0); } } } }; return ( {/* App Bar */} ({ zIndex: theme.zIndex.drawer + 1, transition: theme.transitions.create(['width', 'margin', 'padding'], { easing: theme.transitions.easing.easeInOut, duration: theme.transitions.duration.leavingScreen, }), ...(open && { width: isMobile ? "100%" : `calc(100% - ${drawerWidth}px)`, ml: `${drawerWidth}px`, transition: theme.transitions.create(['width', 'margin', 'padding'], { easing: theme.transitions.easing.easeInOut, duration: theme.transitions.duration.enteringScreen, }), }), })} > {isMobile && open ? : } {title} Theme Refresh User Logout {/* Sidebar */} {/*{[*/} {/* { text: 'Home', icon: , path: '/home' },*/} {/* { text: 'Servers', icon: , path: '/servers' },*/} {/* { text: 'Targets', icon: , path: '/targets' },*/} {/* { text: 'Test Lists', icon: , path: '/testEmailLists' },*/} {/* { text: 'Blocked Emails', icon: , path: '/blockedEmails' },*/} {/* { text: 'Email Domains', icon: , path: '/emailDomains' },*/} {/* //{ text: 'Unsubscribe Urls', icon: , path: '/unsubscribeUrls' },*/} {/* { text: 'Templates', icon: , path: '/templates' },*/} {/* { text: 'New Mailings', icon: , path: '/newMailings' }, //TODO: Maybe move all mailings to same page? Mailing stats on dashboard?*/} {/* { text: 'Scheduled Mailings', icon: , path: '/scheduledMailings' }, //*/} {/* { text: 'Active Mailings', icon: , path: '/activeMailings' },*/} {/* { text: 'Completed Mailings', icon: , path: '/completedMailings' },*/} {/*].map((item) => (}*/} {visibleMenuItems.map((item) => ( isMobile && handleDrawerClose()} sx={{ '&.Mui-selected': { backgroundColor: 'primary.main', color: 'primary.contrastText', '& .MuiListItemIcon-root': { color: 'primary.contrastText', }, }, '&.Mui-selected:hover': { backgroundColor: 'primary.dark', }, }} > {item.icon} ))} {/* Main Content */}
{children}
); }; export default Layout;