49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
import { redirect, notFound } from "next/navigation";
|
|
import { getTranslations } from "next-intl/server";
|
|
import { getSessionUser } from "@/lib/session";
|
|
import { getOrgBilling } from "@/lib/db";
|
|
import { BillingSettingsForm } from "@/components/settings/billing-form";
|
|
|
|
/**
|
|
* /settings/billing — customer-side billing details management.
|
|
*
|
|
* Owner-only by visibility: non-owner members get a 404 (same
|
|
* response as if the page didn't exist). The link to this page
|
|
* is also hidden from non-owners on /billing and elsewhere, but
|
|
* the page itself enforces too — a non-owner who learns the URL
|
|
* still gets 404, not 403, so the page's existence doesn't leak.
|
|
*
|
|
* First-time visitors see an empty form. Subsequent visits see
|
|
* the current values, editable. Save creates or updates via the
|
|
* shared upsert path; the row's existence drives whether the
|
|
* monthly issuance cron will pick this org up.
|
|
*/
|
|
export default async function BillingSettingsPage() {
|
|
const user = await getSessionUser();
|
|
if (!user) redirect("/login");
|
|
// Non-owners get a 404 — see comment above.
|
|
if (!user.roles.includes("owner")) notFound();
|
|
|
|
const t = await getTranslations("settingsBilling");
|
|
const existing = await getOrgBilling(user.orgId);
|
|
|
|
return (
|
|
<main className="max-w-3xl 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">
|
|
{user.isPersonal ? t("subtitlePersonal") : t("subtitle")}
|
|
</p>
|
|
</div>
|
|
<div className="animate-in animate-in-delay-1">
|
|
<BillingSettingsForm
|
|
initial={existing}
|
|
isPersonal={user.isPersonal}
|
|
/>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|