48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
import { getTranslations } from "next-intl/server";
|
|
import { redirect, notFound } from "next/navigation";
|
|
import { getSessionUser, canMutate } from "@/lib/session";
|
|
import { getOrgBilling } from "@/lib/db";
|
|
import { BillingSettingsForm } from "@/components/settings/billing-settings-form";
|
|
|
|
/**
|
|
* /settings/billing — view and edit org-scoped billing (Bug 34/35).
|
|
*
|
|
* Server-side fetches the existing record (if any) and passes it to
|
|
* the client form. The form posts to PUT /api/billing on submit.
|
|
*
|
|
* Access: same gate as the API — owners and platform admins. `user`
|
|
* role redirects to /settings (which also wouldn't list billing for
|
|
* them). 403 here would be friendlier than redirect, but the most
|
|
* likely cause of a `user` landing on this URL is sharing a bookmark
|
|
* with their owner — silent redirect is gentle.
|
|
*/
|
|
export default async function BillingSettingsPage() {
|
|
const user = await getSessionUser();
|
|
if (!user) redirect("/login");
|
|
if (!canMutate(user)) {
|
|
redirect("/settings");
|
|
}
|
|
const t = await getTranslations("settingsBilling");
|
|
|
|
const billing = 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">{t("subtitle")}</p>
|
|
</div>
|
|
|
|
<BillingSettingsForm
|
|
initial={billing}
|
|
isPersonal={user.isPersonal}
|
|
orgName={user.orgName}
|
|
userName={user.name}
|
|
userEmail={user.email}
|
|
/>
|
|
</main>
|
|
);
|
|
}
|