diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9df84a1..b61015a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,32 +1,14 @@ -import React, { Suspense, lazy, useEffect, useState } from 'react'; +import React, { Suspense, lazy, useEffect } from 'react'; import { Routes, Route, Link, Navigate } from 'react-router-dom'; -import { - Typography, - Button, - Box, - IconButton, - SwipeableDrawer, - CircularProgress, - Badge, - 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 { Typography, Button, Box, CircularProgress, useTheme as useMuiTheme } from '@mui/material'; import { useTranslation } from 'react-i18next'; import Footer from './components/Footer'; import CookieBanner from './components/CookieBanner'; -import { GlobalSearch } from './app/GlobalSearch'; import { useAuth } from './hooks/useAuth'; import { useTheme } from './hooks/useTheme'; -import { useDrawerAnchor } from './hooks/useDrawerAnchor'; -import { NavSidebar } from './components/layout/NavSidebar'; -import { BrandMark } from './components/layout/BrandMark'; -import { DesktopTopBar } from './components/layout/DesktopTopBar'; +import { TopBar } from './components/layout/TopBar'; import { CategoryBar } from './components/layout/CategoryBar'; import { useCategories } from './api/queries/useCategories'; -import { NotificationMenu } from './components/layout/NotificationMenu'; import { RequireSuperuser } from './components/auth/RequireSuperuser'; import type { CatppuccinTheme } from './theme'; @@ -58,14 +40,10 @@ const AdminReplyDetail = lazy(() => import('./pages/AdminReplyDetail')); const App: React.FC = () => { const { t } = useTranslation(); const muiTheme = useMuiTheme(); - const { user, loadingUser } = useAuth(); + const { user } = useAuth(); const { themeName, handleThemeChange } = useTheme(); - const { anchor: mobileAnchor } = useDrawerAnchor(); - const [notifAnchorEl, setNotifAnchorEl] = useState(null); const { data: categories = [] } = useCategories(); - const isMobile = useMediaQuery(muiTheme.breakpoints.down('md')); - const [drawerOpen, setDrawerOpen] = useState(false); useEffect(() => { document.body.style.backgroundColor = muiTheme.palette.background.default; @@ -76,33 +54,6 @@ const App: React.FC = () => { await handleThemeChange(name, !!user); }; - const hamburger = ( - setDrawerOpen((o) => !o)} - aria-label="toggle navigation" - sx={{ mx: 0.5 }} - > - - - ); - - const notificationsMenu = ( - <> - setNotifAnchorEl(e.currentTarget)} sx={{ ml: 1 }}> - - - - - setNotifAnchorEl(null)} - menuSx={{ mt: 1 }} - /> - - ); - return ( { width: '100%', }} > - {isMobile && ( - - {mobileAnchor === 'left' && hamburger} - - - - - - - - - {!loadingUser && ( - <> - {user && notificationsMenu} - - {!user && ( - - )} - - )} - - {mobileAnchor === 'right' && hamburger} - - - )} - - {!isMobile && } - - {isMobile && ( - setDrawerOpen(true)} - onClose={() => setDrawerOpen(false)} - disableBackdropTransition - disableDiscovery - slotProps={{ - paper: { - sx: { - height: '100vh', - maxHeight: '100vh', - top: 0, - bottom: 0, - }, - }, - }} - > - setDrawerOpen(false)} /> - - )} + diff --git a/frontend/src/components/layout/BrandMark.tsx b/frontend/src/components/layout/BrandMark.tsx index 455ba02..aeb9326 100644 --- a/frontend/src/components/layout/BrandMark.tsx +++ b/frontend/src/components/layout/BrandMark.tsx @@ -51,7 +51,7 @@ export const BrandMark = ({ onNavigate, compact = false }: BrandMarkProps) => { {!compact && ( - + {t('brand.name')} diff --git a/frontend/src/components/layout/NavSidebar.tsx b/frontend/src/components/layout/NavSidebar.tsx deleted file mode 100644 index 633afc3..0000000 --- a/frontend/src/components/layout/NavSidebar.tsx +++ /dev/null @@ -1,387 +0,0 @@ -import { useState } from 'react'; -import { Link, useLocation } from 'react-router-dom'; -import { - Avatar, - Badge, - Box, - Collapse, - Divider, - List, - ListItem, - ListItemAvatar, - ListItemButton, - ListItemIcon, - ListItemText, - Typography, -} from '@mui/material'; -import ExpandLess from '@mui/icons-material/ExpandLess'; -import ExpandMore from '@mui/icons-material/ExpandMore'; -import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; -import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined'; -import CategoryOutlinedIcon from '@mui/icons-material/CategoryOutlined'; -import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined'; -import CollectionsBookmarkOutlinedIcon from '@mui/icons-material/CollectionsBookmarkOutlined'; -import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'; -import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined'; -import AdminPanelSettingsOutlinedIcon from '@mui/icons-material/AdminPanelSettingsOutlined'; -import LogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined'; -import LoginOutlinedIcon from '@mui/icons-material/LoginOutlined'; -import NotificationsOutlinedIcon from '@mui/icons-material/NotificationsOutlined'; -import PeopleOutlineOutlinedIcon from '@mui/icons-material/PeopleOutlineOutlined'; -import { useTranslation } from 'react-i18next'; -import { useAuth } from '../../hooks/useAuth'; -import { GlobalSearch } from '../../app/GlobalSearch'; -import { NotificationMenu } from './NotificationMenu'; -import { BrandMark } from './BrandMark'; - -export const NAV_SIDEBAR_WIDTH = 280; - -export interface NavSidebarProps { - categories: { name: string; slug: string }[]; - onNavigate?: () => void; - showSearch?: boolean; - showNotifications?: boolean; -} - -export const NavSidebar = ({ - categories, - onNavigate, - showSearch = false, - showNotifications = false, -}: NavSidebarProps) => { - const { t } = useTranslation(); - const location = useLocation(); - const { user, following, logout } = useAuth(); - // Categories are the primary browse axis — open by default so they're - // discoverable without an extra click (UX audit #4). - const [catOpen, setCatOpen] = useState(true); - const [followingOpen, setFollowingOpen] = useState(false); - const [notifAnchorEl, setNotifAnchorEl] = useState(null); - - const close = () => onNavigate?.(); - const isActive = (path: string) => - path === '/' ? location.pathname === '/' : location.pathname.startsWith(path); - - const unreadCount = user?.unread_notifications_count || 0; - - return ( - - - - - - {user ? ( - - - {user.username.charAt(0).toUpperCase()} - - - - {user.username} - - - {t('nav.profile')} - - - - ) : null} - - {showSearch && ( - - - - )} - - - - - - - - - - - - {user && ( - - - - - - - - - )} - - {user && showNotifications && ( - - setNotifAnchorEl(e.currentTarget)}> - - - - - - - - - )} - - setCatOpen(!catOpen)} - selected={location.pathname.startsWith('/category/')} - > - - - - - {catOpen ? : } - - - - {categories.map((cat) => ( - - - - ))} - - - - {user && ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {user.is_superuser && ( - <> - - - - - - - - - - - )} - - {following.length > 0 && ( - <> - - setFollowingOpen(!followingOpen)}> - - - - - {t('nav.following')} - - {following.length} - - - } - /> - {followingOpen ? : } - - - - {following.map((f) => ( - - - - - {!f.avatar && f.username.charAt(0).toUpperCase()} - - - - - - ))} - - - - )} - - )} - - {!user && ( - <> - - - - - - - - - - - )} - - - {user && ( - - { - close(); - logout(); - }} - > - - - - - - - )} - - {showNotifications && ( - setNotifAnchorEl(null)} - onItemNavigate={close} - anchorOrigin={{ vertical: 'center', horizontal: 'right' }} - transformOrigin={{ vertical: 'center', horizontal: 'left' }} - paperSx={{ ml: 1 }} - /> - )} - - ); -}; diff --git a/frontend/src/components/layout/DesktopTopBar.tsx b/frontend/src/components/layout/TopBar.tsx similarity index 92% rename from frontend/src/components/layout/DesktopTopBar.tsx rename to frontend/src/components/layout/TopBar.tsx index a9f951a..4a9ddb4 100644 --- a/frontend/src/components/layout/DesktopTopBar.tsx +++ b/frontend/src/components/layout/TopBar.tsx @@ -28,12 +28,14 @@ import { BrandMark } from './BrandMark'; import { NotificationMenu } from './NotificationMenu'; /** - * Desktop top bar (md+). Promotes global search to the primary, centered slot; - * notifications + an account menu sit on the right. Everything that used to be - * a flat sidebar row (profile, dashboard, settings, support, admin, following, - * logout) now lives behind the avatar menu so the chrome stays light. + * Top bar for every breakpoint. Promotes global search to the primary, centered + * slot; notifications + an account menu sit on the right. Everything that used to + * be a flat sidebar row (profile, dashboard, settings, support, admin, following, + * logout) lives behind the avatar menu — so there's no mobile drawer anymore, the + * same dropdown serves all sizes. Browse links (home/community/categories) live + * in the CategoryBar below. */ -export const DesktopTopBar = () => { +export const TopBar = () => { const { t } = useTranslation(); const { user, following, logout } = useAuth(); @@ -45,7 +47,9 @@ export const DesktopTopBar = () => { const unreadCount = user?.unread_notifications_count || 0; return ( - +