import { notFound, redirect } from "next/navigation"; import type { ReactNode } from "react"; import { getPortalSession } from "@/lib/get-session"; import { loadTenantForShell } from "@/lib/portal-data"; import { Lifeline } from "@/components/portal/Lifeline"; import { NavRail } from "@/components/portal/NavRail"; import { Topbar } from "@/components/portal/Topbar"; import { ArchivedLockout } from "@/components/portal/ArchivedLockout"; import { MockWorker } from "@/components/portal/MockWorker"; import { ToastHost } from "@/components/portal/ToastHost"; const MOCK_API = !!process.env.BP_DEV_FIXTURE; export default async function TenantLayout({ children, params, }: { children: ReactNode; params: Promise<{ slug: string }>; }) { const { slug } = await params; const tenant = await loadTenantForShell(slug); if (!tenant) notFound(); const session = await getPortalSession(); // Tenant mismatch guard — a JWT scoped to tenant A must not be allowed // to view tenant B. If the slug in the path doesn't match the session // tenant_slug, redirect back to whatever this user CAN see. if (session && session.tenant_slug && session.tenant_slug !== slug) { redirect(`/${session.tenant_slug}/dashboard`); } // Archived tenants get a full-page 410 — no shell, no nav, no chrome. if (tenant.status === "archived") { return ; } // Unauthenticated visitors land on the existing in-page sign-in (each // route handles its own zero-session affordance). if (!session) { return (
{children}
); } return (
{MOCK_API ? ( <>