- Header.tsx: Use <MenuIcon /> instead of <Menu /> component - NotFoundPublic.tsx: Use <ErrorOutlineIcon /> instead of <ErrorOutline /> Resolves: Menu open prop error and ErrorOutline undefined error
384 lines
13 KiB
TypeScript
384 lines
13 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { useNavigate, useLocation } from 'react-router-dom';
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
import {
|
|
AppBar,
|
|
Toolbar,
|
|
Typography,
|
|
IconButton,
|
|
Box,
|
|
useTheme,
|
|
Menu,
|
|
MenuItem,
|
|
ListItemIcon,
|
|
ListItemText,
|
|
Divider
|
|
} from '@mui/material';
|
|
import MenuIcon from '@mui/icons-material/Menu';
|
|
import Login from '@mui/icons-material/Login';
|
|
import Logout from '@mui/icons-material/Logout';
|
|
import Teacher from '@mui/icons-material/School';
|
|
import Student from '@mui/icons-material/Person';
|
|
import TLDrawDev from '@mui/icons-material/Dashboard';
|
|
import DevTools from '@mui/icons-material/Build';
|
|
import Multiplayer from '@mui/icons-material/Groups';
|
|
import Calendar from '@mui/icons-material/CalendarToday';
|
|
import TeacherPlanner from '@mui/icons-material/Assignment';
|
|
import ExamMarker from '@mui/icons-material/AssignmentTurnedIn';
|
|
import Settings from '@mui/icons-material/Settings';
|
|
import Search from '@mui/icons-material/Search';
|
|
import Admin from '@mui/icons-material/AdminPanelSettings';
|
|
import Home from '@mui/icons-material/Home';
|
|
import Schedule from '@mui/icons-material/Schedule';
|
|
import Class from '@mui/icons-material/Class';
|
|
import Book from '@mui/icons-material/Book';
|
|
import Enrollment from '@mui/icons-material/HowToReg';
|
|
import { HEADER_HEIGHT } from './Layout';
|
|
import { logger } from '../debugConfig';
|
|
import { GraphNavigator } from '../components/navigation/GraphNavigator';
|
|
|
|
const Header: React.FC = () => {
|
|
const theme = useTheme();
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const { user, signOut } = useAuth();
|
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
const [isAuthenticated, setIsAuthenticated] = useState(!!user);
|
|
const isAdmin = user?.email === import.meta.env.VITE_SUPER_ADMIN_EMAIL;
|
|
const showGraphNavigation = location.pathname === '/single-player';
|
|
|
|
// Update authentication state whenever user changes
|
|
useEffect(() => {
|
|
const newAuthState = !!user;
|
|
setIsAuthenticated(newAuthState);
|
|
logger.debug('user-context', '🔄 User state changed in header', {
|
|
hasUser: newAuthState,
|
|
userId: user?.id,
|
|
userEmail: user?.email,
|
|
userState: newAuthState ? 'logged-in' : 'logged-out',
|
|
isAdmin
|
|
});
|
|
}, [user, isAdmin]);
|
|
|
|
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
|
setAnchorEl(event.currentTarget);
|
|
};
|
|
|
|
const handleMenuClose = () => {
|
|
setAnchorEl(null);
|
|
};
|
|
|
|
const handleNavigation = (path: string) => {
|
|
navigate(path);
|
|
handleMenuClose();
|
|
};
|
|
|
|
const handleSignupNavigation = (role: 'teacher' | 'student') => {
|
|
navigate('/signup', { state: { role } });
|
|
handleMenuClose();
|
|
};
|
|
|
|
const handleSignOut = async () => {
|
|
try {
|
|
logger.debug('auth-service', '🔄 Signing out user', { userId: user?.id });
|
|
await signOut();
|
|
setIsAuthenticated(false);
|
|
setAnchorEl(null);
|
|
logger.debug('auth-service', '✅ User signed out');
|
|
} catch (error) {
|
|
logger.error('auth-service', '❌ Error signing out:', error);
|
|
console.error('Error signing out:', error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AppBar
|
|
position="fixed"
|
|
sx={{
|
|
height: `${HEADER_HEIGHT}px`,
|
|
bgcolor: theme.palette.background.paper,
|
|
color: theme.palette.text.primary,
|
|
boxShadow: 1
|
|
}}
|
|
>
|
|
<Toolbar sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
height: '100%'
|
|
}}>
|
|
{showGraphNavigation && (
|
|
<GraphNavigator />
|
|
)}
|
|
|
|
<Typography
|
|
variant="h6"
|
|
component="div"
|
|
sx={{
|
|
flexGrow: showGraphNavigation ? 0 : 1,
|
|
fontWeight: 600,
|
|
cursor: 'pointer',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1
|
|
}}
|
|
onClick={() => handleNavigation('/')}
|
|
>
|
|
<Home sx={{ color: theme.palette.primary.main }} />
|
|
Classroom Copilot
|
|
</Typography>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
{isAuthenticated && (
|
|
<Typography
|
|
variant="body2"
|
|
sx={{
|
|
color: theme.palette.text.secondary,
|
|
display: { xs: 'none', sm: 'block' }
|
|
}}
|
|
>
|
|
{user?.email}
|
|
</Typography>
|
|
)}
|
|
|
|
<IconButton
|
|
color="inherit"
|
|
aria-label="menu"
|
|
onClick={handleMenuOpen}
|
|
sx={{
|
|
border: `1px solid ${theme.palette.divider}`,
|
|
borderRadius: 1,
|
|
'&:hover': {
|
|
bgcolor: theme.palette.action.hover
|
|
}
|
|
}}
|
|
>
|
|
<MenuIcon />
|
|
</IconButton>
|
|
|
|
<Menu
|
|
anchorEl={anchorEl}
|
|
open={Boolean(anchorEl)}
|
|
onClose={handleMenuClose}
|
|
PaperProps={{
|
|
sx: {
|
|
width: 280,
|
|
maxHeight: '80vh'
|
|
}
|
|
}}
|
|
>
|
|
<Typography
|
|
variant="subtitle2"
|
|
sx={{
|
|
px: 2,
|
|
py: 1.5,
|
|
color: theme.palette.text.secondary,
|
|
fontWeight: 600,
|
|
letterSpacing: '0.5px',
|
|
textTransform: 'uppercase',
|
|
fontSize: '0.75rem'
|
|
}}
|
|
>
|
|
Navigation
|
|
</Typography>
|
|
|
|
{isAuthenticated ? [
|
|
// Home
|
|
<MenuItem key="home" onClick={() => handleNavigation('/')}>
|
|
<ListItemIcon>
|
|
<Home />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Home" />
|
|
</MenuItem>,
|
|
<Divider key="home-divider" />,
|
|
|
|
// Timetable Section
|
|
<Typography
|
|
key="timetable-header"
|
|
variant="subtitle2"
|
|
sx={{
|
|
px: 2,
|
|
py: 1,
|
|
color: theme.palette.primary.main,
|
|
fontWeight: 600,
|
|
letterSpacing: '0.5px',
|
|
textTransform: 'uppercase',
|
|
fontSize: '0.75rem'
|
|
}}
|
|
>
|
|
Timetable & Classes
|
|
</Typography>,
|
|
<MenuItem key="timetable" onClick={() => handleNavigation('/timetable')}>
|
|
<ListItemIcon>
|
|
<Schedule />
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary="My Timetable"
|
|
secondary="View your schedule"
|
|
/>
|
|
</MenuItem>,
|
|
<MenuItem key="classes" onClick={() => handleNavigation('/classes')}>
|
|
<ListItemIcon>
|
|
<Class />
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary="Browse Classes"
|
|
secondary="Find and enroll in classes"
|
|
/>
|
|
</MenuItem>,
|
|
<MenuItem key="my-classes" onClick={() => handleNavigation('/my-classes')}>
|
|
<ListItemIcon>
|
|
<Book />
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary="My Classes"
|
|
secondary="Manage your classes"
|
|
/>
|
|
</MenuItem>,
|
|
<MenuItem key="enrollment-requests" onClick={() => handleNavigation('/enrollment-requests')}>
|
|
<ListItemIcon>
|
|
<Enrollment />
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary="Enrollment Requests"
|
|
secondary="Review pending enrollments"
|
|
/>
|
|
</MenuItem>,
|
|
<Divider key="timetable-divider" />,
|
|
|
|
// Features Section
|
|
<Typography
|
|
key="features-header"
|
|
variant="subtitle2"
|
|
sx={{
|
|
px: 2,
|
|
py: 1,
|
|
color: theme.palette.text.secondary,
|
|
fontWeight: 600,
|
|
letterSpacing: '0.5px',
|
|
textTransform: 'uppercase',
|
|
fontSize: '0.75rem'
|
|
}}
|
|
>
|
|
Features
|
|
</Typography>,
|
|
<MenuItem key="calendar" onClick={() => handleNavigation('/calendar')}>
|
|
<ListItemIcon>
|
|
<Calendar />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Calendar" />
|
|
</MenuItem>,
|
|
<MenuItem key="teacher-planner" onClick={() => handleNavigation('/teacher-planner')}>
|
|
<ListItemIcon>
|
|
<TeacherPlanner />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Teacher Planner" />
|
|
</MenuItem>,
|
|
<MenuItem key="exam-marker" onClick={() => handleNavigation('/exam-marker')}>
|
|
<ListItemIcon>
|
|
<ExamMarker />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Exam Marker" />
|
|
</MenuItem>,
|
|
<Divider key="features-divider" />,
|
|
|
|
// Utilities Section
|
|
<Typography
|
|
key="utilities-header"
|
|
variant="subtitle2"
|
|
sx={{
|
|
px: 2,
|
|
py: 1,
|
|
color: theme.palette.text.secondary,
|
|
fontWeight: 600,
|
|
letterSpacing: '0.5px',
|
|
textTransform: 'uppercase',
|
|
fontSize: '0.75rem'
|
|
}}
|
|
>
|
|
Utilities
|
|
</Typography>,
|
|
<MenuItem key="settings" onClick={() => handleNavigation('/settings')}>
|
|
<ListItemIcon>
|
|
<Settings />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Settings" />
|
|
</MenuItem>,
|
|
<MenuItem key="search" onClick={() => handleNavigation('/search')}>
|
|
<ListItemIcon>
|
|
<Search />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Search" />
|
|
</MenuItem>,
|
|
|
|
// Admin Section
|
|
...(isAdmin ? [
|
|
<Divider key="admin-divider" />,
|
|
<Typography
|
|
key="admin-header"
|
|
variant="subtitle2"
|
|
sx={{
|
|
px: 2,
|
|
py: 1,
|
|
color: theme.palette.error.main,
|
|
fontWeight: 600,
|
|
letterSpacing: '0.5px',
|
|
textTransform: 'uppercase',
|
|
fontSize: '0.75rem'
|
|
}}
|
|
>
|
|
Administration
|
|
</Typography>,
|
|
<MenuItem key="admin" onClick={() => handleNavigation('/admin')}>
|
|
<ListItemIcon>
|
|
<Admin />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Admin Dashboard" />
|
|
</MenuItem>
|
|
] : []),
|
|
|
|
// Authentication Section
|
|
<Divider key="auth-divider" />,
|
|
<MenuItem key="signout" onClick={handleSignOut}>
|
|
<ListItemIcon>
|
|
<Logout />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Sign Out" />
|
|
</MenuItem>
|
|
] : [
|
|
// Authentication Section for Non-authenticated Users
|
|
<MenuItem key="signin" onClick={() => handleNavigation('/login')}>
|
|
<ListItemIcon>
|
|
<Login />
|
|
</ListItemIcon>
|
|
<ListItemText primary="Sign In" />
|
|
</MenuItem>,
|
|
<Divider key="signup-divider" />,
|
|
<MenuItem key="teacher-signup" onClick={() => handleSignupNavigation('teacher')}>
|
|
<ListItemIcon>
|
|
<Teacher />
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary="Sign up as Teacher"
|
|
secondary="Create a teacher account"
|
|
/>
|
|
</MenuItem>,
|
|
<MenuItem key="student-signup" onClick={() => handleSignupNavigation('student')}>
|
|
<ListItemIcon>
|
|
<Student />
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary="Sign up as Student"
|
|
secondary="Create a student account"
|
|
/>
|
|
</MenuItem>
|
|
]}
|
|
</Menu>
|
|
</Box>
|
|
</Toolbar>
|
|
</AppBar>
|
|
);
|
|
};
|
|
|
|
export default Header;
|