"use client"; import { LogOutIcon, RefreshCwIcon } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import { AdminSessionProvider } from "@/components/admin/AdminSession"; import { SubscriptionsDialog } from "@/components/admin/SubscriptionsDialog"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Toaster } from "@/components/ui/sonner"; import { goToAdminLogin, signOut } from "@/lib/admin-auth"; import { api, ApiError } from "@/lib/api"; import type { AdminUser } from "@/lib/types"; type Failure = "denied" | "unavailable"; export default function AdminLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname(); // /admin/login sits under this layout too, and must not be gated: it is the // forwarder signed-out visitors are sent to. Gating it would hand the admin // app the forwarder's own URL as the return target - so signing in would // land back on the forwarder, which forwards again. const isLoginPage = pathname === "/admin/login"; const [me, setMe] = useState(null); const [failure, setFailure] = useState(null); // The one call that decides whether the admin area renders. proxy.ts already // bounced visitors with no cookie at all, but only the API can tell a valid // session from a stale one, or a colleague without the calendar permission // from one who has it. // // Exactly one status sends the user to the login page: 401. Anything else // means they may well be signed in already, and bouncing them would sign them // in, return them here and fail again - an endless loop with nothing on // screen to explain it, triggered by exactly the partial outage most likely // just after a deploy. 403 is the permission case; everything else (500, the // API's 503 when the admin database is down, a network or CORS failure as // status 0) is a fault worth showing. useEffect(() => { if (isLoginPage) return; api .me() .then((user) => { // Signed in, but not for this app. The API would refuse every write, // so say so once instead of failing per action. if (!user.apps.includes("calendar")) { setFailure("denied"); return; } setMe(user); }) .catch((e: unknown) => { const status = e instanceof ApiError ? e.status : 0; if (status === 401) { goToAdminLogin(); return; } setFailure(status === 403 ? "denied" : "unavailable"); }); }, [isLoginPage]); if (isLoginPage) { return <>{children}; } if (failure) { return ; } return (
Nachklang Kalender
{me ? ( {me.fullName} ) : ( )} {me && }
{me ? {children} : }
); } /** What the page looks like while /me is in flight - the list's shape, not a blank page. */ function LoadingShell() { return (
{Array.from({ length: 4 }, (_, i) => ( ))}
); } function FailureScreen({ failure }: { failure: Failure }) { const denied = failure === "denied"; return (

{denied ? "Kein Zugriff auf den Kalender" : "Kalender nicht erreichbar"}

{denied ? "Dein Konto ist angemeldet, hat aber keine Berechtigung für den Kalender. Eine Administratorin oder ein Administrator kann sie dir in der Verwaltung geben – danach die Seite neu laden." : "Die Verwaltung ist gerade nicht erreichbar. Bitte lade die Seite in einem Moment neu."}

{denied && ( )}
); }