fix(ui): tarifs publics unifiés et prix annuel lisible
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 7m5s
CI / Deploy production (on server) (push) Successful in 24s

La page tarifs reprend la barre du site public. En annuel, Pro affiche
8,25 € par mois (99 € l’année), plus le 99 € comme gros chiffre.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-09-01 17:43:05 +00:00
parent dcd7f38c11
commit 4fc5e3ce04
21 changed files with 721 additions and 602 deletions

View File

@@ -11,6 +11,12 @@ import { format } from 'date-fns';
import { motion } from 'motion/react';
import { BillingHistory } from './billing-history';
import { SUBSCRIPTION_TRIAL_DAYS } from '@/lib/billing/trial-constants';
import {
DEFAULT_PRICES,
annualDiscountPercent,
formatBillingAmount,
yearToMonthlyEquivalent,
} from '@/lib/billing/price-catalog';
type Tier = 'PRO' | 'BUSINESS';
type Interval = 'month' | 'year';
@@ -285,12 +291,36 @@ export function BillingPlans() {
const trialCta = (fallback: string) =>
trialEligible ? t('billing.startTrialCta', { days: trialDays }) : fallback;
const proMonth = status?.prices?.PRO?.month ?? DEFAULT_PRICES.PRO.month;
const proYear = status?.prices?.PRO?.year ?? DEFAULT_PRICES.PRO.year;
const businessMonth = status?.prices?.BUSINESS?.month ?? DEFAULT_PRICES.BUSINESS.month;
const businessYear = status?.prices?.BUSINESS?.year ?? DEFAULT_PRICES.BUSINESS.year;
const savePercent = annualDiscountPercent(proMonth.amount, proYear.amount);
const billed = (month: typeof proMonth, year: typeof proYear) => {
if (interval === 'month') {
return {
price: month.display,
period: t('billing.perMonth'),
yearHint: null as string | null,
};
}
return {
price: formatBillingAmount(yearToMonthlyEquivalent(year.amount), year.currency),
period: t('landing.pricing.perMonthAnnual'),
yearHint: t('billing.billedYearTotal', { price: year.display }),
};
};
const proBilled = billed(proMonth, proYear);
const businessBilled = billed(businessMonth, businessYear);
const plans = [
{
id: 'free',
name: t('billing.freePlan'),
price: t('billing.freePrice') || 'Gratuit',
period: '',
yearHint: null as string | null,
description: t('billing.freeDescription') || 'Pour découvrir Memento.',
features: [
t('billing.freeF1'),
@@ -313,9 +343,9 @@ export function BillingPlans() {
{
id: 'pro',
name: t('billing.proPlan'),
price: status?.prices?.PRO?.[interval]?.display ??
(interval === 'month' ? (t('billing.proPrice') || '9,90€') : (t('billing.proAnnualPrice') || '99€')),
period: interval === 'month' ? t('billing.perMonth') : t('billing.perYear'),
price: proBilled.price,
period: proBilled.period,
yearHint: proBilled.yearHint,
description: t('billing.proDescription') || 'Pour les consultants et créateurs exigeants.',
features: [
...(trialEligible ? [t('billing.trialFeature', { days: trialDays })] : []),
@@ -337,9 +367,9 @@ export function BillingPlans() {
{
id: 'business',
name: t('billing.businessPlan'),
price: status?.prices?.BUSINESS?.[interval]?.display ??
(interval === 'month' ? (t('billing.businessPrice') || '29,90€') : (t('billing.businessAnnualPrice') || '299€')),
period: interval === 'month' ? t('billing.perMonth') : t('billing.perYear'),
price: businessBilled.price,
period: businessBilled.period,
yearHint: businessBilled.yearHint,
features: [
...(trialEligible ? [t('billing.trialFeature', { days: trialDays })] : []),
t('billing.businessFeature1'),
@@ -361,6 +391,7 @@ export function BillingPlans() {
name: t('billing.enterpriseTitle') || 'Enterprise',
price: t('billing.contactSales') || 'Sur devis',
period: '',
yearHint: null as string | null,
description: t('billing.enterpriseDescription') || 'Crédits illimités ou pool dédié, connexion unique pour léquipe, support prioritaire.',
features: [
t('billing.enterpriseFeature1'),
@@ -765,7 +796,11 @@ export function BillingPlans() {
)}
>
{t('billing.annual')}
<span className="ms-1 text-primary/80 dark:text-primary">{t('billing.savePercent')}</span>
{savePercent > 0 && (
<span className="ms-1 text-primary/80 dark:text-primary">
{t('billing.savePercent', { percent: savePercent })}
</span>
)}
</button>
</div>
) : (
@@ -800,6 +835,9 @@ export function BillingPlans() {
<span className="text-4xl font-serif font-bold text-ink">{plan.price}</span>
<span className="text-concrete text-xs font-light italic">{plan.period}</span>
</div>
{plan.yearHint && (
<p className="text-xs text-concrete font-light">{plan.yearHint}</p>
)}
<p className="text-xs text-concrete font-light leading-relaxed pe-4">{plan.description}</p>
</div>