Files
pieced-portal/src/app/[locale]/billing/page.tsx
admin fadfdd3435
All checks were successful
Build and Push / build (push) Successful in 1m46s
Phase6: Customer Billing details
2026-05-25 11:47:14 +02:00

66 lines
2.3 KiB
TypeScript

import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { getSessionUser } from "@/lib/session";
import { listInvoices, syncOverdueInvoices } from "@/lib/db";
import { CustomerInvoiceList } from "@/components/billing/customer-invoice-list";
import { RunningTotalWidget } from "@/components/billing/running-total-widget";
/**
* /billing — customer's billing home.
*
* Shows two 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.
*
* Anyone signed in can view this. The data is org-scoped; even
* non-owner team members see the same view. Phase 4 will add a
* "settings.payByInvoice" toggle visibility-gated to owners only.
*/
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);
}
const invoices = await listInvoices({
zitadelOrgId: user.orgId,
limit: 200,
});
return (
<main className="max-w-5xl mx-auto px-6 py-8">
<div className="mb-8 animate-in">
<h1 className="font-display text-2xl font-semibold accent-rule">
{t("title")}
</h1>
<p className="text-sm text-text-secondary mt-3">{t("subtitle")}</p>
</div>
<section className="mb-8 animate-in animate-in-delay-1">
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
{t("currentPeriodHeading")}
</h2>
{/* Phase 6: pass the owner flag so the no-config CTA shows
the right call-to-action vs the right hint. */}
<RunningTotalWidget isOwner={user.roles.includes("owner")} />
</section>
<section className="animate-in animate-in-delay-2">
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
{t("historyHeading")}
</h2>
<CustomerInvoiceList invoices={invoices} />
</section>
</main>
);
}