/* ═══════════════════════════════════════════════════════════════ ADMIN LAYOUT Bare on purpose. No PageShell, no announcement banner, no footer site map — none of that belongs around a staff tool, and /admin should never appear in navConfig. Three areas share this chrome: Home, the CMS and the Panel. The wordmark names whichever one you're in, and from anywhere but Home it's the way back to Home. The tab row is drawn everywhere except Home, where the card grid is the navigation and drawing both would say the same thing twice. Which tabs appear depends on the signed-in user — navFor() drops the superadmin-only ones — but that's cosmetics. The route guard and the API are what actually say no. ═══════════════════════════════════════════════════════════════ */ import { useCallback, useEffect, useMemo, useState } from "react"; import { Link, NavLink, Outlet, useLocation, useNavigate } from "react-router-dom"; import { useAuth } from "../../lib/auth.tsx"; import { AdminTitleContext } from "../../lib/adminTitle.tsx"; import { SITE_VERSION } from "../../lib/version.ts"; import { ROLE_LABELS, isSuper } from "../../lib/roles.ts"; import nguLogo from "../../assets/NGU_Logo.svg"; import { ADMIN_HOME, AREA_TITLES, areaFor, matches, navFor, target, } from "./adminNav.ts"; export default function AdminLayout() { const { user, logout } = useAuth(); const navigate = useNavigate(); const { pathname } = useLocation(); const area = areaFor(pathname); const areaTitle = AREA_TITLES[area]; const isHome = area === "home"; const nav = useMemo(() => navFor(user), [user]); const active = nav.find( (item) => matches(pathname, item.to) || (item.children ?? []).some((child) => matches(pathname, child.to)), ); const activeChild = (active?.children ?? []).find((child) => matches(pathname, child.to), ); // What the page below has published about itself — a record // name, or null on a list. setDetail is stable so publishing // can't loop. const [detail, setDetail] = useState(null); const stableSet = useCallback((value) => setDetail(value), []); const titleContext = useMemo(() => ({ setDetail: stableSet }), [stableSet]); useEffect(() => { // Sections only exist in the CMS. Home and Panel already say // what they are in the area title; "Panel | NGU Admin Panel" // would just stutter. const section = area === "cms" ? activeChild?.label ?? active?.label : null; document.title = [detail, section, areaTitle].filter(Boolean).join(" | "); }, [area, areaTitle, active, activeChild, detail]); async function handleLogout() { await logout(); navigate("/admin/login", { replace: true }); } const subnav = active?.children ?? []; const wordmark = {areaTitle}; return (
{/* Already home, so nothing to link to. */} {isHome ? ( wordmark ) : ( {wordmark} )} {!isHome && ( )}
{user?.name || user?.email}
{/* Subnav. Only drawn where there is something to draw, so Events and People don't get an empty grey strip. */} {subnav.length > 0 && (
{subnav.map((child) => ( {child.label} ))}
)}
{/* flex-1 rather than a fixed height: the footer sits at the bottom of a short page and below the content of a long one, without ever floating over it. */}
); }