This repository has been archived on 2026-05-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Nest/nest-front/src/components/shared/Navbar.tsx

211 lines
6.8 KiB
TypeScript

import { useState, useCallback } from 'react';
import { Link, NavLink, useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { useSettings } from '../../contexts/SettingsContext';
const BASE_NAV_LINKS = [
{ to: '/', label: 'Home', end: true, feature: null as null | 'forum' | 'bugs' },
{ to: '/studio', label: 'Studio', end: false, feature: null },
{ to: '/events', label: 'Events', end: false, feature: null },
{ to: '/forum', label: 'Forum', end: false, feature: 'forum' as const },
{ to: '/bugs', label: 'Bugs', end: false, feature: 'bugs' as const },
];
export function Navbar() {
const { user, isAuthenticated, logout } = useAuth();
const { forumEnabled, bugsEnabled } = useSettings();
const navigate = useNavigate();
const [menuOpen, setMenuOpen] = useState(false);
const navLinks = BASE_NAV_LINKS.filter(({ feature }) => {
if (feature === 'forum') return forumEnabled;
if (feature === 'bugs') return bugsEnabled;
return true;
});
const handleLogout = useCallback(() => {
logout();
setMenuOpen(false);
navigate('/');
}, [logout, navigate]);
const closeMenu = useCallback(() => setMenuOpen(false), []);
const navLinkStyle = ({ isActive }: { isActive: boolean }): React.CSSProperties => ({
fontFamily: 'var(--font-mono)',
fontSize: '0.82rem',
textTransform: 'uppercase',
letterSpacing: '0.12em',
textDecoration: 'none',
color: isActive ? 'var(--color-yellow)' : 'var(--color-text-dim)',
borderBottom: isActive ? '2px solid var(--color-yellow)' : '2px solid transparent',
paddingBottom: '2px',
transition: 'color 0.1s, border-color 0.1s',
});
return (
<header
style={{
background: 'var(--color-bg)',
borderBottom: '2px solid var(--color-border)',
position: 'sticky',
top: 0,
zIndex: 50,
}}
>
<nav
className="max-w-7xl mx-auto px-4 sm:px-6 h-14 flex items-center justify-between"
role="navigation"
aria-label="Main navigation"
>
{/* Logo */}
<Link
to="/"
onClick={closeMenu}
style={{
display: 'flex',
alignItems: 'baseline',
gap: '0.5rem',
textDecoration: 'none',
}}
>
<span
style={{
fontFamily: 'var(--font-heading)',
color: 'var(--color-yellow)',
fontSize: '1.5rem',
letterSpacing: '0.08em',
}}
>
CROWMATE
</span>
<span
style={{
fontFamily: 'var(--font-mono)',
color: 'var(--color-text-muted)',
fontSize: '0.62rem',
letterSpacing: '0.15em',
}}
>
STUDIO
</span>
</Link>
{/* Desktop Nav */}
<div className="hidden md:flex items-center gap-6">
{navLinks.map(({ to, label, end }) => (
<NavLink key={to} to={to} end={end} style={navLinkStyle}>
{label}
</NavLink>
))}
</div>
{/* Desktop Auth */}
<div className="hidden md:flex items-center gap-3">
{isAuthenticated ? (
<>
<Link
to="/account"
style={{
color: 'var(--color-text-dim)',
fontSize: '0.78rem',
fontFamily: 'var(--font-mono)',
textDecoration: 'none',
}}
>
[{user?.username}]
</Link>
<button className="btn-terminal btn-danger" onClick={handleLogout} style={{ padding: '0.3rem 0.85rem', fontSize: '0.75rem' }}>
Logout
</button>
</>
) : (
<>
<Link to="/login" className="btn-terminal" style={{ padding: '0.3rem 0.85rem', fontSize: '0.75rem' }}>
Login
</Link>
<Link to="/register" className="btn-terminal btn-amber" style={{ padding: '0.3rem 0.85rem', fontSize: '0.75rem' }}>
Register
</Link>
</>
)}
</div>
{/* Mobile hamburger */}
<button
className="md:hidden"
style={{ background: 'transparent', border: 'none', cursor: 'pointer', padding: '4px' }}
onClick={() => setMenuOpen((v) => !v)}
aria-label={menuOpen ? 'Close menu' : 'Open menu'}
aria-expanded={menuOpen}
>
{/* Three-bar icon using block chars */}
<div style={{ fontFamily: 'var(--font-mono)', color: 'var(--color-cyan)', fontSize: '1.2rem', lineHeight: 1 }}>
{menuOpen ? '✕' : '≡'}
</div>
</button>
</nav>
{/* Mobile menu */}
{menuOpen && (
<div
style={{
background: 'var(--color-bg)',
borderTop: '2px solid var(--color-border)',
padding: '1rem 1.25rem',
}}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.85rem' }}>
{navLinks.map(({ to, label, end }) => (
<NavLink
key={to}
to={to}
end={end}
onClick={closeMenu}
style={navLinkStyle}
>
{label}
</NavLink>
))}
{/* Auth section */}
<div
style={{
borderTop: '1px solid var(--color-border)',
paddingTop: '0.85rem',
display: 'flex',
gap: '0.6rem',
flexWrap: 'wrap',
}}
>
{isAuthenticated ? (
<>
<Link
to="/account"
style={{ color: 'var(--color-text-dim)', fontSize: '0.78rem', textDecoration: 'none', fontFamily: 'var(--font-mono)' }}
onClick={closeMenu}
>
[{user?.username}]
</Link>
<button className="btn-terminal btn-danger" onClick={handleLogout} style={{ padding: '0.25rem 0.7rem', fontSize: '0.73rem' }}>
Logout
</button>
</>
) : (
<>
<Link to="/login" className="btn-terminal" onClick={closeMenu} style={{ padding: '0.25rem 0.7rem', fontSize: '0.73rem' }}>
Login
</Link>
<Link to="/register" className="btn-terminal btn-amber" onClick={closeMenu} style={{ padding: '0.25rem 0.7rem', fontSize: '0.73rem' }}>
Register
</Link>
</>
)}
</div>
</div>
</div>
)}
</header>
);
}