import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { getSessionUser } from "@/lib/session"; import { listCreditNotesForOrg, listInvoices, syncOverdueInvoices, } from "@/lib/db"; import { CustomerInvoiceList } from "@/components/billing/customer-invoice-list"; import { CustomerCreditNoteList } from "@/components/billing/customer-credit-note-list"; import { RunningTotalWidget } from "@/components/billing/running-total-widget"; /** * /billing — customer's billing home. * * Shows three things: * 1. RunningTotalWidget — current calendar month's accruing cost * (or the already-issued invoice for the current month, if * that ran early). * 2. CustomerInvoiceList — every issued invoice for this org, * newest first. Status is reflected with a colored badge. * 3. CustomerCreditNoteList — Phase 7. Credit notes (voids and * refunds) for this org, with PDF download links. Hidden * entirely when there are none (the common case). * * Anyone signed in can view this. The data is org-scoped; even * non-owner team members see the same view. */ export async function generateMetadata() { const t = await getTranslations("common"); return { title: t("billing") }; } export default async function CustomerBillingPage() { const user = await getSessionUser(); if (!user) redirect("/login"); const t = await getTranslations("customerBilling"); // Sync overdue status before listing — cheap, idempotent. try { await syncOverdueInvoices(); } catch (e) { console.warn("syncOverdueInvoices failed in /billing:", e); } // Parallel fetch — invoices + credit notes are independent. const [invoices, creditNotes] = await Promise.all([ listInvoices({ zitadelOrgId: user.orgId, limit: 200 }), listCreditNotesForOrg(user.orgId, 200), ]); return (

{t("title")}

{t("subtitle")}

{t("currentPeriodHeading")}

{/* Phase 6: pass the owner flag so the no-config CTA shows the right call-to-action vs the right hint. */}

{t("historyHeading")}

{/* Phase 7: credit-note section. CustomerCreditNoteList itself returns null when there are no credit notes, so this whole section disappears for orgs in normal operation. */} {creditNotes.length > 0 && (

{t("creditNotesHeading")}

)}
); }