// Bridges the real tenant-registry contract with the rich design-fixture // shape that the new portal screens render against. // // During M10.2 dev there's no requirement that tenant-registry be up — the // portal still has to render the design end-to-end. So this loader resolves // against the fixtures first (which carry the design fields: mono, seats, // plan label, lifecycle reasons, generated series, etc.) and falls back to // what tenant-registry returns when a slug isn't in the fixture set. // // Once the registry is enriched to carry the design fields end-to-end this // module collapses into a thin pass-through. import { tenantBySlug, type TenantRecord } from "@/lib/fixtures"; import { fetchTenantBySlug, type Tenant } from "@/lib/tenant-registry"; export type PortalTenant = TenantRecord; export async function loadTenantForShell(slug: string): Promise { const fx = tenantBySlug(slug); if (fx) return fx; // Slug isn't one of the 5 fixture tenants — try the real registry. If // the registry isn't reachable we let the error bubble to the layout's // notFound() path. let live: Tenant | null = null; try { live = await fetchTenantBySlug(slug); } catch { return null; } if (!live) return null; // Minimal shim so the shell can render. Design-rich fields fall back to // placeholders that won't blow up the layout. return { id: live.id, domain: `${slug}.breakpilot.eu`, name: live.name, short: live.name, mono: live.name.slice(0, 2).toUpperCase(), status: live.status, legalType: "—", city: "—", country: "—", vat: "—", plan: live.plan, planCode: live.plan, seats: { used: 0, total: 0 }, monthly: 0, contact: "—", contactEmail: "—", renewal: "—", since: live.created_at?.slice(0, 10) ?? "—", entitled: [], trialing: [], trialEnds: live.trial_ends_at ?? undefined, seed: 0, findingCount: 0, products: [], findings: [], activity: [], audit: [], invoices: [], team: [], series: { findings30: [], evidence30: [], controls30: [], heatmap: [], prodSeries: {}, }, metrics: { openFindings: 0, critical: 0, lastScan: "—", lastScanDate: "—", evidence: 0, controlsPassing: 0, controlsTotal: 240, severity: { critical: 0, high: 0, medium: 0, low: 0 }, resolved7: 0, findingsDelta: 0, }, }; } // Returns the trial-days-left if any. export function trialDaysLeft(t: PortalTenant, nowMs: number = Date.now()): number { if (t.status !== "trial") return 0; if (typeof t.trialDaysLeft === "number") return t.trialDaysLeft; if (t.trialEnds) { const ms = new Date(t.trialEnds).getTime() - nowMs; return Math.max(0, Math.ceil(ms / (24 * 3600 * 1000))); } return 0; }