129 lines
4.0 KiB
TypeScript
129 lines
4.0 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
import { Link, useRouter } from '@tanstack/react-router'
|
|
|
|
import { authClient } from '#/lib/auth-client'
|
|
|
|
type StaffUser = { name: string; role?: string | null }
|
|
type SectionLink = { label: string; hash: string }
|
|
|
|
type Props = {
|
|
staffUser?: StaffUser | null
|
|
sectionLinks?: SectionLink[]
|
|
}
|
|
|
|
function MenuIcon() {
|
|
return (
|
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" aria-hidden="true">
|
|
<path d="M4 6h16M4 12h16M4 18h16" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
export function AppSidebar({ staffUser, sectionLinks }: Props) {
|
|
const router = useRouter()
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const navRef = useRef<HTMLElement>(null)
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) return
|
|
|
|
function handleOutsideClick(event: MouseEvent) {
|
|
if (navRef.current && !navRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false)
|
|
}
|
|
}
|
|
function handleKeyDown(event: KeyboardEvent) {
|
|
if (event.key === 'Escape') setIsOpen(false)
|
|
}
|
|
|
|
document.addEventListener('mousedown', handleOutsideClick)
|
|
document.addEventListener('keydown', handleKeyDown)
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleOutsideClick)
|
|
document.removeEventListener('keydown', handleKeyDown)
|
|
}
|
|
}, [isOpen])
|
|
|
|
async function handleLogout() {
|
|
await authClient.signOut()
|
|
await router.navigate({ to: '/staff/login' })
|
|
await router.invalidate()
|
|
}
|
|
|
|
return (
|
|
<nav className="app-sidebar" aria-label="Hauptnavigation" ref={navRef}>
|
|
<div className="app-sidebar-bar">
|
|
<Link className="wordmark app-sidebar-brand" to="/" onClick={() => setIsOpen(false)}>
|
|
Steakhaus <span>bei Mirko</span>
|
|
</Link>
|
|
<button
|
|
type="button"
|
|
className="app-sidebar-toggle"
|
|
aria-label={isOpen ? 'Menü schließen' : 'Menü öffnen'}
|
|
aria-expanded={isOpen}
|
|
onClick={() => setIsOpen((open) => !open)}
|
|
>
|
|
<MenuIcon />
|
|
</button>
|
|
</div>
|
|
|
|
<div className={isOpen ? 'app-sidebar-panel is-open' : 'app-sidebar-panel'} onClick={() => setIsOpen(false)}>
|
|
<ul className="app-sidebar-nav">
|
|
{sectionLinks && sectionLinks.length > 0 && (
|
|
<>
|
|
{sectionLinks.map((section) => (
|
|
<li key={section.hash}>
|
|
<Link to="/" hash={section.hash}>
|
|
{section.label}
|
|
</Link>
|
|
</li>
|
|
))}
|
|
<li className="app-sidebar-divider" role="separator" />
|
|
</>
|
|
)}
|
|
{staffUser && (
|
|
<li>
|
|
<Link to="/staff" activeOptions={{ exact: true }} activeProps={{ className: 'is-active' }}>
|
|
Reservierungen
|
|
</Link>
|
|
</li>
|
|
)}
|
|
{staffUser?.role === 'admin' && (
|
|
<li>
|
|
<Link to="/staff/team" activeProps={{ className: 'is-active' }}>
|
|
Mitarbeiter
|
|
</Link>
|
|
</li>
|
|
)}
|
|
{staffUser && <li className="app-sidebar-divider" role="separator" />}
|
|
<li>
|
|
<Link to="/" activeOptions={{ exact: true }} activeProps={{ className: 'is-active' }}>
|
|
Startseite
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link to="/impressum" activeProps={{ className: 'is-active' }}>
|
|
Impressum
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
|
|
<div className="app-sidebar-footer">
|
|
{staffUser ? (
|
|
<>
|
|
<span className="app-sidebar-user">Angemeldet als {staffUser.name}</span>
|
|
<button type="button" className="btn btn-secondary" onClick={handleLogout}>
|
|
Abmelden
|
|
</button>
|
|
</>
|
|
) : (
|
|
<Link className="btn btn-secondary" to="/staff/login">
|
|
Mitarbeiter-Login
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
)
|
|
}
|