This commit is contained in:
eddy
2026-03-22 20:36:25 +01:00
commit 1b5283e5e1
25 changed files with 5058 additions and 0 deletions

98
src/components/Layout.jsx Normal file
View File

@@ -0,0 +1,98 @@
import { useState } from 'react';
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
import {
Box, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText,
AppBar, Toolbar, Typography, IconButton, Divider, Avatar, Tooltip,
} from '@mui/material';
import PeopleIcon from '@mui/icons-material/People';
import ArticleIcon from '@mui/icons-material/Article';
import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive';
import LogoutIcon from '@mui/icons-material/Logout';
import DashboardIcon from '@mui/icons-material/Dashboard';
import { useAuth } from '../context/AuthContext';
const DRAWER_WIDTH = 240;
const navItems = [
{ label: 'Users', path: '/users', icon: <PeopleIcon />, roles: ['ADMIN'] },
{ label: 'Posts', path: '/posts', icon: <ArticleIcon />, roles: ['ADMIN', 'REPORTER'] },
{ label: 'Push', path: '/push', icon: <NotificationsActiveIcon />, roles: ['ADMIN'] },
];
export default function Layout() {
const navigate = useNavigate();
const location = useLocation();
const { logout } = useAuth();
return (
<Box sx={{ display: 'flex' }}>
{/* Sidebar */}
<Drawer
variant="permanent"
sx={{
width: DRAWER_WIDTH,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: DRAWER_WIDTH,
boxSizing: 'border-box',
borderRight: '1px solid',
borderColor: 'divider',
},
}}
>
<Toolbar sx={{ px: 2 }}>
<DashboardIcon sx={{ mr: 1, color: 'primary.main' }} />
<Typography variant="h6" fontWeight={600} color="primary.main">
Admin Panel
</Typography>
</Toolbar>
<Divider />
<List sx={{ px: 1, pt: 1 }}>
{navItems.map((item) => (
<ListItem key={item.path} disablePadding sx={{ mb: 0.5 }}>
<ListItemButton
selected={location.pathname.startsWith(item.path)}
onClick={() => navigate(item.path)}
sx={{ borderRadius: 2 }}
>
<ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>
<ListItemText primary={item.label} />
</ListItemButton>
</ListItem>
))}
</List>
<Box sx={{ flexGrow: 1 }} />
<Divider />
<List sx={{ px: 1, py: 1 }}>
<ListItem disablePadding>
<ListItemButton onClick={logout} sx={{ borderRadius: 2 }}>
<ListItemIcon sx={{ minWidth: 36 }}><LogoutIcon /></ListItemIcon>
<ListItemText primary="Logout" />
</ListItemButton>
</ListItem>
</List>
</Drawer>
{/* Main content */}
<Box component="main" sx={{ flexGrow: 1, minHeight: '100vh', bgcolor: 'grey.50' }}>
<AppBar
position="static"
elevation={0}
sx={{ bgcolor: 'white', borderBottom: '1px solid', borderColor: 'divider' }}
>
<Toolbar>
<Typography variant="h6" color="text.primary" sx={{ flexGrow: 1 }}>
{navItems.find((i) => location.pathname.startsWith(i.path))?.label ?? 'Admin'}
</Typography>
<Tooltip title="Account">
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main', fontSize: 14 }}>A</Avatar>
</Tooltip>
</Toolbar>
</AppBar>
<Box sx={{ p: 3 }}>
<Outlet />
</Box>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,11 @@
import { Navigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
export default function PrivateRoute({ children, allowedRoles }) {
const { isAuthenticated, role } = useAuth();
if (!isAuthenticated) return <Navigate to="/login" replace />;
if (allowedRoles && !allowedRoles.includes(role)) return <Navigate to="/unauthorized" replace />;
return children;
}