This commit is contained in:
@@ -1,23 +1,30 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getSessionUser } from "@/lib/session";
|
||||
import { listInvoices, syncOverdueInvoices } from "@/lib/db";
|
||||
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 two things:
|
||||
* 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. Phase 4 will add a
|
||||
* "settings.payByInvoice" toggle visibility-gated to owners only.
|
||||
* non-owner team members see the same view.
|
||||
*/
|
||||
export default async function CustomerBillingPage() {
|
||||
const user = await getSessionUser();
|
||||
@@ -31,10 +38,11 @@ export default async function CustomerBillingPage() {
|
||||
console.warn("syncOverdueInvoices failed in /billing:", e);
|
||||
}
|
||||
|
||||
const invoices = await listInvoices({
|
||||
zitadelOrgId: user.orgId,
|
||||
limit: 200,
|
||||
});
|
||||
// Parallel fetch — invoices + credit notes are independent.
|
||||
const [invoices, creditNotes] = await Promise.all([
|
||||
listInvoices({ zitadelOrgId: user.orgId, limit: 200 }),
|
||||
listCreditNotesForOrg(user.orgId, 200),
|
||||
]);
|
||||
|
||||
return (
|
||||
<main className="max-w-5xl mx-auto px-6 py-8">
|
||||
@@ -54,12 +62,24 @@ export default async function CustomerBillingPage() {
|
||||
<RunningTotalWidget isOwner={user.roles.includes("owner")} />
|
||||
</section>
|
||||
|
||||
<section className="animate-in animate-in-delay-2">
|
||||
<section className="animate-in animate-in-delay-2 mb-8">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
|
||||
{t("historyHeading")}
|
||||
</h2>
|
||||
<CustomerInvoiceList invoices={invoices} />
|
||||
</section>
|
||||
|
||||
{/* 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 && (
|
||||
<section className="animate-in animate-in-delay-3">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
|
||||
{t("creditNotesHeading")}
|
||||
</h2>
|
||||
<CustomerCreditNoteList creditNotes={creditNotes} />
|
||||
</section>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user