import React, { Suspense, lazy, useEffect, useState } from 'react'; import { Routes, Route, Link, useNavigate } from 'react-router-dom'; import { Typography, Button, Box, IconButton, Avatar, Menu, MenuItem, Drawer, Divider, List, ListItem, ListItemButton, ListItemText, Collapse, CircularProgress, ListItemAvatar, ListSubheader, Badge, alpha, useMediaQuery, useTheme as useMuiTheme, } from '@mui/material'; import { GlassAppBar } from './components/ui'; import MenuIcon from '@mui/icons-material/Menu'; import NotificationsIcon from '@mui/icons-material/Notifications'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; import { useTranslation } from 'react-i18next'; import api from './lib/api'; import Footer from './components/Footer'; import CookieBanner from './components/CookieBanner'; import { formatDate } from './utils/date'; import { GlobalSearch } from './app/GlobalSearch'; import { useAuth } from './hooks/useAuth'; import { useTheme } from './hooks/useTheme'; import { useNotifications } from './hooks/useNotifications'; import type { Notification } from './contexts/NotificationContext'; import type { CatppuccinTheme } from './theme'; const MainPage = lazy(() => import('./pages/MainPage')); const CategoryFlavors = lazy(() => import('./pages/categories/CategoryFlavors')); const FlavorDetail = lazy(() => import('./pages/flavors/FlavorDetail')); const Dashboard = lazy(() => import('./pages/Dashboard')); const PublicProfile = lazy(() => import('./pages/PublicProfile')); const CommunityFeed = lazy(() => import('./pages/CommunityFeed')); const About = lazy(() => import('./pages/About')); const Privacy = lazy(() => import('./pages/Privacy')); const Login = lazy(() => import('./pages/Login')); const Settings = lazy(() => import('./pages/Settings')); const Support = lazy(() => import('./pages/Support')); const VerifyEmail = lazy(() => import('./pages/VerifyEmail')); const AdminDashboard = lazy(() => import('./pages/AdminDashboard')); const AdminUserDetail = lazy(() => import('./pages/AdminUserDetail')); const AdminRatingDetail = lazy(() => import('./pages/AdminRatingDetail')); const AdminReplyDetail = lazy(() => import('./pages/AdminReplyDetail')); const App: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); const muiTheme = useMuiTheme(); const { user, following, loadingUser, logout } = useAuth(); const { themeName, handleThemeChange } = useTheme(); const { notifications, markAllRead, markRead } = useNotifications(); const [anchorEl, setAnchorEl] = useState(null); const [notifAnchorEl, setNotifAnchorEl] = useState(null); const [catAnchorEl, setCatAnchorEl] = useState(null); const [categories, setCategories] = useState<{ name: string; slug: string }[]>([]); const [drawerOpen, setDrawerOpen] = useState(false); const [catOpen, setCatOpen] = useState(false); const [adminMode, setAdminMode] = useState(localStorage.getItem('admin-mode') === 'true'); const isMobile = useMediaQuery('(max-width:1150px)'); useEffect(() => { api .get('categories/') .then((res) => { const data = Array.isArray(res.data) ? res.data : res.data.results || []; setCategories(data); }) .catch(() => setCategories([])); }, []); useEffect(() => { document.body.style.backgroundColor = muiTheme.palette.background.default; document.body.style.color = muiTheme.palette.text.primary; }, [muiTheme]); const toggleAdminMode = () => { const newVal = !adminMode; setAdminMode(newVal); localStorage.setItem('admin-mode', String(newVal)); }; const onThemeChange = async (name: CatppuccinTheme) => { await handleThemeChange(name, !!user); setAnchorEl(null); }; const handleNotificationClick = async (notif: Notification) => { if (!notif.is_read) await markRead(notif.id); setNotifAnchorEl(null); if (notif.notification_type.startsWith('ticket')) { navigate('/support'); } else if (notif.notification_type === 'profile_comment') { navigate(`/profile/${user?.username}`); } else if (notif.notification_type === 'follow') { navigate(`/profile/${notif.actor_username}`); } else if (notif.flavor_id) { window.location.href = `/flavor/${notif.flavor_id}`; } }; const drawer = ( setDrawerOpen(false)} style={{ color: 'inherit', textDecoration: 'none' }} > Holy Flavors Archive setDrawerOpen(false)}> {user && ( setDrawerOpen(false)}> )} setCatOpen(!catOpen)}> {catOpen ? : } {categories.map((cat) => ( setDrawerOpen(false)} sx={{ pl: 4 }} > ))} {user ? ( <> setDrawerOpen(false)} > {user.username.charAt(0).toUpperCase()} {user.username} } /> setDrawerOpen(false)}> setDrawerOpen(false)}> setDrawerOpen(false)}> {user.is_superuser && ( )} {user.is_superuser && ( setDrawerOpen(false)} > )} {following.length > 0 && ( <> {t('nav.following')} {following.map((f) => ( setDrawerOpen(false)} > {!f.avatar && f.username.charAt(0).toUpperCase()} ))} )} ) : ( setDrawerOpen(false)}> )} ); const notificationsMenu = ( <> setNotifAnchorEl(e.currentTarget)} sx={{ ml: 1 }}> setNotifAnchorEl(null)} elevation={3} sx={{ mt: 1 }} PaperProps={{ sx: { width: 320, maxHeight: 400, borderRadius: 1 } }} > Notifications {(user?.unread_notifications_count || 0) > 0 && ( )} {notifications.length === 0 ? ( No notifications yet ) : ( notifications.map((n) => ( handleNotificationClick(n)} sx={{ py: 1.5, px: 2, borderBottom: '1px solid', borderColor: 'divider', bgcolor: n.is_read ? 'transparent' : alpha(muiTheme.palette.primary.main, 0.05), whiteSpace: 'normal', display: 'flex', gap: 2, alignItems: 'flex-start', }} > {n.actor_username.charAt(0).toUpperCase()} {n.actor_username}{' '} {n.notification_type === 'reply' ? `replied to your review on ${n.flavor_name}` : n.notification_type === 'mention' ? `mentioned you on ${n.flavor_name}` : n.notification_type === 'follow' ? t('community.notifFollow') : n.notification_type === 'profile_comment' ? `left a message on your guestbook` : n.notification_type === 'ticket_new' ? t('community.notifTicketNew') : user?.is_superuser ? t('community.notifTicketReplyAdmin') : t('community.notifTicketReply')} {formatDate(n.created_at)} )) )} ); return ( Holy Flavors Archive HFA {user && ( )} setCatAnchorEl(null)} elevation={3} sx={{ mt: 1 }} > {categories.map((cat) => ( setCatAnchorEl(null)} > {t(`categories.${cat.slug}`, { defaultValue: cat.name })} ))} {!loadingUser && ( <> {user && notificationsMenu} {user ? ( setAnchorEl(e.currentTarget)}> {user.username.charAt(0).toUpperCase()} setAnchorEl(null)} elevation={3} sx={{ mt: 1 }} > setAnchorEl(null)} > {t('nav.profile')} setAnchorEl(null)}> {t('nav.dashboard')} setAnchorEl(null)}> {t('nav.settings')} setAnchorEl(null)}> {t('support.title')} {user.is_superuser && ( {t('admin.adminMode')}: {adminMode ? 'ON' : 'OFF'} )} {user.is_superuser && ( setAnchorEl(null)} sx={{ fontWeight: 'bold', color: 'primary.main' }} > Admin Panel )} {t('nav.logout')} ) : ( )} )} {isMobile && ( setDrawerOpen(true)} sx={{ ml: 1 }}> )} setDrawerOpen(false)}> {drawer} } > } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> 404 - Page Not Found } />