"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { Card } from "@/components/ui/card"; interface OrgEntry { zitadelOrgId: string; companyName: string | null; country: string | null; hasBillingAddress: boolean; } interface Props { orgs: OrgEntry[]; } const LOCALE_OPTIONS = [ { value: "de", label: "Deutsch" }, { value: "en", label: "English" }, { value: "fr", label: "Français" }, { value: "it", label: "Italiano" }, ]; /** * Step 1 of the custom-invoice flow: pick an org. Creating the * draft on the backend allocates an id we redirect to; the editor * page then loads the draft and lets the admin add lines. * * The dropdown shows the company name when known, falling back to * the raw org id. Orgs without a billing snapshot are visually * marked and warn the admin — they can still create the draft but * won't be able to issue until billing info is set. * * Default issue date = today; due date = today + 30 days. These * are sensible defaults the editor can override. */ export function NewInvoiceForm({ orgs }: Props) { const t = useTranslations("adminBilling"); const router = useRouter(); const [orgId, setOrgId] = useState( orgs.find((o) => o.hasBillingAddress)?.zitadelOrgId ?? orgs[0]?.zitadelOrgId ?? "" ); const [locale, setLocale] = useState<"de" | "en" | "fr" | "it">("de"); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const selected = orgs.find((o) => o.zitadelOrgId === orgId); // Pick a locale default from the org's country if admin hasn't // overridden — same heuristic the auto cron uses. const onOrgChange = (newOrgId: string) => { setOrgId(newOrgId); const o = orgs.find((x) => x.zitadelOrgId === newOrgId); const c = (o?.country ?? "").toUpperCase(); if (["CH", "LI", "AT", "DE"].includes(c)) setLocale("de"); else if (["FR", "BE", "LU"].includes(c)) setLocale("fr"); else if (c === "IT") setLocale("it"); else setLocale("en"); }; const onSubmit = async () => { if (!orgId) { setError(t("newInvoiceOrgRequired")); return; } setError(""); setBusy(true); try { const today = new Date().toISOString().slice(0, 10); const due = new Date(); due.setDate(due.getDate() + 30); const dueIso = due.toISOString().slice(0, 10); const res = await fetch("/api/admin/billing/invoice-drafts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ zitadelOrgId: orgId, payload: { issueDate: today, dueDate: dueIso, locale, paymentMethod: "invoice", lines: [], }, }), }); const j = await res.json().catch(() => ({})); if (!res.ok) throw new Error(j.error || `HTTP ${res.status}`); router.push(`/admin/billing/invoice-drafts/${j.draft.id}`); } catch (e: any) { setError(e.message); setBusy(false); } }; return (
{selected && !selected.hasBillingAddress && (

{t("newInvoiceOrgBillingMissing")}

)}
{error &&
{error}
}
); }