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

@@ -6,11 +6,20 @@ import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useLanguage } from '@/lib/i18n'
import { SUBSCRIPTION_TRIAL_DAYS } from '@/lib/billing/trial-constants'
import { PublicSiteChrome } from '@/components/public-site-chrome'
import {
DEFAULT_PRICES,
annualDiscountPercent,
} from '@/lib/billing/price-catalog'
export default function PricingPage() {
const { t } = useLanguage()
const [billingInterval, setBillingInterval] = useState<'monthly' | 'annual'>('monthly')
const trialDays = SUBSCRIPTION_TRIAL_DAYS
const annualSavePercent = annualDiscountPercent(
DEFAULT_PRICES.PRO.month.amount,
DEFAULT_PRICES.PRO.year.amount,
)
const { data: byokCatalog } = useQuery({
queryKey: ['public', 'byok-catalog'],
queryFn: async () => {
@@ -48,51 +57,28 @@ export default function PricingPage() {
]
return (
<main className="min-h-screen bg-[#0B0A09] text-[#F4F1EA] font-[family-name:var(--font-manrope)] selection:bg-[#D4A373]/40 selection:text-white">
<nav className="sticky top-0 z-[100] px-5 sm:px-8 py-4 flex items-center justify-between bg-[#0B0A09]/70 backdrop-blur-xl border-b border-white/[0.06]">
<Link href="/" className="flex items-center gap-2.5 group">
<div className="w-9 h-9 bg-[#F4F1EA] text-[#0B0A09] flex items-center justify-center rounded-lg">
<span className="font-serif text-xl font-bold leading-none">M</span>
</div>
<span className="font-serif text-xl font-medium tracking-tight">Memento</span>
</Link>
<div className="flex items-center gap-2 sm:gap-3">
<Link href="/login" className="text-[13px] text-white/75 hover:text-white transition-colors px-2">
{t('landing.nav.login')}
</Link>
<Link
href="/register"
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full bg-[#F4F1EA] text-[#0B0A09] text-[13px] font-semibold hover:bg-white transition-colors"
>
{t('landing.nav.cta')}
</Link>
</div>
</nav>
<section className="px-5 sm:px-8 py-28">
<PublicSiteChrome currentPage="pricing">
<section className="px-5 sm:px-8 py-20 sm:py-28">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-12">
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-[#D4A373] mb-4 block">
{t('landing.pricing.label')}
</span>
<h1 className="font-serif text-3xl sm:text-5xl tracking-tight mb-4">{t('landing.pricing.title')}</h1>
<p className="text-white/70 mb-8">{t('landing.pricing.desc')}</p>
<div className="inline-flex p-1 rounded-full border border-white/10 bg-white/[0.03]">
<p className="text-white/80 mb-8">{t('landing.pricing.desc')}</p>
<div className="inline-flex p-1 rounded-full border border-white/15 bg-white/[0.04]">
<button
type="button"
onClick={() => setBillingInterval('monthly')}
className={`px-5 py-2 rounded-full text-[12px] font-semibold transition-all ${billingInterval === 'monthly' ? 'bg-[#F4F1EA] text-[#0B0A09]' : 'text-white/70'}`}
className={`px-5 py-2 rounded-full text-[13px] font-semibold transition-all ${billingInterval === 'monthly' ? 'bg-[#F4F1EA] text-[#0B0A09]' : 'text-white/80'}`}
>
{t('landing.pricing.monthly')}
</button>
<button
type="button"
onClick={() => setBillingInterval('annual')}
className={`px-5 py-2 rounded-full text-[12px] font-semibold transition-all relative ${billingInterval === 'annual' ? 'bg-[#F4F1EA] text-[#0B0A09]' : 'text-white/70'}`}
className={`px-5 py-2 rounded-full text-[13px] font-semibold transition-all relative ${billingInterval === 'annual' ? 'bg-[#F4F1EA] text-[#0B0A09]' : 'text-white/80'}`}
>
{t('landing.pricing.annual')}
<span className="absolute -top-3 -right-1 text-[10px] text-[#D4A373] whitespace-nowrap">
{t('landing.pricing.savePercent')}
<span className="absolute -top-3 -right-1 text-[11px] text-[#E8C39A] whitespace-nowrap">
{t('landing.pricing.savePercent', { percent: annualSavePercent })}
</span>
</button>
</div>
@@ -103,32 +89,32 @@ export default function PricingPage() {
key={plan.key}
className={`rounded-2xl border p-6 flex flex-col ${
plan.popular
? 'border-[#D4A373]/50 bg-[#D4A373]/10'
: 'border-white/[0.08] bg-white/[0.02]'
? 'border-[#D4A373]/60 bg-[#D4A373]/12'
: 'border-white/[0.12] bg-white/[0.04]'
}`}
>
{plan.popular && (
<span className="text-[10px] font-bold uppercase tracking-widest text-[#D4A373] mb-3">
<span className="text-[11px] font-bold uppercase tracking-widest text-[#E8C39A] mb-3">
{t('landing.pricing.popular')}
</span>
)}
<h2 className="text-[13px] font-medium tracking-wide text-white/80 mb-2">
<h2 className="text-[14px] font-medium tracking-wide text-[#F4F1EA] mb-2">
{t(`landing.pricing.${plan.key}.name`)}
</h2>
<div className="flex items-baseline gap-1 mb-2">
<span className="text-3xl font-serif">{plan.price}</span>
{plan.period && <span className="text-sm text-white/70">{plan.period}</span>}
{plan.period && <span className="text-sm text-white/80">{plan.period}</span>}
</div>
{plan.hasTrial && (
<p className="text-[11px] font-semibold text-[#D4A373] mb-3">
<p className="text-[12px] font-semibold text-[#E8C39A] mb-3">
{t('landing.pricing.trialBadge', { days: trialDays })}
</p>
)}
<p className="text-sm text-white/70 mb-6">{t(`landing.pricing.${plan.key}.desc`)}</p>
<p className="text-sm text-white/80 mb-6">{t(`landing.pricing.${plan.key}.desc`)}</p>
<ul className="space-y-2.5 mb-8 flex-1">
{plan.hasTrial && (
<li className="flex gap-2 text-xs text-[#D4A373]/90">
<Check size={12} className="text-[#D4A373] mt-0.5 shrink-0" />
<li className="flex gap-2 text-xs text-[#E8C39A]">
<Check size={12} className="text-[#E8C39A] mt-0.5 shrink-0" />
{t('landing.pricing.trialFeature', { days: trialDays })}
</li>
)}
@@ -136,8 +122,8 @@ export default function PricingPage() {
const feat = t(`landing.pricing.${plan.key}.feature${j}`, { count: providerCount })
if (!feat || feat.startsWith('landing.')) return null
return (
<li key={j} className="flex gap-2 text-sm text-white/80">
<Check size={12} className="text-[#D4A373] mt-0.5 shrink-0" />
<li key={j} className="flex gap-2 text-sm text-[#F4F1EA]/90">
<Check size={12} className="text-[#E8C39A] mt-0.5 shrink-0" />
{feat}
</li>
)
@@ -148,7 +134,7 @@ export default function PricingPage() {
className={`py-3 rounded-xl text-center text-[13px] font-semibold transition-colors ${
plan.popular
? 'bg-[#F4F1EA] text-[#0B0A09] hover:bg-white'
: 'bg-white/10 text-white hover:bg-white/15'
: 'bg-white/15 text-white hover:bg-white/20'
}`}
>
{plan.hasTrial
@@ -160,6 +146,6 @@ export default function PricingPage() {
</div>
</div>
</section>
</main>
</PublicSiteChrome>
)
}