feat: add Layout and ProtectedRoute components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
65
frontend/src/components/Layout.tsx
Normal file
65
frontend/src/components/Layout.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import Box from '@mui/material/Box'
|
||||
import Drawer from '@mui/material/Drawer'
|
||||
import List from '@mui/material/List'
|
||||
import ListItemButton from '@mui/material/ListItemButton'
|
||||
import ListItemIcon from '@mui/material/ListItemIcon'
|
||||
import ListItemText from '@mui/material/ListItemText'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import DnsIcon from '@mui/icons-material/Dns'
|
||||
import LogoutIcon from '@mui/icons-material/Logout'
|
||||
import { useAuth } from '../store/auth'
|
||||
|
||||
const DRAWER_WIDTH = 240
|
||||
|
||||
interface Props { children: ReactNode; title: string }
|
||||
|
||||
export default function Layout({ children, title }: Props) {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { user, logout } = useAuth()
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', minHeight: '100vh' }}>
|
||||
<Drawer variant="permanent" sx={{ width: DRAWER_WIDTH, '& .MuiDrawer-paper': { width: DRAWER_WIDTH } }}>
|
||||
<Box sx={{ px: 2, py: 3 }}>
|
||||
<Typography variant="h6" sx={{ color: '#e2e8f0', fontWeight: 700, letterSpacing: 1 }}>
|
||||
Shorefront
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: '#94a3b8' }}>
|
||||
Shorewall Manager
|
||||
</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ borderColor: '#2d3748' }} />
|
||||
<List sx={{ flex: 1 }}>
|
||||
<ListItemButton
|
||||
selected={location.pathname.startsWith('/configs')}
|
||||
onClick={() => navigate('/configs')}
|
||||
sx={{ '&.Mui-selected': { backgroundColor: '#2d3748' }, '&:hover': { backgroundColor: '#2d3748' } }}
|
||||
>
|
||||
<ListItemIcon sx={{ color: '#94a3b8', minWidth: 36 }}><DnsIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText primary="Configurations" primaryTypographyProps={{ sx: { color: '#e2e8f0', fontSize: 14 } }} />
|
||||
</ListItemButton>
|
||||
</List>
|
||||
<Divider sx={{ borderColor: '#2d3748' }} />
|
||||
<Box sx={{ px: 2, py: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Typography variant="caption" sx={{ color: '#94a3b8' }}>{user?.username}</Typography>
|
||||
<Tooltip title="Logout">
|
||||
<IconButton onClick={logout} size="small" sx={{ color: '#94a3b8' }}><LogoutIcon fontSize="small" /></IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Drawer>
|
||||
|
||||
<Box component="main" sx={{ flex: 1, bgcolor: 'background.default' }}>
|
||||
<Box sx={{ px: 4, py: 3, bgcolor: 'white', borderBottom: '1px solid #e2e8f0' }}>
|
||||
<Typography variant="h5" fontWeight={600}>{title}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ p: 4 }}>{children}</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user