Billing rework

This commit is contained in:
2026-05-02 00:34:26 +02:00
parent 08460f93d4
commit 3d7f3fd1bb
7 changed files with 52 additions and 11 deletions

View File

@@ -40,6 +40,7 @@ export default async function BillingSettingsPage() {
isPersonal={user.isPersonal} isPersonal={user.isPersonal}
orgName={user.orgName} orgName={user.orgName}
userName={user.name} userName={user.name}
userEmail={user.email}
/> />
</main> </main>
); );

View File

@@ -1008,7 +1008,11 @@ export function OnboardingWizard({
})) }))
} }
rows={3} rows={3}
placeholder={t("billingNotesPlaceholder")} placeholder={t(
isPersonal
? "billingNotesPlaceholderPersonal"
: "billingNotesPlaceholder"
)}
className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors resize-y" className="w-full px-3 py-2 bg-surface-2 border border-border rounded-lg text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors resize-y"
/> />
</div> </div>
@@ -1113,6 +1117,19 @@ export function OnboardingWizard({
</div> </div>
} }
/> />
{/* Bug 35: VAT review row. Company customers see this so
they can verify the VAT id they typed before submitting.
Personal customers never see it — they don't have a
VAT number, the form didn't ask, the review hides it. */}
{!isPersonal &&
config.billingAddress.vatNumber &&
config.billingAddress.vatNumber.trim().length > 0 && (
<ReviewRow
label={t("billingVatNumber")}
value={config.billingAddress.vatNumber}
mono
/>
)}
<ReviewRow <ReviewRow
label={t("reviewContactEmail")} label={t("reviewContactEmail")}
value={userEmail || ""} value={userEmail || ""}

View File

@@ -20,6 +20,13 @@ interface Props {
orgName: string; orgName: string;
/** Default full-name for personal orgs on first edit. */ /** Default full-name for personal orgs on first edit. */
userName: string; userName: string;
/**
* Default billing email — the address the user registered with.
* Used on first edit (when `initial` is null). Customers can still
* type a different address (e.g. accounting@…) but the registration
* email is a sensible starting point.
*/
userEmail: string;
} }
/** /**
@@ -38,6 +45,7 @@ export function BillingSettingsForm({
isPersonal, isPersonal,
orgName, orgName,
userName, userName,
userEmail,
}: Props) { }: Props) {
const t = useTranslations("settingsBilling"); const t = useTranslations("settingsBilling");
const tCommon = useTranslations("common"); const tCommon = useTranslations("common");
@@ -53,7 +61,12 @@ export function BillingSettingsForm({
const [city, setCity] = useState(initial?.city ?? ""); const [city, setCity] = useState(initial?.city ?? "");
const [country, setCountry] = useState(initial?.country ?? "CH"); const [country, setCountry] = useState(initial?.country ?? "CH");
const [vatNumber, setVatNumber] = useState(initial?.vatNumber ?? ""); const [vatNumber, setVatNumber] = useState(initial?.vatNumber ?? "");
const [billingEmail, setBillingEmail] = useState(initial?.billingEmail ?? ""); // Default billing email to the user's registration email when no
// record exists yet. They can change it (a separate accounting
// address is common); we just want sensible pre-fill on first edit.
const [billingEmail, setBillingEmail] = useState(
initial?.billingEmail ?? userEmail ?? ""
);
const [notes, setNotes] = useState(initial?.notes ?? ""); const [notes, setNotes] = useState(initial?.notes ?? "");
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
@@ -227,7 +240,9 @@ export function BillingSettingsForm({
onChange={(e) => setNotes(e.target.value)} onChange={(e) => setNotes(e.target.value)}
rows={3} rows={3}
className="w-full px-3 py-2 rounded-lg border border-border bg-surface-2 text-text-primary text-sm focus:outline-none focus:border-text-secondary" className="w-full px-3 py-2 rounded-lg border border-border bg-surface-2 text-text-primary text-sm focus:outline-none focus:border-text-secondary"
placeholder={t("notesPlaceholder")} placeholder={t(
isPersonal ? "notesPlaceholderPersonal" : "notesPlaceholder"
)}
/> />
</div> </div>

View File

@@ -118,7 +118,8 @@
"rejectionReason": "Angegebener Grund", "rejectionReason": "Angegebener Grund",
"saveChanges": "Änderungen speichern", "saveChanges": "Änderungen speichern",
"billingVatNumber": "MWST-Nummer", "billingVatNumber": "MWST-Nummer",
"billingVatHelp": "Ihre registrierte MWST-Nummer. Falls Ihre Firma von der MWST befreit ist, leer lassen und in den Notizen erläutern." "billingVatHelp": "Ihre registrierte MWST-Nummer. Falls Ihre Firma von der MWST befreit ist, leer lassen und in den Notizen erläutern.",
"billingNotesPlaceholderPersonal": "Was wir wissen sollten — bevorzugte Zahlungsart, Rechnungsreferenz, etc."
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
@@ -409,6 +410,7 @@
"saved": "Gespeichert.", "saved": "Gespeichert.",
"saveFailed": "Konnte nicht gespeichert werden. Bitte erneut versuchen.", "saveFailed": "Konnte nicht gespeichert werden. Bitte erneut versuchen.",
"lastUpdated": "Zuletzt aktualisiert {when}", "lastUpdated": "Zuletzt aktualisiert {when}",
"fullName": "Voller Name" "fullName": "Voller Name",
"notesPlaceholderPersonal": "Was wir wissen sollten — bevorzugte Zahlungsart, Rechnungsreferenz, etc."
} }
} }

View File

@@ -118,7 +118,8 @@
"rejectionReason": "Reason given", "rejectionReason": "Reason given",
"saveChanges": "Save changes", "saveChanges": "Save changes",
"billingVatNumber": "VAT number", "billingVatNumber": "VAT number",
"billingVatHelp": "Your registered VAT identifier. If your company is VAT-exempt, leave blank and explain in the notes field." "billingVatHelp": "Your registered VAT identifier. If your company is VAT-exempt, leave blank and explain in the notes field.",
"billingNotesPlaceholderPersonal": "Anything we should know — preferred payment method, billing reference, etc."
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
@@ -409,6 +410,7 @@
"saved": "Saved.", "saved": "Saved.",
"saveFailed": "Could not save. Please try again.", "saveFailed": "Could not save. Please try again.",
"lastUpdated": "Last updated {when}", "lastUpdated": "Last updated {when}",
"fullName": "Full name" "fullName": "Full name",
"notesPlaceholderPersonal": "Anything we should know — preferred payment method, billing reference, etc."
} }
} }

View File

@@ -118,7 +118,8 @@
"rejectionReason": "Motif indiqué", "rejectionReason": "Motif indiqué",
"saveChanges": "Enregistrer les modifications", "saveChanges": "Enregistrer les modifications",
"billingVatNumber": "Numéro de TVA", "billingVatNumber": "Numéro de TVA",
"billingVatHelp": "Votre identifiant TVA enregistré. Si votre entreprise est exonérée de TVA, laissez vide et précisez dans les notes." "billingVatHelp": "Votre identifiant TVA enregistré. Si votre entreprise est exonérée de TVA, laissez vide et précisez dans les notes.",
"billingNotesPlaceholderPersonal": "Tout ce que nous devons savoir — moyen de paiement préféré, référence de facturation, etc."
}, },
"dashboard": { "dashboard": {
"title": "Tableau de bord", "title": "Tableau de bord",
@@ -409,6 +410,7 @@
"saved": "Enregistré.", "saved": "Enregistré.",
"saveFailed": "Impossible d'enregistrer. Veuillez réessayer.", "saveFailed": "Impossible d'enregistrer. Veuillez réessayer.",
"lastUpdated": "Dernière mise à jour {when}", "lastUpdated": "Dernière mise à jour {when}",
"fullName": "Nom complet" "fullName": "Nom complet",
"notesPlaceholderPersonal": "Tout ce que nous devons savoir — moyen de paiement préféré, référence de facturation, etc."
} }
} }

View File

@@ -118,7 +118,8 @@
"rejectionReason": "Motivo indicato", "rejectionReason": "Motivo indicato",
"saveChanges": "Salva modifiche", "saveChanges": "Salva modifiche",
"billingVatNumber": "Partita IVA", "billingVatNumber": "Partita IVA",
"billingVatHelp": "Il tuo identificativo IVA registrato. Se la tua azienda è esente IVA, lascia vuoto e spiega nelle note." "billingVatHelp": "Il tuo identificativo IVA registrato. Se la tua azienda è esente IVA, lascia vuoto e spiega nelle note.",
"billingNotesPlaceholderPersonal": "Qualsiasi cosa dovremmo sapere — metodo di pagamento preferito, riferimento per fatturazione, ecc."
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
@@ -409,6 +410,7 @@
"saved": "Salvato.", "saved": "Salvato.",
"saveFailed": "Impossibile salvare. Riprova.", "saveFailed": "Impossibile salvare. Riprova.",
"lastUpdated": "Ultimo aggiornamento {when}", "lastUpdated": "Ultimo aggiornamento {when}",
"fullName": "Nome completo" "fullName": "Nome completo",
"notesPlaceholderPersonal": "Qualsiasi cosa dovremmo sapere — metodo di pagamento preferito, riferimento per fatturazione, ecc."
} }
} }