feat(onboarding): show recurring monthly fee in the wizard cost summary

This commit is contained in:
2026-05-29 23:38:22 +02:00
parent 521398b0fc
commit 3c2cb696c7
4 changed files with 54 additions and 18 deletions

View File

@@ -81,6 +81,7 @@ export default async function NewInstancePage() {
hasOrgBilling={hasOrgBilling}
existingOrgBilling={orgBilling}
setupFeeChf={pricing.tenantSetupFeeChf}
monthlyFeeChf={pricing.tenantMonthlyFeeChf}
/>
</div>
</div>

View File

@@ -326,6 +326,7 @@ export default async function DashboardPage() {
hasOrgBilling={hasOrgBilling}
existingOrgBilling={orgBilling}
setupFeeChf={platformPricing.tenantSetupFeeChf}
monthlyFeeChf={platformPricing.tenantMonthlyFeeChf}
/>
</div>
</div>

View File

@@ -31,6 +31,12 @@ interface OnboardingFlowProps {
* step. Forwarded straight to the wizard.
*/
setupFeeChf?: number | null;
/**
* Recurring per-tenant monthly fee (net CHF). Forwarded to the
* wizard's review-step cost summary so the customer sees the ongoing
* commitment, not just the one-time setup fee.
*/
monthlyFeeChf?: number | null;
/**
* Bug 6: when present, the wizard is rendered in edit mode against
* the given pending request. See `OnboardingWizard` for the full
@@ -59,6 +65,7 @@ export function OnboardingFlow({
hasOrgBilling,
existingOrgBilling,
setupFeeChf,
monthlyFeeChf,
editingRequest,
}: OnboardingFlowProps) {
const router = useRouter();
@@ -71,6 +78,7 @@ export function OnboardingFlow({
hasOrgBilling={hasOrgBilling}
existingOrgBilling={existingOrgBilling}
setupFeeChf={setupFeeChf}
monthlyFeeChf={monthlyFeeChf}
editingRequest={editingRequest}
onComplete={() => {
// Navigate back to /dashboard and re-fetch on the server. The

View File

@@ -117,6 +117,13 @@ interface WizardProps {
* the order skips the Checkout redirect (handled server-side).
*/
setupFeeChf?: number | null;
/**
* The platform's recurring per-tenant monthly fee (net CHF, before
* VAT). Shown on the review step alongside the setup fee so the
* customer sees the ongoing commitment — not just the one-time
* charge — before submitting. Null/0 hides the monthly line.
*/
monthlyFeeChf?: number | null;
/**
* Bug 6: when present, the wizard renders in "edit" mode — fields
* are pre-populated from the request, the SOUL.md auto-fetch is
@@ -157,6 +164,7 @@ export function OnboardingWizard({
hasOrgBilling,
existingOrgBilling,
setupFeeChf,
monthlyFeeChf,
editingRequest,
onComplete,
}: WizardProps) {
@@ -1382,28 +1390,46 @@ export function OnboardingWizard({
<p className="text-xs text-text-muted">{t("confirmNote")}</p>
{/* Phase 9b: order-time setup-fee notice + amount. The
figure shown is the net platform fee (before VAT);
VAT is added server-side based on the billing
country. We show "+ VAT" rather than a computed
gross to avoid mis-displaying a country-dependent
total. If setupFeeChf is null/0, no charge happens
and the whole block is suppressed. */}
{typeof setupFeeChf === "number" && setupFeeChf > 0 && (
{/* Cost summary. Surfaces the full commitment before
submitting — not just the one-time setup fee but the
recurring monthly per-assistant fee and the fact that
AI usage is billed by consumption (with the budget-cap
control as the reassurance). All figures are net (before
VAT); VAT is added server-side per billing country, so
we show "+ VAT" rather than a country-dependent gross.
The block is suppressed only when there are no fixed
fees at all. */}
{((typeof setupFeeChf === "number" && setupFeeChf > 0) ||
(typeof monthlyFeeChf === "number" && monthlyFeeChf > 0)) && (
<div className="text-xs rounded-md border border-accent/30 bg-accent/10 text-text-secondary px-3 py-3 mt-4">
<strong className="block text-text-primary mb-1">
{t("setupFeeNoticeHeading")}
<strong className="block text-text-primary mb-2">
{t("costSummaryHeading")}
</strong>
<div className="flex items-baseline justify-between mb-2 pb-2 border-b border-accent/20">
<span>{t("setupFeeAmountLabel")}</span>
<span className="text-sm font-semibold text-text-primary">
CHF {setupFeeChf.toFixed(2)}{" "}
<span className="text-[10px] font-normal text-text-muted">
{t("setupFeePlusVat")}
{typeof setupFeeChf === "number" && setupFeeChf > 0 && (
<div className="flex items-baseline justify-between mb-1.5">
<span>{t("costSetupLabel")}</span>
<span className="text-sm font-semibold text-text-primary">
CHF {setupFeeChf.toFixed(2)}{" "}
<span className="text-[10px] font-normal text-text-muted">
{t("setupFeePlusVat")}
</span>
</span>
</span>
</div>
)}
{typeof monthlyFeeChf === "number" && monthlyFeeChf > 0 && (
<div className="flex items-baseline justify-between mb-1.5">
<span>{t("costMonthlyLabel")}</span>
<span className="text-sm font-semibold text-text-primary">
CHF {monthlyFeeChf.toFixed(2)}{" "}
<span className="text-[10px] font-normal text-text-muted">
{t("setupFeePlusVat")}
</span>
</span>
</div>
)}
<div className="mt-2 pt-2 border-t border-accent/20 leading-relaxed">
{t("costUsageNote")}
</div>
{t("setupFeeNoticeBody")}
</div>
)}
</div>