211 lines
6.8 KiB
TypeScript
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>
|
|
);
|
|
}
|