removed sidebar

This commit is contained in:
nvrl committed 2026-06-16 22:19:53 +02:00
1 parent 0621437546
commit e714e1ea51
4 files changed
+16 -504

No files matched your search

+5 -110
View File
@@ -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 | HTMLElement>(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 = (
<IconButton
color="inherit"
onClick={() => setDrawerOpen((o) => !o)}
aria-label="toggle navigation"
sx={{ mx: 0.5 }}
>
<MenuIcon />
</IconButton>
);
const notificationsMenu = (
<>
<IconButton color="inherit" onClick={(e) => setNotifAnchorEl(e.currentTarget)} sx={{ ml: 1 }}>
<Badge badgeContent={user?.unread_notifications_count || 0} color="error">
<NotificationsIcon />
</Badge>
</IconButton>
<NotificationMenu
anchorEl={notifAnchorEl}
open={Boolean(notifAnchorEl)}
onClose={() => setNotifAnchorEl(null)}
menuSx={{ mt: 1 }}
/>
</>
);
return (
<Box
sx={{
@@ -112,63 +63,7 @@ const App: React.FC = () => {
width: '100%',
}}
>
{isMobile && (
<GlassAppBar>
{mobileAnchor === 'left' && hamburger}
<Box sx={{ mr: 1, display: 'flex', alignItems: 'center' }}>
<BrandMark compact />
</Box>
<GlobalSearch />
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{!loadingUser && (
<>
{user && notificationsMenu}
{!user && (
<Button
variant="contained"
component={Link}
to="/login"
sx={{ borderRadius: 2, ml: 1 }}
>
{t('nav.login')}
</Button>
)}
</>
)}
{mobileAnchor === 'right' && hamburger}
</Box>
</GlassAppBar>
)}
{!isMobile && <DesktopTopBar />}
{isMobile && (
<SwipeableDrawer
anchor={mobileAnchor}
open={drawerOpen}
onOpen={() => setDrawerOpen(true)}
onClose={() => setDrawerOpen(false)}
disableBackdropTransition
disableDiscovery
slotProps={{
paper: {
sx: {
height: '100vh',
maxHeight: '100vh',
top: 0,
bottom: 0,
},
},
}}
>
<NavSidebar categories={categories} onNavigate={() => setDrawerOpen(false)} />
</SwipeableDrawer>
)}
<TopBar />
<CategoryBar categories={categories} />
+1 -1
View File
@@ -51,7 +51,7 @@ export const BrandMark = ({ onNavigate, compact = false }: BrandMarkProps) => {
</Typography>
</Box>
{!compact && (
<Box sx={{ minWidth: 0 }}>
<Box sx={{ minWidth: 0, display: { xs: 'none', md: 'block' } }}>
<Typography sx={{ fontWeight: 800, lineHeight: 1.1, letterSpacing: '-0.01em' }} noWrap>
{t('brand.name')}
</Typography>
@@ -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 | HTMLElement>(null);
const close = () => onNavigate?.();
const isActive = (path: string) =>
path === '/' ? location.pathname === '/' : location.pathname.startsWith(path);
const unreadCount = user?.unread_notifications_count || 0;
return (
<Box
sx={{
width: NAV_SIDEBAR_WIDTH,
height: '100%',
display: 'flex',
flexDirection: 'column',
}}
role="presentation"
>
<Box
sx={{
p: 2,
borderBottom: '1px solid',
borderColor: 'divider',
}}
>
<BrandMark onNavigate={close} />
</Box>
{user ? (
<Box
component={Link}
to={`/profile/${user.username}`}
onClick={close}
sx={{
p: 2,
display: 'flex',
alignItems: 'center',
gap: 1.5,
color: 'inherit',
textDecoration: 'none',
borderBottom: '1px solid',
borderColor: 'divider',
'&:hover': { bgcolor: 'action.hover' },
}}
>
<Avatar
src={user.avatar || undefined}
sx={{
width: 44,
height: 44,
border: '2px solid',
borderColor: 'primary.main',
}}
>
{user.username.charAt(0).toUpperCase()}
</Avatar>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="subtitle1"
sx={{
fontWeight: 700,
lineHeight: 1.2,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{user.username}
</Typography>
<Typography variant="caption" color="text.secondary">
{t('nav.profile')}
</Typography>
</Box>
</Box>
) : null}
{showSearch && (
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
<GlobalSearch compact />
</Box>
)}
<List sx={{ flex: 1, overflowY: 'auto', py: 1 }}>
<ListItem disablePadding>
<ListItemButton component={Link} to="/" onClick={close} selected={isActive('/')}>
<ListItemIcon sx={{ minWidth: 40 }}>
<HomeOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('nav.home')} />
</ListItemButton>
</ListItem>
{user && (
<ListItem disablePadding>
<ListItemButton
component={Link}
to="/community"
onClick={close}
selected={isActive('/community')}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<GroupsOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('nav.community')} />
</ListItemButton>
</ListItem>
)}
{user && showNotifications && (
<ListItem disablePadding>
<ListItemButton onClick={(e) => setNotifAnchorEl(e.currentTarget)}>
<ListItemIcon sx={{ minWidth: 40 }}>
<Badge badgeContent={unreadCount} color="error">
<NotificationsOutlinedIcon />
</Badge>
</ListItemIcon>
<ListItemText primary={t('nav.notifications', { defaultValue: 'Notifications' })} />
</ListItemButton>
</ListItem>
)}
<ListItemButton
onClick={() => setCatOpen(!catOpen)}
selected={location.pathname.startsWith('/category/')}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<CategoryOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('nav.categories')} />
{catOpen ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={catOpen} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{categories.map((cat) => (
<ListItemButton
key={cat.slug}
component={Link}
to={`/category/${cat.slug}`}
onClick={close}
selected={location.pathname === `/category/${cat.slug}`}
sx={{ pl: 7 }}
>
<ListItemText primary={t(`categories.${cat.slug}`, { defaultValue: cat.name })} />
</ListItemButton>
))}
</List>
</Collapse>
{user && (
<>
<Divider sx={{ my: 1 }} />
<ListItem disablePadding>
<ListItemButton
component={Link}
to={`/profile/${user.username}`}
onClick={close}
selected={location.pathname === `/profile/${user.username}`}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<PersonOutlineOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('nav.profile')} />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton
component={Link}
to="/dashboard"
onClick={close}
selected={isActive('/dashboard')}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<CollectionsBookmarkOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('nav.dashboard')} />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton
component={Link}
to="/settings"
onClick={close}
selected={isActive('/settings')}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<SettingsOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('nav.settings')} />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton
component={Link}
to="/support"
onClick={close}
selected={isActive('/support')}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<HelpOutlineOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('support.title')} />
</ListItemButton>
</ListItem>
{user.is_superuser && (
<>
<Divider sx={{ my: 1 }} />
<ListItem disablePadding>
<ListItemButton
component={Link}
to="/admin-panel"
onClick={close}
selected={isActive('/admin-panel')}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<AdminPanelSettingsOutlinedIcon color="primary" />
</ListItemIcon>
<ListItemText
primary={t('admin.panelTitle', { defaultValue: 'Admin Panel' })}
primaryTypographyProps={{ sx: { color: 'primary.main', fontWeight: 700 } }}
/>
</ListItemButton>
</ListItem>
</>
)}
{following.length > 0 && (
<>
<Divider sx={{ my: 1 }} />
<ListItemButton onClick={() => setFollowingOpen(!followingOpen)}>
<ListItemIcon sx={{ minWidth: 40 }}>
<PeopleOutlineOutlinedIcon />
</ListItemIcon>
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<span>{t('nav.following')}</span>
<Typography variant="caption" color="text.secondary">
{following.length}
</Typography>
</Box>
}
/>
{followingOpen ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={followingOpen} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{following.map((f) => (
<ListItem key={f.id} disablePadding>
<ListItemButton
component={Link}
to={`/profile/${f.username}`}
onClick={close}
selected={location.pathname === `/profile/${f.username}`}
sx={{ pl: 5 }}
>
<ListItemAvatar sx={{ minWidth: 40 }}>
<Avatar src={f.avatar || undefined} sx={{ width: 28, height: 28 }}>
{!f.avatar && f.username.charAt(0).toUpperCase()}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={f.username}
primaryTypographyProps={{ variant: 'body2' }}
/>
</ListItemButton>
</ListItem>
))}
</List>
</Collapse>
</>
)}
</>
)}
{!user && (
<>
<Divider sx={{ my: 1 }} />
<ListItem disablePadding>
<ListItemButton
component={Link}
to="/login"
onClick={close}
selected={isActive('/login')}
>
<ListItemIcon sx={{ minWidth: 40 }}>
<LoginOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('nav.login')} />
</ListItemButton>
</ListItem>
</>
)}
</List>
{user && (
<Box sx={{ borderTop: '1px solid', borderColor: 'divider' }}>
<ListItemButton
onClick={() => {
close();
logout();
}}
>
<ListItemIcon sx={{ minWidth: 40, color: 'error.main' }}>
<LogoutOutlinedIcon />
</ListItemIcon>
<ListItemText
primary={t('nav.logout')}
primaryTypographyProps={{ sx: { color: 'error.main', fontWeight: 600 } }}
/>
</ListItemButton>
</Box>
)}
{showNotifications && (
<NotificationMenu
anchorEl={notifAnchorEl}
open={Boolean(notifAnchorEl)}
onClose={() => setNotifAnchorEl(null)}
onItemNavigate={close}
anchorOrigin={{ vertical: 'center', horizontal: 'right' }}
transformOrigin={{ vertical: 'center', horizontal: 'left' }}
paperSx={{ ml: 1 }}
/>
)}
</Box>
);
};
@@ -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 (
<GlassAppBar toolbarSx={{ maxWidth: 1400, width: '100%', mx: 'auto', px: { md: 6 } }}>
<GlassAppBar
toolbarSx={{ maxWidth: 1400, width: '100%', mx: 'auto', px: { xs: 1, sm: 4, md: 6 } }}
>
<BrandMark />
<GlobalSearch />