import { auth, signIn, signOut } from "@/auth"; import { ShellEmpty } from "@/components/ShellEmpty"; import type { SessionWithExtras } from "@/lib/session"; import { fetchTenantBySlug } from "@/lib/tenant-registry"; export default async function Dashboard({ params, }: { params: Promise<{ slug: string }>; }) { const { slug } = await params; const session = (await auth()) as SessionWithExtras | null; if (!session) { async function login() { "use server"; await signIn("keycloak", { redirectTo: `/${slug}/dashboard` }); } return (

Sign in to {slug}

); } async function logout() { "use server"; await signOut({ redirectTo: `/${slug}/dashboard` }); } const tenant = await fetchTenantBySlug(slug); const products = session.products ?? []; const trialDaysLeft = computeTrialDaysLeft(tenant?.trial_ends_at); return (
{tenant?.status === "trial" && tenant.trial_ends_at && ( )}

Dashboard

Welcome, {session.user?.name ?? session.user?.email ?? "user"}. Signed in as {session.org_roles?.join(", ") ?? "(no roles)"}.

Your products

{products.length === 0 ? ( ) : (
    {products.map((p) => (
  • {p}

    Tile content lands in M10.1.

  • ))}
)}
); } // Pure compute, lives outside any render path so react-hooks/purity is satisfied. function computeTrialDaysLeft(endsAt: string | null | undefined): number { if (!endsAt) return 0; const ms = new Date(endsAt).getTime() - Date.now(); return Math.max(0, Math.ceil(ms / (24 * 3600 * 1000))); } function TrialBanner({ endsAt, slug, daysLeft, }: { endsAt: string; slug: string; daysLeft: number; }) { const ends = new Date(endsAt); const urgent = daysLeft <= 3; return (
Trial — {daysLeft} day{daysLeft === 1 ? "" : "s"} left {" "}(ends {ends.toLocaleDateString()}). Upgrade →
); }