60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
import { createRoot } from "react-dom/client";
|
|
import { lazy, Suspense, useEffect, useState } from "react";
|
|
import { createBrowserRouter, Link, NavLink, Outlet, RouterProvider, useLocation, type RouteObject } from "react-router-dom";
|
|
import type { AuthUser } from "@stc/contracts";
|
|
import { AppShell } from "@stc/ui";
|
|
import { webFeatures } from "./features";
|
|
import { visibleNav } from "./nav";
|
|
|
|
function Home() {
|
|
return <p className="work">No features installed yet</p>;
|
|
}
|
|
|
|
/** The signed-in user (GET /api/me), refreshed on navigation so sign-in and sign-out update the nav. */
|
|
function useSessionUser(): AuthUser | null {
|
|
const { pathname } = useLocation();
|
|
const [user, setUser] = useState<AuthUser | null>(null);
|
|
useEffect(() => {
|
|
let live = true;
|
|
fetch("/api/me", { credentials: "same-origin" })
|
|
.then((r) => (r.ok ? r.json() : { user: null }))
|
|
.then((b: { user: AuthUser | null }) => { if (live) setUser(b.user ?? null); })
|
|
.catch(() => { if (live) setUser(null); });
|
|
return () => { live = false; };
|
|
}, [pathname]);
|
|
return user;
|
|
}
|
|
|
|
function Layout() {
|
|
// Links the user cannot use are hidden; the pages and APIs still enforce access themselves.
|
|
const nav = visibleNav(webFeatures, useSessionUser());
|
|
const header = (
|
|
<>
|
|
<Link to="/" style={{ fontWeight: 600, color: "var(--head)", textDecoration: "none" }}>STCourses</Link>
|
|
{nav.length ? (
|
|
<nav className="stc-nav" aria-label="Areas">
|
|
{nav.map((r) => <NavLink key={r.path} to={r.path}>{r.nav!.label}</NavLink>)}
|
|
</nav>
|
|
) : null}
|
|
</>
|
|
);
|
|
return (
|
|
<AppShell header={header}>
|
|
<Suspense fallback={null}><Outlet /></Suspense>
|
|
</AppShell>
|
|
);
|
|
}
|
|
|
|
const featureRoutes: RouteObject[] = webFeatures.flatMap((f) =>
|
|
f.routes.map((r) => {
|
|
const Element = lazy(r.element);
|
|
return { path: r.path, element: <Element /> };
|
|
}),
|
|
);
|
|
|
|
const router = createBrowserRouter([
|
|
{ element: <Layout />, children: [...featureRoutes, ...(featureRoutes.some((r) => r.path === "/") ? [] : [{ path: "*", element: <Home /> }])] },
|
|
]);
|
|
|
|
createRoot(document.getElementById("root")!).render(<RouterProvider router={router} />);
|