/** * Page tarifaire `/plan` — Sprint 5b. * * 3 colonnes (Découverte / Standard / Premium). Le CTA de chaque carte dépend * du plan actuel de l'utilisateur : * - free → CTA Standard et Premium actifs (plein tarif). * - standard → Standard désactivé "Plan actuel" ; Premium = "Passer en Premium" * (sans prix affiché — Stripe calcule le prorata côté serveur). * - premium → Tous désactivés ; Premium marqué "Plan actuel". * * Le clic sur un CTA payant déclenche `createCheckoutSession(priceType)` puis * redirige le navigateur en full-page vers l'URL Stripe Checkout retournée. * * Règle D : aucun `plan === 'xxx'` exposé — la sélection du CTA passe par * une fonction `getCtaConfig(plan)` qui mappe explicitement chaque plan vers * ses CTA, sans `if/else` éparpillés. */ import { useState } from 'react' import { useMutation } from '@tanstack/react-query' import { usePlan } from '@/features/dashboard/hooks/usePlan' import { createCheckoutSession, type PriceType } from '../api' import { PlanCard, type PlanCardCta } from '../components/PlanCard' type Plan = 'free' | 'standard' | 'premium' interface PlanColumn { key: 'free' | 'standard' | 'premium' title: string price: string priceCadence?: string description: string features: string[] highlighted: boolean } const COLUMNS: PlanColumn[] = [ { key: 'free', title: 'Découverte', price: 'Gratuit', description: 'Goûter le produit, voir comment ça marche.', features: [ '5 simulations à vie', 'Score global et niveau NCLC', 'Feedback court (2-3 lignes)', 'Accès EE T1, T2, T3 et EO T1, T3', ], highlighted: false, }, { key: 'standard', title: 'Standard', price: '19,90 €', priceCadence: '/ 4 semaines', description: 'Progression sérieuse — toutes les corrections détaillées.', features: [ 'Simulations illimitées', 'Rapport détaillé par critère', 'Suggestions, exercices, production modèle', 'Historique complet et dashboard', 'Indice de préparation après 5 productions', ], highlighted: true, }, { key: 'premium', title: 'Premium', price: '39,90 €', priceCadence: '/ 4 semaines', description: 'Tout Standard, plus les outils de simulation réelle.', features: [ 'Tout le plan Standard', 'Mode Examen (60 min EE / 12 min EO)', 'EO Tâche 2 — dialogue live avec l’examinateur IA', 'Analyse des patterns sur 5 dernières productions', 'Exercices long terme personnalisés', ], highlighted: false, }, ] interface CtaConfigs { standard: { cta: PlanCardCta; hint?: string } premium: { cta: PlanCardCta; hint?: string } } function buildCtaConfigs( plan: Plan, pendingType: PriceType | null, onUpgrade: (priceType: PriceType) => void, ): CtaConfigs { const isStandardPending = pendingType === 'standard' const isPremiumPending = pendingType === 'premium' const anyPending = pendingType !== null if (plan === 'free') { return { standard: { cta: { label: 'Choisir Standard — 19,90 €/4 sem.', variant: 'primary', loading: isStandardPending, disabled: anyPending, onClick: () => onUpgrade('standard'), }, }, premium: { cta: { label: 'Choisir Premium — 39,90 €/4 sem.', variant: 'primary', loading: isPremiumPending, disabled: anyPending, onClick: () => onUpgrade('premium'), }, }, } } if (plan === 'standard') { return { standard: { cta: { label: 'Plan actuel', variant: 'secondary', disabled: true }, }, premium: { cta: { label: 'Passer en Premium', variant: 'primary', loading: isPremiumPending, disabled: anyPending, onClick: () => onUpgrade('premium'), }, hint: 'Stripe calculera automatiquement le prorata sur votre abonnement en cours.', }, } } // premium return { standard: { cta: { label: 'Inférieur à votre plan', variant: 'secondary', disabled: true }, }, premium: { cta: { label: 'Plan actuel', variant: 'secondary', disabled: true }, }, } } export function PricingPage() { const { data: planData, isLoading } = usePlan() const [pendingType, setPendingType] = useState(null) const [errorByType, setErrorByType] = useState>>({}) const mutation = useMutation({ mutationFn: createCheckoutSession, onSuccess: (data) => { // Redirection full-page vers Stripe Checkout. L'utilisateur reviendra // sur /dashboard?upgrade=success après paiement (cf. backend success_url). window.location.href = data.url }, onError: (err: Error, priceType) => { setErrorByType((prev) => ({ ...prev, [priceType]: err.message || 'Impossible de démarrer le paiement. Réessayez dans quelques instants.', })) setPendingType(null) }, }) function handleUpgrade(priceType: PriceType) { setErrorByType((prev) => ({ ...prev, [priceType]: undefined })) setPendingType(priceType) mutation.mutate(priceType) } const plan = (planData?.plan as Plan | undefined) ?? 'free' const ctaConfigs = buildCtaConfigs(plan, pendingType, handleUpgrade) return (

Tarifs

Choisissez votre plan

Toutes les offres incluent l’accès aux 5 tâches du TCF Canada (EE T1/T2/T3, EO T1/T3). Annulation libre à tout moment depuis votre espace abonnement.

{COLUMNS.map((col) => { if (col.key === 'free') { return ( ) } const config = ctaConfigs[col.key] return ( ) })}
{isLoading && (

Chargement de votre plan…

)}
) }