feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
131
frontend/src/components/landing/hero-word-comparison.tsx
Normal file
131
frontend/src/components/landing/hero-word-comparison.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { FileText } from "lucide-react";
|
||||
|
||||
/** Un seul exemple : même .docx, original EN à gauche, traduction FR à droite. */
|
||||
const SOURCE_DOC = {
|
||||
heading: "1. Scope and objectives",
|
||||
content: (
|
||||
<>
|
||||
<p className="mb-2 leading-relaxed">
|
||||
This document defines the <strong className="text-emerald-700 bg-emerald-50 px-1 rounded font-semibold border border-emerald-200">thermal performance requirements</strong> for the HVAC subsystem. All contractors must submit compliance certificates before commissioning.
|
||||
</p>
|
||||
<div className="my-3 border border-slate-200 rounded p-1 bg-white shadow-sm">
|
||||
<img src="/revenue_chart.png" alt="Corporate Chart" className="w-full h-auto rounded" />
|
||||
</div>
|
||||
<p className="mb-2 leading-relaxed">
|
||||
The design shall maintain indoor comfort per <span className="underline decoration-blue-400 decoration-2 font-medium">ISO 7730</span>. Deviations require written approval from the project engineer.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
const TARGET_DOC = {
|
||||
heading: "1. Périmètre et objectifs",
|
||||
content: (
|
||||
<>
|
||||
<p className="mb-2 leading-relaxed">
|
||||
Le présent document définit les <strong className="text-emerald-700 bg-emerald-50 px-1 rounded font-semibold border border-emerald-200">exigences de performance thermique</strong> pour le sous-système CVC. Toutes les entreprises doivent fournir des certificats de conformité avant la mise en service.
|
||||
</p>
|
||||
<div className="my-3 border border-slate-200 rounded p-1 bg-white shadow-sm">
|
||||
<img src="/revenue_chart.png" alt="Corporate Chart" className="w-full h-auto rounded" />
|
||||
</div>
|
||||
<p className="mb-2 leading-relaxed">
|
||||
La conception doit garantir le confort intérieur conformément à <span className="underline decoration-blue-400 decoration-2 font-medium">l'ISO 7730</span>. Toute dérogation nécessite l'accord écrit de l'ingénieur de projet.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
export function HeroWordComparison() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative w-full max-w-xl mx-auto lg:max-w-none lg:mx-0">
|
||||
<div className="relative rounded-xl bg-[#1a1a1c] border border-[#27272A] shadow-2xl overflow-hidden ring-1 ring-white/5">
|
||||
<div className="absolute -inset-px bg-gradient-to-br from-primary/20 via-transparent to-emerald-500/10 opacity-40 blur-xl pointer-events-none rounded-xl" />
|
||||
|
||||
<div className="relative flex items-center gap-2 px-3 py-2 bg-[#252526] border-b border-[#3c3c3c]">
|
||||
<div className="flex gap-1.5" aria-hidden>
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-[#ff5f57]" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-[#febc2e]" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-[#28c840]" />
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-center gap-2 min-w-0">
|
||||
<FileText className="h-4 w-4 shrink-0 text-[#2b579a]" aria-hidden />
|
||||
<span className="truncate text-xs text-slate-300 font-medium">
|
||||
{t("landing.heroDoc.fileName")}
|
||||
</span>
|
||||
<span className="hidden sm:inline text-[10px] text-slate-500 uppercase tracking-wide shrink-0">
|
||||
Word
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex items-center gap-3 px-3 py-1.5 bg-[#2d2d30] border-b border-[#3c3c3c] text-[10px] text-slate-400">
|
||||
<span className="text-white font-medium">{t("landing.heroDoc.ribbonFile")}</span>
|
||||
<span>{t("landing.heroDoc.ribbonHome")}</span>
|
||||
<span>{t("landing.heroDoc.ribbonInsert")}</span>
|
||||
<span>{t("landing.heroDoc.ribbonLayout")}</span>
|
||||
<span className="ml-auto hidden sm:flex items-center gap-1 text-slate-500" aria-hidden>
|
||||
<span className="px-1 rounded bg-[#3c3c3c] font-semibold text-slate-300">B</span>
|
||||
<span className="px-1 rounded bg-[#3c3c3c] italic text-slate-300">I</span>
|
||||
<span className="px-1 rounded bg-[#3c3c3c] underline text-slate-300">U</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative p-2 sm:p-4 bg-[#1e1e1e]">
|
||||
<p className="mb-3 text-center text-xs text-slate-400 sm:text-left sm:px-1">
|
||||
{t("landing.heroDoc.caption")}
|
||||
</p>
|
||||
<div className="flex rounded-lg overflow-hidden border border-[#3c3c3c] shadow-inner min-h-[240px] sm:min-h-[300px]">
|
||||
<div className="w-1/2 border-r border-[#c8c8c8] bg-[#f3f3f3] flex flex-col">
|
||||
<div className="shrink-0 px-2 py-1.5 bg-[#e7e7e7] border-b border-[#d0d0d0]">
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wide text-[#444] truncate block">
|
||||
{t("landing.heroDoc.badgeOriginal")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 p-3 sm:p-4 overflow-hidden bg-white text-left">
|
||||
<article
|
||||
className="text-[11px] sm:text-xs leading-relaxed text-[#242424]"
|
||||
style={{
|
||||
fontFamily:
|
||||
'Calibri, "Segoe UI", "Helvetica Neue", Arial, sans-serif',
|
||||
}}
|
||||
>
|
||||
<h3 className="text-sm sm:text-base font-bold text-[#2b579a] mb-2">
|
||||
{SOURCE_DOC.heading}
|
||||
</h3>
|
||||
{SOURCE_DOC.content}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-1/2 bg-[#f0f7ff] flex flex-col">
|
||||
<div className="shrink-0 px-2 py-1.5 bg-[#ddeaf7] border-b border-[#b4c7e7]">
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wide text-[#1a3a5c] truncate block">
|
||||
{t("landing.heroDoc.badgeTranslated")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 p-3 sm:p-4 overflow-hidden bg-white text-left">
|
||||
<article
|
||||
className="text-[11px] sm:text-xs leading-relaxed text-[#242424]"
|
||||
style={{
|
||||
fontFamily:
|
||||
'Calibri, "Segoe UI", "Helvetica Neue", Arial, sans-serif',
|
||||
}}
|
||||
>
|
||||
<h3 className="text-sm sm:text-base font-bold text-[#2b579a] mb-2">
|
||||
{TARGET_DOC.heading}
|
||||
</h3>
|
||||
{TARGET_DOC.content}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { LanguageSwitcher } from "@/components/ui/language-switcher";
|
||||
import { ThemeToggle } from "@/components/ui/theme-toggle";
|
||||
import { HeroWordComparison } from "@/components/landing/hero-word-comparison";
|
||||
import {
|
||||
Table2,
|
||||
FileText,
|
||||
@@ -20,53 +24,55 @@ import {
|
||||
|
||||
export function LandingPage() {
|
||||
const { t } = useTranslation();
|
||||
const [isYearly, setIsYearly] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0A0A0B] text-white font-sans">
|
||||
<div className="min-h-screen bg-background text-foreground font-sans">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-50 w-full border-b border-[#27272A] bg-[#0A0A0B]/80 backdrop-blur-md">
|
||||
<header className="sticky top-0 z-50 w-full border-b border-border bg-background/80 backdrop-blur-md">
|
||||
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center justify-center rounded-lg bg-primary/10 p-1.5">
|
||||
<Languages className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<span className="text-white text-lg font-bold tracking-tight">
|
||||
<span className="text-foreground text-lg font-bold tracking-tight">
|
||||
Office Translator
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
<a
|
||||
className="text-slate-300 hover:text-white text-sm font-medium transition-colors"
|
||||
className="text-muted-foreground hover:text-foreground text-sm font-medium transition-colors"
|
||||
href="#features"
|
||||
>
|
||||
{t("nav.features")}
|
||||
</a>
|
||||
<a
|
||||
className="text-slate-300 hover:text-white text-sm font-medium transition-colors"
|
||||
className="text-muted-foreground hover:text-foreground text-sm font-medium transition-colors"
|
||||
href="#pricing"
|
||||
>
|
||||
{t("nav.pricing")}
|
||||
</a>
|
||||
<a
|
||||
className="text-slate-300 hover:text-white text-sm font-medium transition-colors"
|
||||
className="text-muted-foreground hover:text-foreground text-sm font-medium transition-colors"
|
||||
href="#enterprise"
|
||||
>
|
||||
{t("nav.enterprise")}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<LanguageSwitcher variant="button" />
|
||||
<ThemeToggle />
|
||||
<a
|
||||
className="hidden sm:inline-flex h-9 items-center justify-center rounded-lg px-4 text-sm font-medium text-slate-300 transition-colors hover:text-white"
|
||||
className="hidden sm:inline-flex h-9 items-center justify-center rounded-lg px-4 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
|
||||
href="/auth/login"
|
||||
>
|
||||
{t("common.login")}
|
||||
</a>
|
||||
<Button
|
||||
asChild
|
||||
className="bg-primary text-[#0A0A0B] font-bold hover:bg-primary/90"
|
||||
className="bg-primary text-primary-foreground font-bold hover:bg-primary/90"
|
||||
>
|
||||
<Link href="/auth/register">{t("common.signup")}</Link>
|
||||
</Button>
|
||||
@@ -77,13 +83,13 @@ export function LandingPage() {
|
||||
<main>
|
||||
{/* Hero Section */}
|
||||
<section className="relative pt-20 pb-32 overflow-hidden">
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-primary/10 via-[#0A0A0B] to-[#0A0A0B]" />
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-primary/10 via-background to-background" />
|
||||
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col lg:flex-row items-center gap-12 lg:gap-20">
|
||||
{/* Hero Text */}
|
||||
<div className="flex-1 text-center lg:text-left space-y-8">
|
||||
<h1 className="text-4xl font-extrabold tracking-tight text-white sm:text-5xl lg:text-6xl xl:text-7xl">
|
||||
<h1 className="text-4xl font-extrabold tracking-tight text-foreground sm:text-5xl lg:text-6xl xl:text-7xl">
|
||||
{t("hero.title")}
|
||||
<br />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-emerald-400">
|
||||
@@ -91,7 +97,7 @@ export function LandingPage() {
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p className="mx-auto lg:mx-0 max-w-2xl text-lg text-slate-400">
|
||||
<p className="mx-auto lg:mx-0 max-w-2xl text-lg text-muted-foreground">
|
||||
{t("hero.subtitle")}
|
||||
</p>
|
||||
|
||||
@@ -99,14 +105,14 @@ export function LandingPage() {
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="bg-primary text-[#0A0A0B] font-bold hover:bg-primary/90"
|
||||
className="bg-primary text-primary-foreground font-bold hover:bg-primary/90"
|
||||
>
|
||||
<Link href="/auth/register">{t("hero.cta")}</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="border-[#27272A] bg-[#141416] text-white hover:bg-[#27272A] gap-2"
|
||||
className="gap-2"
|
||||
>
|
||||
<PlayCircle className="h-5 w-5" />
|
||||
{t("hero.demoCta")}
|
||||
@@ -115,251 +121,240 @@ export function LandingPage() {
|
||||
|
||||
{/* Trust Badges */}
|
||||
<div className="pt-8 flex flex-wrap justify-center lg:justify-start gap-6">
|
||||
<div className="flex items-center gap-2 text-sm text-slate-400">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<ShieldCheck className="h-5 w-5 text-primary" />
|
||||
<span>{t("hero.badge1")}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-slate-400">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Clock className="h-5 w-5 text-primary" />
|
||||
<span>{t("hero.badge2")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hero Visual */}
|
||||
{/* Hero Visual — Word EN | FR + illustrations */}
|
||||
<div className="flex-1 w-full max-w-lg lg:max-w-none relative">
|
||||
<div className="relative rounded-xl bg-[#141416] border border-[#27272A] p-2 shadow-2xl">
|
||||
<div className="absolute -inset-0.5 bg-gradient-to-br from-primary/30 to-transparent opacity-30 blur-lg rounded-xl" />
|
||||
<div className="relative aspect-[16/10] overflow-hidden rounded-lg bg-[#0A0A0B]">
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<div className="w-full h-full flex">
|
||||
<div className="w-1/2 border-r border-[#27272A] relative p-4">
|
||||
<div className="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded">
|
||||
{t("landing.originalLabel")} (EN)
|
||||
</div>
|
||||
<div className="mt-8 space-y-2">
|
||||
<div className="h-3 bg-slate-700 rounded w-3/4" />
|
||||
<div className="h-3 bg-slate-700 rounded w-1/2" />
|
||||
<div className="h-3 bg-slate-700 rounded w-5/6" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1/2 relative p-4">
|
||||
<div className="absolute top-2 left-2 bg-primary text-[#0A0A0B] font-bold text-xs px-2 py-1 rounded">
|
||||
{t("landing.translatedLabel")} (FR)
|
||||
</div>
|
||||
<div className="mt-8 space-y-2">
|
||||
<div className="h-3 bg-primary/30 rounded w-3/4" />
|
||||
<div className="h-3 bg-primary/30 rounded w-1/2" />
|
||||
<div className="h-3 bg-primary/30 rounded w-5/6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HeroWordComparison />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Grid */}
|
||||
<section className="py-24 bg-[#0A0A0B] relative" id="features">
|
||||
<section className="py-24 bg-background relative" id="features">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl mb-4">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl mb-4">
|
||||
{t("features.title")}
|
||||
</h2>
|
||||
<p className="text-lg text-slate-400 max-w-2xl mx-auto">
|
||||
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
|
||||
{t("features.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="flex flex-wrap justify-center gap-6">
|
||||
{/* Feature 1 - Excel */}
|
||||
<div className="group relative rounded-2xl border border-[#27272A] bg-[#141416] p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="w-full md:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)] group relative rounded-2xl border border-border bg-card p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<Table2 className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
<h3 className="text-xl font-bold text-foreground mb-2">
|
||||
{t("features.excel.title")}
|
||||
</h3>
|
||||
<p className="text-slate-400">{t("features.excel.description")}</p>
|
||||
<p className="text-muted-foreground">{t("features.excel.description")}</p>
|
||||
</div>
|
||||
|
||||
{/* Feature 2 - Word */}
|
||||
<div className="group relative rounded-2xl border border-[#27272A] bg-[#141416] p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="w-full md:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)] group relative rounded-2xl border border-border bg-card p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<FileText className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
<h3 className="text-xl font-bold text-foreground mb-2">
|
||||
{t("features.word.title")}
|
||||
</h3>
|
||||
<p className="text-slate-400">{t("features.word.description")}</p>
|
||||
<p className="text-muted-foreground">{t("features.word.description")}</p>
|
||||
</div>
|
||||
|
||||
{/* Feature 3 - PowerPoint */}
|
||||
<div className="group relative rounded-2xl border border-[#27272A] bg-[#141416] p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="w-full md:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)] group relative rounded-2xl border border-border bg-card p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<Presentation className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
<h3 className="text-xl font-bold text-foreground mb-2">
|
||||
{t("features.powerpoint.title")}
|
||||
</h3>
|
||||
<p className="text-slate-400">
|
||||
<p className="text-muted-foreground">
|
||||
{t("features.powerpoint.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Feature 4 - AI */}
|
||||
<div className="group relative rounded-2xl border border-[#27272A] bg-[#141416] p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="w-full md:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)] group relative rounded-2xl border border-border bg-card p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<Bot className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
<h3 className="text-xl font-bold text-foreground mb-2">
|
||||
{t("features.ai.title")}
|
||||
</h3>
|
||||
<p className="text-slate-400">{t("features.ai.description")}</p>
|
||||
<p className="text-muted-foreground">{t("features.ai.description")}</p>
|
||||
</div>
|
||||
|
||||
{/* Feature 5 - Privacy */}
|
||||
<div className="group relative rounded-2xl border border-[#27272A] bg-[#141416] p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<Lock className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
{t("features.privacy.title")}
|
||||
</h3>
|
||||
<p className="text-slate-400">{t("features.privacy.description")}</p>
|
||||
</div>
|
||||
|
||||
{/* Feature 6 - Speed */}
|
||||
<div className="group relative rounded-2xl border border-[#27272A] bg-[#141416] p-8 hover:border-primary/50 transition-colors">
|
||||
{/* Feature 5 - Speed */}
|
||||
<div className="w-full md:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)] group relative rounded-2xl border border-border bg-card p-8 hover:border-primary/50 transition-colors">
|
||||
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<Zap className="h-6 w-6" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
<h3 className="text-xl font-bold text-foreground mb-2">
|
||||
{t("features.speed.title")}
|
||||
</h3>
|
||||
<p className="text-slate-400">{t("features.speed.description")}</p>
|
||||
<p className="text-muted-foreground">{t("features.speed.description")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing Section */}
|
||||
<section className="py-24 bg-[#050505]" id="pricing">
|
||||
<section className="py-24 bg-muted/30" id="pricing">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl mb-4">
|
||||
<div className="text-center mb-10">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl mb-4">
|
||||
{t("pricing.title")}
|
||||
</h2>
|
||||
<p className="text-lg text-slate-400">{t("pricing.subtitle")}</p>
|
||||
<p className="text-lg text-muted-foreground">{t("pricing.subtitle")}</p>
|
||||
|
||||
<div className="flex justify-center mt-8">
|
||||
<div className="inline-flex items-center gap-3 bg-card border border-border rounded-full p-1.5">
|
||||
<button
|
||||
onClick={() => setIsYearly(false)}
|
||||
className={cn(
|
||||
"px-5 py-2 rounded-full text-sm font-medium transition-all",
|
||||
!isYearly ? "bg-primary text-primary-foreground shadow" : "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
>
|
||||
{t("common.monthly")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsYearly(true)}
|
||||
className={cn(
|
||||
"px-5 py-2 rounded-full text-sm font-medium transition-all flex items-center gap-2",
|
||||
isYearly ? "bg-primary text-primary-foreground shadow" : "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
>
|
||||
{t("common.yearly")}
|
||||
<span className={cn("text-xs px-1.5 py-0.5 rounded-full font-bold", isYearly ? "bg-background text-primary" : "bg-emerald-500/20 text-emerald-600 dark:text-emerald-400")}>
|
||||
{t("common.save20")}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
{/* Free Plan */}
|
||||
<div className="rounded-2xl border border-[#27272A] bg-[#141416] p-8 flex flex-col">
|
||||
<h3 className="text-xl font-semibold text-white">
|
||||
{t("pricing.free.name")}
|
||||
{/* Starter Plan */}
|
||||
<div className="rounded-2xl border border-border bg-card p-8 flex flex-col">
|
||||
<h3 className="text-xl font-semibold text-foreground">
|
||||
{t("pricing.starter.name")}
|
||||
</h3>
|
||||
<p className="mt-4 text-slate-400 text-sm">
|
||||
{t("pricing.free.description")}
|
||||
<p className="mt-4 text-muted-foreground text-sm">
|
||||
{t("pricing.starter.description")}
|
||||
</p>
|
||||
<div className="my-6">
|
||||
<span className="text-4xl font-bold text-white">
|
||||
{t("pricing.free.price")}
|
||||
<div className="mt-6 mb-2">
|
||||
<span className="text-4xl font-bold text-foreground">
|
||||
{isYearly ? t("pricing.starter.priceYearly") : t("pricing.starter.priceMonthly")}
|
||||
</span>
|
||||
<span className="text-slate-500">/{t("common.month")}</span>
|
||||
<span className="text-muted-foreground">/{t("common.month")}</span>
|
||||
</div>
|
||||
<div className="h-6 mb-4">
|
||||
{isYearly && (
|
||||
<span className="text-sm font-medium text-emerald-600 dark:text-emerald-400">
|
||||
{t("pricing.starter.billedYearly")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ul className="space-y-4 mb-8 flex-1">
|
||||
{["5 documents / month", "Word & Excel only", "Community support"].map(
|
||||
(feature, i) => (
|
||||
<li key={i} className="flex items-center text-slate-300 text-sm">
|
||||
<Check className="h-4 w-4 text-primary mr-2" />
|
||||
{feature}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<li key={i} className="flex items-center text-muted-foreground text-sm">
|
||||
<Check className="h-4 w-4 text-primary mr-2 shrink-0" />
|
||||
{t(`pricing.starter.features.${i}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Button
|
||||
asChild
|
||||
variant="outline"
|
||||
className="border-[#27272A] text-white hover:bg-[#27272A]"
|
||||
>
|
||||
<Link href="/auth/register">{t("common.signup")}</Link>
|
||||
<Button asChild variant="outline">
|
||||
<Link href="/pricing?plan=starter">{t("common.signup")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Pro Plan */}
|
||||
<div className="relative rounded-2xl border border-primary bg-[#141416] p-8 flex flex-col shadow-lg shadow-primary/10 scale-105 z-10">
|
||||
<div className="absolute -top-4 left-1/2 -translate-x-1/2 rounded-full bg-[#F59E0B] px-3 py-1 text-xs font-bold text-black uppercase tracking-wide">
|
||||
<div className="relative rounded-2xl border border-primary bg-card p-8 flex flex-col shadow-lg shadow-primary/10 scale-105 z-10">
|
||||
<div className="absolute -top-4 left-1/2 -translate-x-1/2 rounded-full bg-warning px-3 py-1 text-xs font-bold text-warning-foreground uppercase tracking-wide">
|
||||
{t("common.popular")}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white">
|
||||
<h3 className="text-xl font-semibold text-foreground">
|
||||
{t("pricing.pro.name")}
|
||||
</h3>
|
||||
<p className="mt-4 text-slate-400 text-sm">
|
||||
<p className="mt-4 text-muted-foreground text-sm">
|
||||
{t("pricing.pro.description")}
|
||||
</p>
|
||||
<div className="my-6">
|
||||
<span className="text-4xl font-bold text-white">
|
||||
{t("pricing.pro.price")}
|
||||
<div className="mt-6 mb-2">
|
||||
<span className="text-4xl font-bold text-foreground">
|
||||
{isYearly ? t("pricing.pro.priceYearly") : t("pricing.pro.priceMonthly")}
|
||||
</span>
|
||||
<span className="text-slate-500">/{t("common.month")}</span>
|
||||
<span className="text-muted-foreground">/{t("common.month")}</span>
|
||||
</div>
|
||||
<div className="h-6 mb-4">
|
||||
{isYearly && (
|
||||
<span className="text-sm font-medium text-emerald-600 dark:text-emerald-400">
|
||||
{t("pricing.pro.billedYearly")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ul className="space-y-4 mb-8 flex-1">
|
||||
{[
|
||||
"50 documents / month",
|
||||
"All formats (PPTX included)",
|
||||
"Advanced AI models (GPT-4)",
|
||||
"Priority support",
|
||||
].map((feature, i) => (
|
||||
<li key={i} className="flex items-center text-white text-sm">
|
||||
<Check className="h-4 w-4 text-primary mr-2" />
|
||||
{feature}
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<li key={i} className="flex items-center text-foreground text-sm">
|
||||
<Check className="h-4 w-4 text-primary mr-2 shrink-0" />
|
||||
{t(`pricing.pro.features.${i}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Button
|
||||
asChild
|
||||
className="bg-primary text-[#0A0A0B] font-bold hover:bg-primary/90"
|
||||
className="bg-primary text-primary-foreground font-bold hover:bg-primary/90"
|
||||
>
|
||||
<Link href="/auth/register">{t("common.tryPro")}</Link>
|
||||
<Link href="/pricing?plan=pro">{t("common.tryPro")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Enterprise Plan */}
|
||||
<div className="rounded-2xl border border-[#27272A] bg-[#141416] p-8 flex flex-col">
|
||||
<h3 className="text-xl font-semibold text-white">
|
||||
{t("pricing.enterprise.name")}
|
||||
{/* Business Plan */}
|
||||
<div className="rounded-2xl border border-border bg-card p-8 flex flex-col">
|
||||
<h3 className="text-xl font-semibold text-foreground">
|
||||
{t("pricing.business.name")}
|
||||
</h3>
|
||||
<p className="mt-4 text-slate-400 text-sm">
|
||||
{t("pricing.enterprise.description")}
|
||||
<p className="mt-4 text-muted-foreground text-sm">
|
||||
{t("pricing.business.description")}
|
||||
</p>
|
||||
<div className="my-6">
|
||||
<span className="text-4xl font-bold text-white">
|
||||
{t("common.onRequest")}
|
||||
<div className="mt-6 mb-2">
|
||||
<span className="text-4xl font-bold text-foreground">
|
||||
{isYearly ? t("pricing.business.priceYearly") : t("pricing.business.priceMonthly")}
|
||||
</span>
|
||||
<span className="text-muted-foreground">/{t("common.month")}</span>
|
||||
</div>
|
||||
<div className="h-6 mb-4">
|
||||
{isYearly && (
|
||||
<span className="text-sm font-medium text-emerald-600 dark:text-emerald-400">
|
||||
{t("pricing.business.billedYearly")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ul className="space-y-4 mb-8 flex-1">
|
||||
{[
|
||||
"Unlimited documents",
|
||||
"API Access",
|
||||
"SSO & Advanced Security",
|
||||
"Dedicated Account Manager",
|
||||
].map((feature, i) => (
|
||||
<li key={i} className="flex items-center text-slate-300 text-sm">
|
||||
<Check className="h-4 w-4 text-primary mr-2" />
|
||||
{feature}
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<li key={i} className="flex items-center text-muted-foreground text-sm">
|
||||
<Check className="h-4 w-4 text-primary mr-2 shrink-0" />
|
||||
{t(`pricing.business.features.${i}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Button
|
||||
asChild
|
||||
variant="outline"
|
||||
className="border-[#27272A] text-white hover:bg-[#27272A]"
|
||||
>
|
||||
<Link href="#contact">{t("common.contactSales")}</Link>
|
||||
<Button asChild variant="outline">
|
||||
<Link href="/pricing?plan=business">{t("common.tryPro")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -367,20 +362,20 @@ export function LandingPage() {
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="relative py-20 px-4">
|
||||
<section className="relative py-20 px-4 bg-background">
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-primary/5 to-transparent pointer-events-none" />
|
||||
<div className="mx-auto max-w-4xl rounded-3xl bg-[#141416] border border-[#27272A] p-12 text-center shadow-2xl overflow-hidden relative">
|
||||
<div className="mx-auto max-w-4xl rounded-3xl bg-card border border-border p-12 text-center shadow-2xl overflow-hidden relative">
|
||||
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-primary to-transparent opacity-50" />
|
||||
<h2 className="text-3xl font-bold text-white mb-6">
|
||||
<h2 className="text-3xl font-bold text-foreground mb-6">
|
||||
{t("cta.title")}
|
||||
</h2>
|
||||
<p className="text-slate-400 mb-8 max-w-2xl mx-auto">
|
||||
<p className="text-muted-foreground mb-8 max-w-2xl mx-auto">
|
||||
{t("cta.subtitle")}
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="bg-primary text-[#0A0A0B] font-bold hover:bg-primary/90"
|
||||
className="bg-primary text-primary-foreground font-bold hover:bg-primary/90"
|
||||
>
|
||||
<Link href="/auth/register">{t("cta.button")}</Link>
|
||||
</Button>
|
||||
@@ -389,29 +384,29 @@ export function LandingPage() {
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-[#27272A] bg-[#0A0A0B] py-12">
|
||||
<footer className="border-t border-border bg-background py-12">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center justify-center rounded-lg bg-primary/10 p-1">
|
||||
<Languages className="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
<span className="text-white font-bold text-lg">
|
||||
<span className="text-foreground font-bold text-lg">
|
||||
Office Translator
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-6 text-sm text-slate-400">
|
||||
<a className="hover:text-white transition-colors" href="#">
|
||||
<div className="flex gap-6 text-sm text-muted-foreground">
|
||||
<a className="hover:text-foreground transition-colors" href="#">
|
||||
{t("footer.privacy")}
|
||||
</a>
|
||||
<a className="hover:text-white transition-colors" href="#">
|
||||
<a className="hover:text-foreground transition-colors" href="#">
|
||||
{t("footer.terms")}
|
||||
</a>
|
||||
<a className="hover:text-white transition-colors" href="#">
|
||||
<a className="hover:text-foreground transition-colors" href="#">
|
||||
{t("footer.contact")}
|
||||
</a>
|
||||
</div>
|
||||
<div className="text-slate-500 text-sm">{t("footer.copyright")}</div>
|
||||
<div className="text-muted-foreground text-sm">{t("footer.copyright")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
43
frontend/src/components/ui/theme-toggle.tsx
Normal file
43
frontend/src/components/ui/theme-toggle.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type ThemeToggleProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function ThemeToggle({ className }: ThemeToggleProps) {
|
||||
const { theme, setTheme, resolvedTheme } = useTheme();
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const isDark = mounted && (resolvedTheme === "dark" || theme === "dark");
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn("size-9 shrink-0 text-foreground", className)}
|
||||
aria-label={isDark ? "Passer en mode clair" : "Passer en mode sombre"}
|
||||
onClick={() => setTheme(isDark ? "light" : "dark")}
|
||||
>
|
||||
{mounted ? (
|
||||
isDark ? (
|
||||
<Sun className="size-4" aria-hidden />
|
||||
) : (
|
||||
<Moon className="size-4" aria-hidden />
|
||||
)
|
||||
) : (
|
||||
<span className="size-4 inline-block" aria-hidden />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user