"use client"; import { useState, useEffect } from "react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { Check, X, Zap, Building2, Crown, Sparkles, ArrowRight, ArrowLeft, Star, Shield, Rocket, Users, Headphones, Lock, Globe, Clock, ChevronDown, ChevronUp, Cpu, BarChart3, Infinity, FileText, Layers, Brain, BadgeCheck, Gauge } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { cn } from "@/lib/utils"; import { API_BASE } from "@/lib/config"; import { ANNUAL_DISCOUNT_PERCENT } from "@/lib/pricing"; /* ───────────────────────────────────────────── Types ───────────────────────────────────────────── */ interface Plan { id: string; name: string; price_monthly: number; price_yearly: number; docs_per_month: number; max_pages_per_doc: number; max_file_size_mb: number; max_chars_per_month: number; features: string[]; providers: string[]; ai_translation: boolean; ai_tier?: string; api_access: boolean; priority_processing: boolean; team_seats?: number; popular?: boolean; highlight?: string; description?: string; badge?: string; } interface CreditPackage { credits: number; price: number; price_per_credit: number; popular?: boolean; } /* ───────────────────────────────────────────── Static plan data (fallback + SSR-friendly) ───────────────────────────────────────────── */ const STATIC_PLANS: Plan[] = [ { id: "free", name: "Gratuit", price_monthly: 0, price_yearly: 0, docs_per_month: 5, max_pages_per_doc: 15, max_file_size_mb: 5, max_chars_per_month: 50_000, providers: ["google"], features: [ "5 documents / mois", "Jusqu'à 15 pages par document", "Google Traduction inclus", "Toutes les langues (130+)", "Support communautaire", ], ai_translation: false, api_access: false, priority_processing: false, popular: false, description: "Parfait pour découvrir l'application", }, { id: "starter", name: "Starter", price_monthly: 9.00, price_yearly: 86.40, docs_per_month: 50, max_pages_per_doc: 50, max_file_size_mb: 10, max_chars_per_month: 500_000, providers: ["google", "deepl"], features: [ "50 documents / mois", "Jusqu'à 50 pages par document", "Google Traduction + DeepL", "Fichiers jusqu'à 10 Mo", "Support par e-mail", "Historique 30 jours", ], ai_translation: false, api_access: false, priority_processing: false, popular: false, description: "Pour les particuliers et petits projets", }, { id: "pro", name: "Pro", price_monthly: 19.00, price_yearly: 182.40, docs_per_month: 200, max_pages_per_doc: 200, max_file_size_mb: 25, max_chars_per_month: 2_000_000, providers: ["google", "deepl", "openrouter"], features: [ "200 documents / mois", "Jusqu'à 200 pages par document", "Traduction IA Essentielle (DeepSeek V3.2)", "Google Traduction + DeepL", "Fichiers jusqu'à 25 Mo", "Glossaires personnalisés", "Support prioritaire", "Historique 90 jours", ], ai_translation: true, ai_tier: "essential", api_access: false, priority_processing: true, popular: true, highlight: "Le plus populaire", description: "Pour les professionnels et équipes en croissance", badge: "POPULAIRE", }, { id: "business", name: "Business", price_monthly: 49.00, price_yearly: 470.40, docs_per_month: 1000, max_pages_per_doc: 500, max_file_size_mb: 50, max_chars_per_month: 10_000_000, providers: ["google", "deepl", "openrouter", "openrouter_premium", "openai"], features: [ "1 000 documents / mois", "Jusqu'à 500 pages par document", "IA Essentielle + Premium (Claude Haiku)", "Tous les fournisseurs de traduction", "Fichiers jusqu'à 50 Mo", "Accès API (10 000 appels/mois)", "Webhooks de notification", "Support dédié", "Historique 1 an", "Analytiques avancées", ], ai_translation: true, ai_tier: "premium", api_access: true, priority_processing: true, team_seats: 5, popular: false, description: "Pour les équipes et organisations", }, { id: "enterprise", name: "Entreprise", price_monthly: -1, price_yearly: -1, docs_per_month: -1, max_pages_per_doc: -1, max_file_size_mb: -1, max_chars_per_month: -1, providers: ["all"], features: [ "Documents illimités", "Tous les modèles IA (GPT-5, Claude Opus 4.6…)", "Déploiement on-premise ou cloud dédié", "SLA 99,9 % garanti", "Support 24/7 dédié", "Marque blanche (white-label)", "Équipes illimitées", "Intégrations sur mesure", ], ai_translation: true, ai_tier: "custom", api_access: true, priority_processing: true, team_seats: -1, popular: false, description: "Solutions sur mesure pour grandes organisations", badge: "SUR DEVIS", }, ]; const STATIC_CREDITS: CreditPackage[] = [ { credits: 50, price: 5, price_per_credit: 0.10 }, { credits: 150, price: 12, price_per_credit: 0.08, popular: true }, { credits: 500, price: 35, price_per_credit: 0.07 }, { credits: 1000, price: 60, price_per_credit: 0.06 }, ]; /* ───────────────────────────────────────────── Visual config by plan ───────────────────────────────────────────── */ const PLAN_ICONS: Record = { free: Sparkles, starter: Zap, pro: Crown, business: Building2, enterprise: Rocket, }; const PLAN_COLORS: Record = { free: { gradient: "from-slate-600 to-slate-700", border: "border-slate-700/50", badge: "bg-slate-700", button: "bg-slate-700 hover:bg-slate-600" }, starter: { gradient: "from-blue-600 to-blue-700", border: "border-blue-700/50", badge: "bg-blue-600", button: "bg-blue-600 hover:bg-blue-500" }, pro: { gradient: "from-violet-600 to-violet-700", border: "border-violet-500/60",badge: "bg-violet-600", button: "bg-violet-600 hover:bg-violet-500" }, business: { gradient: "from-emerald-600 to-emerald-700",border:"border-emerald-700/50",badge:"bg-emerald-600", button: "bg-emerald-600 hover:bg-emerald-500" }, enterprise: { gradient: "from-amber-600 to-amber-700", border: "border-amber-700/50", badge: "bg-amber-600", button: "bg-amber-600 hover:bg-amber-500" }, }; /** Évite le flash des tarifs statiques (STATIC_PLANS) avant la réponse API au refresh. */ function PricingDataSkeleton() { return ( <>
{Array.from({ length: 5 }).map((_, i) => (
))}
{Array.from({ length: 4 }).map((_, i) => (
))}
); } const FAQS = [ { q: "Puis-je changer de forfait à tout moment ?", a: "Oui. Le passage à un forfait supérieur est immédiat et proratisé. La rétrogradation prend effet à la fin de la période en cours.", }, { q: "Qu'est-ce que la « Traduction IA Essentielle » ?", a: "C'est notre moteur IA basé sur DeepSeek V3.2 via OpenRouter. Il comprend le contexte de vos documents, préserve la mise en page et gère les termes techniques bien mieux qu'une traduction classique.", }, { q: "Quelle est la différence entre IA Essentielle et IA Premium ?", a: "L'IA Essentielle utilise DeepSeek V3.2 (excellent rapport qualité/prix). L'IA Premium utilise Claude 3.5 Haiku d'Anthropic, plus précis sur les documents juridiques, médicaux et techniques complexes.", }, { q: "Mes documents sont-ils conservés après traduction ?", a: "Les fichiers traduits sont disponibles selon votre forfait (30 jours Starter, 90 jours Pro, 1 an Business). Ils sont chiffrés au repos et en transit.", }, { q: "Que se passe-t-il si je dépasse mon quota mensuel ?", a: "Vous pouvez acheter des crédits supplémentaires à l'unité, ou upgrader votre forfait. Vous êtes notifié à 80 % d'utilisation.", }, { q: "Y a-t-il une version d'essai gratuite des forfaits payants ?", a: "Le forfait Gratuit est permanent et sans carte bancaire. Pour les forfaits Pro et Business, contactez-nous pour un accès d'essai de 14 jours.", }, { q: "Quels formats de fichiers sont supportés ?", a: "Word (.docx), Excel (.xlsx/.xls), PowerPoint (.pptx), et bientôt PDF. Tous les plans supportent les mêmes formats.", }, ]; /* ───────────────────────────────────────────── Main component ───────────────────────────────────────────── */ export default function PricingPage() { const router = useRouter(); const searchParams = useSearchParams(); const planFromUrl = searchParams.get("plan"); const [isYearly, setIsYearly] = useState(false); const [plans, setPlans] = useState(STATIC_PLANS); const [credits, setCredits] = useState(STATIC_CREDITS); const [currentPlan, setCurrentPlan] = useState(null); const [openFAQ, setOpenFAQ] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(false); const [loadingPlanId, setLoadingPlanId] = useState(null); const [toastMsg, setToastMsg] = useState<{ type: 'ok' | 'err'; text: string } | null>(null); const [annualDiscountPercent, setAnnualDiscountPercent] = useState(ANNUAL_DISCOUNT_PERCENT); /** Tant que false : on n’affiche pas STATIC_PLANS (évite le flash de faux prix au refresh). */ const [pricingLoaded, setPricingLoaded] = useState(false); useEffect(() => { // Fetch live plans — backend returns prices set by admin (pas de cache navigateur) const plansUrl = `${API_BASE}/api/v1/auth/plans`; fetch(plansUrl, { cache: "no-store" }) .then(async (r) => { if (!r.ok) { throw new Error(`HTTP ${r.status}`); } return r.json(); }) .then((json) => { const d = json.data ?? json; const meta = (json.meta ?? d.meta) as { annual_discount_percent?: number } | undefined; if (typeof meta?.annual_discount_percent === "number") { setAnnualDiscountPercent(meta.annual_discount_percent); } if (Array.isArray(d.plans) && d.plans.length) setPlans(d.plans); if (Array.isArray(d.credit_packages)) setCredits(d.credit_packages); }) .catch((err) => { if (process.env.NODE_ENV === "development") { console.warn("[pricing] Impossible de charger /api/v1/auth/plans — affichage des tarifs par défaut.", err); } }) .finally(() => { setPricingLoaded(true); }); // Fetch current user plan (if logged in) const token = localStorage.getItem("token"); if (token) { setIsLoggedIn(true); fetch(`${API_BASE}/api/v1/auth/me`, { headers: { Authorization: `Bearer ${token}` } }) .then((r) => r.json()) .then((json) => { const user = json.data ?? json; if (user?.plan) setCurrentPlan(user.plan); }) .catch(() => {}); } }, []); // Auto-trigger checkout when ?plan= is in the URL and user is already logged in useEffect(() => { if (!pricingLoaded) return; if (planFromUrl && isLoggedIn && plans.length > 0 && currentPlan !== null) { const targetPlan = plans.find(p => p.id === planFromUrl); if (targetPlan && targetPlan.price_monthly > 0 && currentPlan !== planFromUrl) { // Directly initiate checkout — no more redirect to /settings/subscription handleSubscribe(planFromUrl); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [planFromUrl, isLoggedIn, plans, currentPlan, pricingLoaded]); const displayPrice = (plan: Plan) => { if (plan.price_monthly === -1) return null; if (plan.price_monthly === 0) return 0; const price = isYearly ? (plan.price_yearly / 12) : plan.price_monthly; return Number.isInteger(price) ? price.toString() : price.toFixed(2); }; const handleSubscribe = async (planId: string) => { const token = localStorage.getItem("token"); if (!token) { router.push(`/auth/login?redirect=/pricing`); return; } if (planId === "enterprise") { window.location.href = "mailto:contact@votre-domaine.com?subject=Offre Enterprise"; return; } if (planId === currentPlan) return; setLoadingPlanId(planId); setToastMsg(null); try { const res = await fetch(`${API_BASE}/api/v1/auth/create-checkout`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ plan: planId, billing_period: isYearly ? "yearly" : "monthly", }), }); const data = await res.json(); const url = data.data?.url ?? data.url; const backendMessage = data.message ?? data.error ?? data.data?.error; if (!res.ok) { throw new Error(backendMessage || "Erreur lors de la création du paiement."); } if (url) { window.location.replace(url); } else { // Demo mode: Stripe not yet configured setToastMsg({ type: 'ok', text: `Mode démo — Stripe n'est pas encore configuré. En production, vous seriez redirigé vers le paiement pour activer le forfait ${planId}.`, }); } } catch (error) { const message = error instanceof Error ? error.message : "Erreur réseau. Veuillez réessayer."; setToastMsg({ type: 'err', text: message }); } finally { setLoadingPlanId(null); } }; return (
{/* ── Top navigation ── */}
{isLoggedIn ? "Dashboard" : "Accueil"} {isLoggedIn && ( Mon abonnement )}
{/* ── Toast notification ── */} {toastMsg && (
{toastMsg.type === 'ok' ? '✓' : '✕'}

{toastMsg.text}

)} {/* ── Header ── */}
Modèles IA mis à jour — Mars 2026

Un forfait pour chaque besoin

Traduisez vos documents Word, Excel et PowerPoint en conservant la mise en page originale. Sans jamais saisir de clé API.

{/* Monthly / Yearly toggle */}
{/* ── Plan cards (+ comparaison + crédits) : squelette jusqu’à l’API pour éviter le flash des tarifs statiques */}
{!pricingLoaded ? ( ) : ( <>
{plans.map((plan) => { const Icon = PLAN_ICONS[plan.id] ?? Sparkles; const colors = PLAN_COLORS[plan.id] ?? PLAN_COLORS.starter; const price = displayPrice(plan); const isCurrent = currentPlan === plan.id; const isEnterprise = plan.id === "enterprise"; return (
{/* Popular badge */} {plan.badge && (
{plan.badge}
)} {isCurrent && (
Mon forfait
)} {/* Header */}
{plan.name}
{isEnterprise ? (
Sur devis
) : price === 0 ? (
Gratuit
) : (
{price} € /mois
)} {isYearly && plan.price_yearly > 0 && (
Facturé {plan.price_yearly.toFixed(2)} € / an
)}

{plan.description}

{/* Features */}
{/* Key stats */}
} label="Documents" value={plan.docs_per_month === -1 ? "Illimité" : `${plan.docs_per_month} / mois`} /> } label="Pages max" value={plan.max_pages_per_doc === -1 ? "Illimité" : `${plan.max_pages_per_doc} p / doc`} /> {plan.ai_translation && ( } label="Traduction IA" value={ plan.ai_tier === "essential" ? "Essentielle" : plan.ai_tier === "premium" ? "Essentielle + Premium" : "Sur mesure" } highlight /> )}
{plan.features.map((feat, i) => (
{feat}
))}
{/* CTA */}
{isCurrent ? ( ) : plan.id === "free" && !currentPlan ? ( ) : ( )}
); })}
{/* ── Feature comparison table ── */}

Comparaison détaillée

Tout ce qui est inclus dans chaque forfait

{plans.slice(0, 4).map((p) => ( ))} {[ { label: "Documents / mois", vals: plans.slice(0,4).map(p => p.docs_per_month === -1 ? "∞" : String(p.docs_per_month)) }, { label: "Pages max / document", vals: plans.slice(0,4).map(p => p.max_pages_per_doc === -1 ? "∞" : String(p.max_pages_per_doc)) }, { label: "Taille max fichier", vals: plans.slice(0,4).map(p => p.max_file_size_mb === -1 ? "∞" : `${p.max_file_size_mb} Mo`) }, { label: "Google Traduction", vals: plans.slice(0,4).map(() => true) }, { label: "DeepL", vals: plans.slice(0,4).map(p => p.providers.includes("deepl") || p.providers.includes("all")) }, { label: "Traduction IA Essentielle", vals: plans.slice(0,4).map(p => p.ai_translation && (p.ai_tier === "essential" || p.ai_tier === "premium" || p.ai_tier === "custom")) }, { label: "Traduction IA Premium", vals: plans.slice(0,4).map(p => p.ai_translation && (p.ai_tier === "premium" || p.ai_tier === "custom")) }, { label: "Accès API", vals: plans.slice(0,4).map(p => p.api_access) }, { label: "Traitement prioritaire", vals: plans.slice(0,4).map(p => p.priority_processing) }, { label: "Support", vals: ["Communauté", "E-mail", "Prioritaire", "Dédié"] }, ].map((row, i) => ( {row.vals.map((val, j) => ( ))} ))}
Fonctionnalité {p.name}
{row.label} {typeof val === "boolean" ? ( val ? : ) : ( {val} )}
{/* ── Credits ── */}

Crédits supplémentaires

Besoin de plus ? Achetez des crédits à l'unité, sans abonnement. 1 crédit = 1 page traduite.

{credits.map((pkg, i) => (
{pkg.popular && (
Le meilleur rapport
)}
{pkg.credits}
crédits
{pkg.price} €
{(pkg.price_per_credit * 100).toFixed(0)} cts / crédit
))}
)} {/* ── Trust signals ── */}
{[ { icon: , title: "Chiffrement de bout en bout", sub: "TLS 1.3 + AES-256 au repos" }, { icon: , title: "130+ langues", sub: "Dont arabe, persan, hébreu (RTL)" }, { icon: , title: "Traitement parallèle", sub: "IA multi-thread ultra-rapide" }, { icon: , title: "Disponible 24/7", sub: "Uptime garanti 99,9 %" }, ].map((t, i) => (
{t.icon}
{t.title}
{t.sub}
))}
{/* ── AI Models info ── */}

Nos modèles IA — Mars 2026

Traduction IA Essentielle Forfait Pro
Basée sur DeepSeek V3.2 — le modèle IA le plus rentable de 2026. Qualité comparable aux modèles frontier à 1/50ème du coût.
163K tokens de contexte $0.25/$0.38 per 1M Excellent rapport qualité/prix
Traduction IA Premium Forfait Business
Basée sur Claude 3.5 Haiku d'Anthropic — précis sur les documents juridiques, médicaux et techniques complexes.
200K tokens de contexte $0.25/$1.25 per 1M Meilleure précision
{/* ── FAQ ── */}

Questions fréquentes

{FAQS.map((faq, i) => (
{openFAQ === i && (
{faq.a}
)}
))}
{/* ── CTA bottom ── */}

Prêt à commencer ?

Commencez gratuitement, sans carte bancaire. Passez à un forfait supérieur quand vous en avez besoin.

); } /* ── Sub-components ── */ function Stat({ icon, label, value, highlight }: { icon: React.ReactNode; label: string; value: string; highlight?: boolean }) { return (
{icon} {label} : {value}
); }