feat(frontend): socle de refonte au Système de Design de l'État (DSFR)
- charge le DSFR (CSS/JS officiels) en statique via public/dsfr + postinstall - en-tête officiel (bloc Marianne « République Française » + nom de service) - pied de page officiel DSFR (liens institutionnels, licence etalab) - page d'accueil refondue (hero, fr-card, fr-tile, fr-callout, fr-btn) - composant Button mappé sur fr-btn - assets DSFR hors git (régénérés par `npm install`) Build OK (19 routes). Pages internes encore en Tailwind : conversion à poursuivre. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,3 +40,6 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
# Assets DSFR copiés par le postinstall (régénérés depuis node_modules)
|
||||||
|
/public/dsfr
|
||||||
|
|||||||
Generated
+39
@@ -8,6 +8,7 @@
|
|||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codegouvfr/react-dsfr": "^1.32.4",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
@@ -370,6 +371,29 @@
|
|||||||
"specificity": "bin/cli.js"
|
"specificity": "bin/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codegouvfr/react-dsfr": {
|
||||||
|
"version": "1.32.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codegouvfr/react-dsfr/-/react-dsfr-1.32.4.tgz",
|
||||||
|
"integrity": "sha512-yhjPqPObB3BsehZTRhJZ9qXwBRT2RZl0cn6hG838MlkPntTE5H5w0vU05KmKcdDBnQ0wDV59G7nnZrpKZQlNuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tsafe": "^1.8.5",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"copy-dsfr-to-public": "bin/copy-dsfr-to-public.js",
|
||||||
|
"only-include-used-icons": "bin/only-include-used-icons.js",
|
||||||
|
"react-dsfr": "bin/react-dsfr.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@gouvfr/dsfr-chart": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@gouvfr/dsfr-chart": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@csstools/color-helpers": {
|
"node_modules/@csstools/color-helpers": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
|
||||||
@@ -8001,6 +8025,12 @@
|
|||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tsafe": {
|
||||||
|
"version": "1.8.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsafe/-/tsafe-1.8.12.tgz",
|
||||||
|
"integrity": "sha512-nFRqW0ttu/2o6XTXsHiVZWJBCOaxhVqZLg7dgs3coZNsCMPXPfwz+zPHAQA+70fNnVJLAPg1EgGIqK9Q84tvAw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tsconfig-paths": {
|
"node_modules/tsconfig-paths": {
|
||||||
"version": "3.15.0",
|
"version": "3.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
||||||
@@ -8964,6 +8994,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"test": "vitest run"
|
"test": "vitest run",
|
||||||
|
"postinstall": "node scripts/copy-dsfr.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codegouvfr/react-dsfr": "^1.32.4",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copie les assets du Système de Design de l'État (DSFR) vers public/dsfr,
|
||||||
|
// pour les servir en statique (CSS, JS, polices Marianne, icônes).
|
||||||
|
// Lancé automatiquement après `npm install` (script postinstall).
|
||||||
|
import { cpSync, rmSync, existsSync } from "node:fs";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
const src = join(root, "node_modules", "@codegouvfr", "react-dsfr", "dsfr");
|
||||||
|
const dest = join(root, "public", "dsfr");
|
||||||
|
|
||||||
|
if (!existsSync(src)) {
|
||||||
|
console.warn("[copy-dsfr] Source DSFR introuvable, étape ignorée.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
rmSync(dest, { recursive: true, force: true });
|
||||||
|
cpSync(src, dest, { recursive: true });
|
||||||
|
console.log("[copy-dsfr] Assets DSFR copiés vers public/dsfr.");
|
||||||
+20
-19
@@ -1,26 +1,17 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter, Poppins } from "next/font/google";
|
import Script from "next/script";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import Providers from "@/components/Providers";
|
import Providers from "@/components/Providers";
|
||||||
|
|
||||||
const inter = Inter({
|
|
||||||
variable: "--font-inter",
|
|
||||||
subsets: ["latin"],
|
|
||||||
weight: ["400", "500", "600", "700"],
|
|
||||||
display: "swap",
|
|
||||||
});
|
|
||||||
|
|
||||||
const poppins = Poppins({
|
|
||||||
variable: "--font-poppins",
|
|
||||||
subsets: ["latin"],
|
|
||||||
weight: ["500", "600", "700"],
|
|
||||||
display: "swap",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "CESIZen - Votre sant\u00e9 mentale",
|
title: "CESIZen - Votre santé mentale",
|
||||||
description:
|
description:
|
||||||
"Plateforme de gestion du stress et suivi \u00e9motionnel. Suivez vos \u00e9motions, consultez des ressources et prenez soin de votre sant\u00e9 mentale.",
|
"Plateforme publique de gestion du stress et de suivi émotionnel. Suivez vos émotions, consultez des ressources et prenez soin de votre santé mentale.",
|
||||||
|
icons: {
|
||||||
|
icon: [{ url: "/dsfr/favicon/favicon.svg", type: "image/svg+xml" }],
|
||||||
|
shortcut: ["/dsfr/favicon/favicon.ico"],
|
||||||
|
apple: [{ url: "/dsfr/favicon/apple-touch-icon.png" }],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -29,9 +20,19 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="fr" suppressHydrationWarning>
|
<html lang="fr" data-fr-scheme="system" suppressHydrationWarning>
|
||||||
<body className={`${inter.variable} ${poppins.variable} antialiased`}>
|
<body>
|
||||||
|
{/* Système de Design de l'État (DSFR) — feuilles de style officielles */}
|
||||||
|
<link rel="stylesheet" href="/dsfr/dsfr.min.css" />
|
||||||
|
<link rel="stylesheet" href="/dsfr/utility/utility.min.css" />
|
||||||
<Providers>{children}</Providers>
|
<Providers>{children}</Providers>
|
||||||
|
{/* JS DSFR : comportements interactifs (menu, navigation, modales) */}
|
||||||
|
<Script src="/dsfr/dsfr.module.min.js" type="module" strategy="afterInteractive" />
|
||||||
|
<Script
|
||||||
|
src="/dsfr/dsfr.nomodule.min.js"
|
||||||
|
noModule
|
||||||
|
strategy="afterInteractive"
|
||||||
|
/>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
+156
-546
@@ -1,592 +1,202 @@
|
|||||||
'use client';
|
import Link from "next/link";
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import {
|
|
||||||
BookOpen,
|
|
||||||
Heart,
|
|
||||||
BarChart3,
|
|
||||||
Shield,
|
|
||||||
ArrowRight,
|
|
||||||
Sparkles,
|
|
||||||
Brain,
|
|
||||||
TrendingUp,
|
|
||||||
Users,
|
|
||||||
CheckCircle2,
|
|
||||||
Star,
|
|
||||||
Zap,
|
|
||||||
Clock,
|
|
||||||
SmilePlus,
|
|
||||||
} from 'lucide-react';
|
|
||||||
|
|
||||||
// Hook d'animation au scroll (Intersection Observer)
|
|
||||||
function useScrollReveal() {
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
const el = ref.current;
|
|
||||||
if (!el) return;
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
el.classList.add('animate-in');
|
|
||||||
observer.unobserve(el);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ threshold: 0.15 }
|
|
||||||
);
|
|
||||||
observer.observe(el);
|
|
||||||
return () => observer.disconnect();
|
|
||||||
}, []);
|
|
||||||
return ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
function RevealSection({ children, className = '', delay = 0 }: { children: React.ReactNode; className?: string; delay?: number }) {
|
|
||||||
const ref = useScrollReveal();
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
className={`reveal-section ${className}`}
|
|
||||||
style={{ transitionDelay: `${delay}ms` }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen overflow-hidden">
|
<>
|
||||||
<style jsx global>{`
|
{/* ─── Hero ─── */}
|
||||||
.reveal-section {
|
<section className="fr-container fr-py-8w">
|
||||||
opacity: 0;
|
<div className="fr-grid-row fr-grid-row--gutters fr-grid-row--middle">
|
||||||
transform: translateY(40px);
|
<div className="fr-col-12 fr-col-md-7">
|
||||||
transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1), transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
|
<h1>
|
||||||
}
|
CESIZen, prenez soin de votre santé mentale
|
||||||
.reveal-section.animate-in {
|
</h1>
|
||||||
opacity: 1;
|
<p className="fr-text--lead">
|
||||||
transform: translateY(0);
|
Une plateforme publique pour suivre vos émotions au quotidien,
|
||||||
}
|
comprendre vos tendances et accéder à des ressources fiables sur la
|
||||||
@keyframes float {
|
prévention du stress.
|
||||||
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
</p>
|
||||||
50% { transform: translateY(-20px) rotate(2deg); }
|
<ul className="fr-btns-group fr-btns-group--inline-md">
|
||||||
}
|
<li>
|
||||||
@keyframes float-delay {
|
<Link className="fr-btn fr-btn--lg" href="/inscription">
|
||||||
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
Créer un compte gratuitement
|
||||||
50% { transform: translateY(-15px) rotate(-2deg); }
|
|
||||||
}
|
|
||||||
@keyframes gradient-shift {
|
|
||||||
0%, 100% { background-position: 0% 50%; }
|
|
||||||
50% { background-position: 100% 50%; }
|
|
||||||
}
|
|
||||||
@keyframes pulse-ring {
|
|
||||||
0% { transform: scale(0.8); opacity: 0.5; }
|
|
||||||
50% { transform: scale(1.1); opacity: 0; }
|
|
||||||
100% { transform: scale(0.8); opacity: 0; }
|
|
||||||
}
|
|
||||||
@keyframes count-up {
|
|
||||||
from { opacity: 0; transform: translateY(10px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
|
||||||
.float-anim { animation: float 6s ease-in-out infinite; }
|
|
||||||
.float-anim-delay { animation: float-delay 7s ease-in-out infinite; }
|
|
||||||
.gradient-text {
|
|
||||||
background: linear-gradient(135deg, #06c656, #fce117, #06c656);
|
|
||||||
background-size: 200% 200%;
|
|
||||||
animation: gradient-shift 4s ease infinite;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
|
|
||||||
{/* ─── Hero Section ─── */}
|
|
||||||
<section className="relative overflow-hidden bg-linear-to-br from-candlelight-50 via-white to-malachite-50 dark:from-gray-950 dark:via-gray-900 dark:to-gray-950 min-h-[90vh] flex items-center">
|
|
||||||
{/* Orbes décoratives */}
|
|
||||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
||||||
<div className="absolute -top-40 -right-40 w-125 h-125 bg-candlelight-200/40 dark:bg-candlelight-500/10 rounded-full blur-[100px] float-anim" />
|
|
||||||
<div className="absolute -bottom-40 -left-40 w-125 h-125 bg-malachite-200/40 dark:bg-malachite-500/10 rounded-full blur-[100px] float-anim-delay" />
|
|
||||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-150 h-150 bg-candlelight-100/20 dark:bg-candlelight-500/5 rounded-full blur-[120px]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Grille de fond */}
|
|
||||||
<div className="absolute inset-0 bg-[linear-gradient(rgba(0,0,0,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(0,0,0,0.03)_1px,transparent_1px)] dark:bg-[linear-gradient(rgba(255,255,255,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.03)_1px,transparent_1px)] bg-size-[60px_60px] pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="relative max-w-6xl mx-auto px-4 py-20 sm:py-32 w-full">
|
|
||||||
<div className="text-center">
|
|
||||||
<RevealSection>
|
|
||||||
<div className="inline-flex items-center gap-2 bg-white/70 dark:bg-gray-800/70 backdrop-blur-md rounded-full px-5 py-2.5 mb-8 border border-gray-200/80 dark:border-gray-700/80 shadow-sm">
|
|
||||||
<Sparkles className="w-4 h-4 text-candlelight-500" />
|
|
||||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
|
|
||||||
Plateforme du Ministère des Solidarités et de la Santé
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<RevealSection delay={100}>
|
|
||||||
<h1 className="font-display text-6xl sm:text-7xl lg:text-8xl font-bold tracking-tight mb-6">
|
|
||||||
<span className="gradient-text">CESI</span>
|
|
||||||
<span className="text-gray-900 dark:text-white">ZEN</span>
|
|
||||||
</h1>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<RevealSection delay={200}>
|
|
||||||
<p className="text-xl sm:text-2xl lg:text-3xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto mb-4 font-light">
|
|
||||||
Votre compagnon pour une{' '}
|
|
||||||
<span className="font-semibold text-malachite-600 dark:text-malachite-400">santé mentale</span>{' '}
|
|
||||||
épanouie
|
|
||||||
</p>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<RevealSection delay={300}>
|
|
||||||
<p className="text-base sm:text-lg text-gray-500 dark:text-gray-400 max-w-xl mx-auto mb-12 leading-relaxed">
|
|
||||||
Suivez vos émotions, comprenez vos tendances et prenez soin de vous
|
|
||||||
au quotidien avec des outils simples, bienveillants et confidentiels.
|
|
||||||
</p>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<RevealSection delay={400}>
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-16">
|
|
||||||
<Link
|
|
||||||
href="/inscription"
|
|
||||||
className="group inline-flex items-center justify-center gap-2 bg-candlelight-500 hover:bg-candlelight-400 text-black font-semibold px-8 py-4 rounded-2xl transition-all duration-300 hover:shadow-xl hover:shadow-candlelight-500/30 hover:-translate-y-0.5 text-lg"
|
|
||||||
>
|
|
||||||
Commencer gratuitement
|
|
||||||
<ArrowRight className="w-5 h-5 transition-transform duration-300 group-hover:translate-x-1" />
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
<Link
|
<Link
|
||||||
|
className="fr-btn fr-btn--lg fr-btn--secondary"
|
||||||
href="/informations"
|
href="/informations"
|
||||||
className="inline-flex items-center justify-center gap-2 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm hover:bg-white dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200 font-semibold px-8 py-4 rounded-2xl border-2 border-gray-200 dark:border-gray-700 transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5 text-lg"
|
|
||||||
>
|
>
|
||||||
<BookOpen className="w-5 h-5" />
|
Consulter les ressources
|
||||||
En savoir plus
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</li>
|
||||||
</RevealSection>
|
</ul>
|
||||||
|
</div>
|
||||||
{/* Mini stats */}
|
<div className="fr-col-12 fr-col-md-5">
|
||||||
<RevealSection delay={500}>
|
<div className="fr-callout">
|
||||||
<div className="flex flex-wrap justify-center gap-8 sm:gap-12">
|
<p className="fr-callout__title">Pourquoi suivre ses émotions ?</p>
|
||||||
<MiniStat icon={<Users className="w-5 h-5" />} value="100%" label="Gratuit" />
|
<p className="fr-callout__text">
|
||||||
<MiniStat icon={<Shield className="w-5 h-5" />} value="RGPD" label="Conforme" />
|
Nommer une émotion réduit son intensité (affect labeling, UCLA).
|
||||||
<MiniStat icon={<Clock className="w-5 h-5" />} value="2 min" label="Pour commencer" />
|
Le suivi régulier aide à identifier ses tendances et à mieux gérer
|
||||||
</div>
|
son stress.
|
||||||
</RevealSection>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Séparateur vague */}
|
|
||||||
<div className="absolute bottom-0 left-0 right-0">
|
|
||||||
<svg viewBox="0 0 1440 80" fill="none" className="w-full h-auto" preserveAspectRatio="none">
|
|
||||||
<path d="M0 80L60 68C120 56 240 32 360 24C480 16 600 24 720 32C840 40 960 48 1080 44C1200 40 1320 24 1380 16L1440 8V80H1380C1320 80 1200 80 1080 80C960 80 840 80 720 80C600 80 480 80 360 80C240 80 120 80 60 80H0Z" className="fill-white dark:fill-gray-950" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ─── Comment ça marche ─── */}
|
{/* ─── Comment ça marche ─── */}
|
||||||
<section className="py-24 bg-white dark:bg-gray-950">
|
<section className="fr-background-alt--grey fr-py-8w">
|
||||||
<div className="max-w-6xl mx-auto px-4">
|
<div className="fr-container">
|
||||||
<RevealSection>
|
<h2>Comment ça marche ?</h2>
|
||||||
<div className="text-center mb-20">
|
<p className="fr-text--lead fr-mb-6w">
|
||||||
<span className="inline-flex items-center gap-2 text-malachite-600 dark:text-malachite-400 font-semibold text-sm uppercase tracking-wider mb-4">
|
Trois étapes simples pour prendre soin de votre bien-être émotionnel.
|
||||||
<Zap className="w-4 h-4" />
|
</p>
|
||||||
Simple et rapide
|
<div className="fr-grid-row fr-grid-row--gutters">
|
||||||
</span>
|
|
||||||
<h2 className="font-display text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white mb-4">
|
|
||||||
Comment ça marche ?
|
|
||||||
</h2>
|
|
||||||
<p className="text-lg text-gray-500 dark:text-gray-400 max-w-2xl mx-auto">
|
|
||||||
Trois étapes simples pour prendre soin de votre bien-être émotionnel.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-8 lg:gap-12 relative">
|
|
||||||
{/* Ligne de connexion */}
|
|
||||||
<div className="hidden md:block absolute top-24 left-[20%] right-[20%] h-0.5 bg-linear-to-r from-candlelight-300 via-malachite-300 to-candlelight-300 dark:from-candlelight-800 dark:via-malachite-800 dark:to-candlelight-800" />
|
|
||||||
|
|
||||||
<RevealSection delay={0}>
|
|
||||||
<StepCard
|
|
||||||
step={1}
|
|
||||||
icon={<SmilePlus className="w-7 h-7" />}
|
|
||||||
title="Identifiez votre émotion"
|
|
||||||
description="Choisissez parmi un panel d'émotions et sous-émotions. Notez l'intensité ressentie sur une échelle de 1 à 10."
|
|
||||||
color="candlelight"
|
|
||||||
/>
|
|
||||||
</RevealSection>
|
|
||||||
<RevealSection delay={150}>
|
|
||||||
<StepCard
|
|
||||||
step={2}
|
|
||||||
icon={<Brain className="w-7 h-7" />}
|
|
||||||
title="Ajoutez du contexte"
|
|
||||||
description="Notez ce qui a déclenché cette émotion. Même quelques mots suffisent à mieux comprendre vos tendances."
|
|
||||||
color="malachite"
|
|
||||||
/>
|
|
||||||
</RevealSection>
|
|
||||||
<RevealSection delay={300}>
|
|
||||||
<StepCard
|
|
||||||
step={3}
|
|
||||||
icon={<TrendingUp className="w-7 h-7" />}
|
|
||||||
title="Suivez votre évolution"
|
|
||||||
description="Consultez vos rapports visuels pour identifier vos tendances émotionnelles sur la semaine, le mois ou l'année."
|
|
||||||
color="candlelight"
|
|
||||||
/>
|
|
||||||
</RevealSection>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* ─── Features Section ─── */}
|
|
||||||
<section className="py-24 bg-gray-50 dark:bg-gray-900/50 relative overflow-hidden">
|
|
||||||
<div className="absolute top-0 right-0 w-96 h-96 bg-candlelight-200/20 dark:bg-candlelight-500/5 rounded-full blur-[100px] pointer-events-none" />
|
|
||||||
<div className="absolute bottom-0 left-0 w-96 h-96 bg-malachite-200/20 dark:bg-malachite-500/5 rounded-full blur-[100px] pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="max-w-6xl mx-auto px-4 relative">
|
|
||||||
<RevealSection>
|
|
||||||
<div className="text-center mb-20">
|
|
||||||
<span className="inline-flex items-center gap-2 text-candlelight-600 dark:text-candlelight-400 font-semibold text-sm uppercase tracking-wider mb-4">
|
|
||||||
<Star className="w-4 h-4" />
|
|
||||||
Fonctionnalités
|
|
||||||
</span>
|
|
||||||
<h2 className="font-display text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white mb-4">
|
|
||||||
Tout ce dont vous avez besoin
|
|
||||||
</h2>
|
|
||||||
<p className="text-lg text-gray-500 dark:text-gray-400 max-w-2xl mx-auto">
|
|
||||||
Des outils conçus pour vous accompagner au quotidien dans la gestion
|
|
||||||
de votre bien-être émotionnel.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
icon: <Heart className="w-7 h-7" />,
|
step: "1",
|
||||||
title: "Journal d'émotions",
|
title: "Identifiez votre émotion",
|
||||||
description: "Enregistrez vos émotions quotidiennes et suivez leur évolution dans le temps grâce à un journal intime sécurisé.",
|
desc: "Choisissez parmi un référentiel d'émotions et sous-émotions, puis notez l'intensité ressentie de 1 à 10.",
|
||||||
color: 'malachite' as const,
|
|
||||||
delay: 0,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BarChart3 className="w-7 h-7" />,
|
step: "2",
|
||||||
title: 'Rapports visuels',
|
title: "Ajoutez du contexte",
|
||||||
description: "Visualisez vos tendances grâce à des graphiques clairs : répartition, évolution et intensité de vos émotions.",
|
desc: "Notez ce qui a déclenché cette émotion. Quelques mots suffisent à mieux comprendre vos tendances.",
|
||||||
color: 'candlelight' as const,
|
|
||||||
delay: 100,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BookOpen className="w-7 h-7" />,
|
step: "3",
|
||||||
title: 'Ressources fiables',
|
title: "Suivez votre évolution",
|
||||||
description: "Accédez à des contenus validés par des experts sur la santé mentale, la gestion du stress et le bien-être.",
|
desc: "Consultez des rapports visuels (semaine, mois, trimestre, année) pour identifier vos tendances.",
|
||||||
color: 'malachite' as const,
|
|
||||||
delay: 200,
|
|
||||||
},
|
},
|
||||||
{
|
].map((s) => (
|
||||||
icon: <Shield className="w-7 h-7" />,
|
<div className="fr-col-12 fr-col-md-4" key={s.step}>
|
||||||
title: 'Données protégées',
|
<div className="fr-card fr-enlarge-link">
|
||||||
description: "Vos données sont chiffrées et protégées conformément au RGPD. Vous gardez le contrôle total sur vos informations.",
|
<div className="fr-card__body">
|
||||||
color: 'candlelight' as const,
|
<div className="fr-card__content">
|
||||||
delay: 300,
|
<h3 className="fr-card__title">
|
||||||
},
|
<span className="fr-badge fr-badge--blue-ecume fr-mb-1w">
|
||||||
].map((feature) => (
|
Étape {s.step}
|
||||||
<RevealSection key={feature.title} delay={feature.delay}>
|
</span>
|
||||||
<FeatureCard {...feature} />
|
<br />
|
||||||
</RevealSection>
|
{s.title}
|
||||||
|
</h3>
|
||||||
|
<p className="fr-card__desc">{s.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ─── Témoignages / Social Proof ─── */}
|
{/* ─── Fonctionnalités ─── */}
|
||||||
<section className="py-24 bg-white dark:bg-gray-950">
|
<section className="fr-container fr-py-8w">
|
||||||
<div className="max-w-6xl mx-auto px-4">
|
<h2>Tout ce dont vous avez besoin</h2>
|
||||||
<RevealSection>
|
<p className="fr-text--lead fr-mb-6w">
|
||||||
<div className="text-center mb-16">
|
Des outils conçus pour vous accompagner dans la gestion de votre
|
||||||
<span className="inline-flex items-center gap-2 text-malachite-600 dark:text-malachite-400 font-semibold text-sm uppercase tracking-wider mb-4">
|
bien-être émotionnel.
|
||||||
<Heart className="w-4 h-4" />
|
</p>
|
||||||
Pourquoi CESIZen ?
|
<div className="fr-grid-row fr-grid-row--gutters">
|
||||||
</span>
|
{[
|
||||||
<h2 className="font-display text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white mb-4">
|
{
|
||||||
Conçu pour votre bien-être
|
icon: "fr-icon-heart-line",
|
||||||
</h2>
|
title: "Journal d'émotions",
|
||||||
|
desc: "Enregistrez vos émotions quotidiennes et suivez leur évolution dans un journal sécurisé.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "fr-icon-line-chart-line",
|
||||||
|
title: "Rapports visuels",
|
||||||
|
desc: "Visualisez vos tendances : répartition, évolution et intensité de vos émotions.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "fr-icon-book-2-line",
|
||||||
|
title: "Ressources fiables",
|
||||||
|
desc: "Accédez à des contenus validés sur la santé mentale et la gestion du stress.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "fr-icon-shield-line",
|
||||||
|
title: "Données protégées",
|
||||||
|
desc: "Vos données sont protégées conformément au RGPD. Vous en gardez le contrôle total.",
|
||||||
|
},
|
||||||
|
].map((f) => (
|
||||||
|
<div className="fr-col-12 fr-col-md-6 fr-col-lg-3" key={f.title}>
|
||||||
|
<div className="fr-tile fr-tile--vertical">
|
||||||
|
<div className="fr-tile__body">
|
||||||
|
<div className="fr-tile__content">
|
||||||
|
<h3 className="fr-tile__title">
|
||||||
|
<span
|
||||||
|
className={`${f.icon} fr-mr-1w`}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{f.title}
|
||||||
|
</h3>
|
||||||
|
<p className="fr-tile__detail">{f.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RevealSection>
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-8">
|
{/* ─── Engagements ─── */}
|
||||||
|
<section className="fr-background-alt--grey fr-py-8w">
|
||||||
|
<div className="fr-container">
|
||||||
|
<h2>Une application pensée pour vous</h2>
|
||||||
|
<p className="fr-text--lead fr-mb-4w">
|
||||||
|
CESIZen est édité pour le Ministère des Solidarités et de la Santé :
|
||||||
|
un outil fiable, accessible et respectueux de votre vie privée.
|
||||||
|
</p>
|
||||||
|
<div className="fr-grid-row fr-grid-row--gutters">
|
||||||
{[
|
{[
|
||||||
{
|
"Totalement gratuit et sans publicité",
|
||||||
quote: "Prendre conscience de ses émotions est le premier pas vers une meilleure santé mentale.",
|
"Conforme RGPD — vos données vous appartiennent",
|
||||||
author: "Approche scientifique",
|
"Interface accessible et inclusive (RGAA)",
|
||||||
detail: "Basé sur les travaux de la psychologie positive",
|
"Rapports visuels pour comprendre vos tendances",
|
||||||
delay: 0,
|
"Contenus validés par des professionnels de santé",
|
||||||
},
|
"Export et suppression de vos données à tout moment",
|
||||||
{
|
|
||||||
quote: "Le simple fait de nommer une émotion réduit son intensité de 50%. C'est scientifiquement prouvé.",
|
|
||||||
author: "Affect Labeling",
|
|
||||||
detail: "Étude UCLA, Lieberman et al.",
|
|
||||||
delay: 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
quote: "L'écriture expressive améliore la santé physique et mentale sur le long terme.",
|
|
||||||
author: "Journaling thérapeutique",
|
|
||||||
detail: "Méthode Pennebaker",
|
|
||||||
delay: 300,
|
|
||||||
},
|
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<RevealSection key={item.author} delay={item.delay}>
|
<div className="fr-col-12 fr-col-md-6" key={item}>
|
||||||
<div className="bg-gray-50 dark:bg-gray-900 rounded-3xl p-8 h-full flex flex-col border border-gray-100 dark:border-gray-800 transition-all duration-300 hover:shadow-lg hover:-translate-y-1">
|
<p>
|
||||||
<div className="flex gap-1 mb-4">
|
<span
|
||||||
{[1, 2, 3, 4, 5].map((s) => (
|
className="fr-icon-check-line fr-mr-1w"
|
||||||
<Star key={s} className="w-4 h-4 fill-candlelight-400 text-candlelight-400" />
|
aria-hidden="true"
|
||||||
))}
|
/>
|
||||||
</div>
|
{item}
|
||||||
<blockquote className="text-gray-700 dark:text-gray-300 leading-relaxed mb-6 flex-1 italic">
|
</p>
|
||||||
“{item.quote}”
|
</div>
|
||||||
</blockquote>
|
|
||||||
<div>
|
|
||||||
<p className="font-semibold text-gray-900 dark:text-white text-sm">{item.author}</p>
|
|
||||||
<p className="text-gray-500 dark:text-gray-500 text-xs mt-1">{item.detail}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</RevealSection>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<ul className="fr-btns-group fr-btns-group--inline-md fr-mt-4w">
|
||||||
</section>
|
<li>
|
||||||
|
<Link className="fr-btn fr-btn--lg" href="/inscription">
|
||||||
{/* ─── Checklist Section ─── */}
|
|
||||||
<section className="py-24 bg-gray-50 dark:bg-gray-900/50">
|
|
||||||
<div className="max-w-6xl mx-auto px-4">
|
|
||||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
|
||||||
<RevealSection>
|
|
||||||
<div>
|
|
||||||
<span className="inline-flex items-center gap-2 text-candlelight-600 dark:text-candlelight-400 font-semibold text-sm uppercase tracking-wider mb-4">
|
|
||||||
<CheckCircle2 className="w-4 h-4" />
|
|
||||||
Avantages
|
|
||||||
</span>
|
|
||||||
<h2 className="font-display text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white mb-6">
|
|
||||||
Une application pensée pour vous
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-500 dark:text-gray-400 mb-8 leading-relaxed">
|
|
||||||
CESIZen est conçu par le Ministère des Solidarités et de la Santé
|
|
||||||
pour offrir un outil fiable, accessible et respectueux de votre vie privée.
|
|
||||||
</p>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{[
|
|
||||||
'Totalement gratuit et sans publicité',
|
|
||||||
'Conforme RGPD — vos données vous appartiennent',
|
|
||||||
'Interface accessible et inclusive (RGAA)',
|
|
||||||
'Mode sombre pour un usage confortable',
|
|
||||||
'Rapports visuels pour comprendre vos tendances',
|
|
||||||
'Contenus validés par des professionnels de santé',
|
|
||||||
'Export de vos données à tout moment',
|
|
||||||
'Suppression de compte en un clic',
|
|
||||||
].map((item, i) => (
|
|
||||||
<div key={i} className="flex items-start gap-3">
|
|
||||||
<CheckCircle2 className="w-5 h-5 text-malachite-500 mt-0.5 shrink-0" />
|
|
||||||
<span className="text-gray-700 dark:text-gray-300">{item}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<RevealSection delay={200}>
|
|
||||||
<div className="relative">
|
|
||||||
{/* Carte décorative simulant l'app */}
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-3xl shadow-2xl shadow-gray-200/50 dark:shadow-black/30 p-8 border border-gray-100 dark:border-gray-700">
|
|
||||||
<div className="flex items-center gap-3 mb-6">
|
|
||||||
<div className="w-10 h-10 bg-candlelight-500 rounded-xl flex items-center justify-center">
|
|
||||||
<Heart className="w-5 h-5 text-black" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="font-semibold text-gray-900 dark:text-white text-sm">Comment vous sentez-vous ?</p>
|
|
||||||
<p className="text-gray-500 text-xs">Aujourd'hui à 14:30</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-4 gap-3 mb-6">
|
|
||||||
{[
|
|
||||||
{ emoji: '😊', label: 'Joie', color: 'bg-yellow-100 dark:bg-yellow-900/30' },
|
|
||||||
{ emoji: '😌', label: 'Calme', color: 'bg-blue-100 dark:bg-blue-900/30' },
|
|
||||||
{ emoji: '😤', label: 'Colère', color: 'bg-red-100 dark:bg-red-900/30' },
|
|
||||||
{ emoji: '😢', label: 'Triste', color: 'bg-purple-100 dark:bg-purple-900/30' },
|
|
||||||
].map((e) => (
|
|
||||||
<div key={e.label} className={`${e.color} rounded-2xl p-3 text-center transition-transform hover:scale-105`}>
|
|
||||||
<span className="text-2xl block mb-1">{e.emoji}</span>
|
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">{e.label}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3 mb-6">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm text-gray-500 dark:text-gray-400">Intensité</span>
|
|
||||||
<span className="text-sm font-semibold text-malachite-600 dark:text-malachite-400">7/10</span>
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-gray-100 dark:bg-gray-700 rounded-full h-3">
|
|
||||||
<div className="bg-linear-to-r from-malachite-400 to-malachite-500 rounded-full h-3" style={{ width: '70%' }} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-2xl p-4">
|
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
|
|
||||||
“Belle journée au travail, bonne ambiance avec l'équipe...”
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Décoration */}
|
|
||||||
<div className="absolute -z-10 -top-6 -right-6 w-full h-full bg-linear-to-br from-candlelight-200 to-malachite-200 dark:from-candlelight-900/30 dark:to-malachite-900/30 rounded-3xl" />
|
|
||||||
</div>
|
|
||||||
</RevealSection>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* ─── CTA Section ─── */}
|
|
||||||
<section className="py-24 relative overflow-hidden">
|
|
||||||
<div className="absolute inset-0 bg-linear-to-br from-malachite-500 via-malachite-600 to-malachite-700" />
|
|
||||||
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.05)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.05)_1px,transparent_1px)] bg-size-[40px_40px] pointer-events-none" />
|
|
||||||
<div className="absolute -top-20 -right-20 w-80 h-80 bg-candlelight-400/20 rounded-full blur-[80px] pointer-events-none" />
|
|
||||||
<div className="absolute -bottom-20 -left-20 w-80 h-80 bg-malachite-300/20 rounded-full blur-[80px] pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto px-4 text-center relative">
|
|
||||||
<RevealSection>
|
|
||||||
<div className="inline-flex items-center gap-2 bg-white/15 backdrop-blur-sm rounded-full px-5 py-2 mb-8 border border-white/20">
|
|
||||||
<Sparkles className="w-4 h-4 text-candlelight-300" />
|
|
||||||
<span className="text-sm font-medium text-white/90">Rejoignez la communauté</span>
|
|
||||||
</div>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<RevealSection delay={100}>
|
|
||||||
<h2 className="font-display text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-6">
|
|
||||||
Prêt à prendre soin de vous ?
|
|
||||||
</h2>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<RevealSection delay={200}>
|
|
||||||
<p className="text-lg text-white/80 mb-10 max-w-2xl mx-auto leading-relaxed">
|
|
||||||
Créez votre compte en 2 minutes et commencez à suivre votre bien-être
|
|
||||||
émotionnel. C'est gratuit, confidentiel et sans engagement.
|
|
||||||
</p>
|
|
||||||
</RevealSection>
|
|
||||||
|
|
||||||
<RevealSection delay={300}>
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
||||||
<Link
|
|
||||||
href="/inscription"
|
|
||||||
className="group inline-flex items-center gap-2 bg-candlelight-500 hover:bg-candlelight-400 text-black font-bold px-10 py-4 rounded-2xl transition-all duration-300 hover:shadow-xl hover:shadow-candlelight-500/30 hover:-translate-y-0.5 text-lg"
|
|
||||||
>
|
|
||||||
Créer mon compte
|
Créer mon compte
|
||||||
<ArrowRight className="w-5 h-5 transition-transform duration-300 group-hover:translate-x-1" />
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
<Link
|
<Link
|
||||||
|
className="fr-btn fr-btn--lg fr-btn--secondary"
|
||||||
href="/connexion"
|
href="/connexion"
|
||||||
className="inline-flex items-center gap-2 bg-white/15 hover:bg-white/25 backdrop-blur-sm text-white font-semibold px-10 py-4 rounded-2xl border border-white/20 transition-all duration-300 hover:-translate-y-0.5 text-lg"
|
|
||||||
>
|
>
|
||||||
Se connecter
|
Se connecter
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</li>
|
||||||
</RevealSection>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ─── Urgence Banner ─── */}
|
{/* ─── Urgence ─── */}
|
||||||
<section className="bg-red-50 dark:bg-red-950/30 border-t border-red-100 dark:border-red-900/50 py-8">
|
<section className="fr-container fr-py-6w">
|
||||||
<div className="max-w-4xl mx-auto px-4 text-center">
|
<div className="fr-callout fr-callout--brown-caramel fr-icon-information-line">
|
||||||
<p className="text-red-700 dark:text-red-300 font-medium leading-relaxed">
|
<p className="fr-callout__title">En cas d'urgence ou de détresse</p>
|
||||||
<span className="font-bold">En cas d'urgence</span>, appelez le{' '}
|
<p className="fr-callout__text">
|
||||||
<a href="tel:3114" className="font-bold underline decoration-2 underline-offset-2 hover:text-red-900 dark:hover:text-red-200 transition-colors">
|
Appelez le <strong>3114</strong> (numéro national de prévention du
|
||||||
3114
|
suicide, gratuit, 24h/24) ou le <strong>15</strong> (SAMU). Vous
|
||||||
</a>{' '}
|
n'êtes pas seul·e.
|
||||||
(numéro national de prévention du suicide) ou le{' '}
|
|
||||||
<a href="tel:15" className="font-bold underline decoration-2 underline-offset-2 hover:text-red-900 dark:hover:text-red-200 transition-colors">
|
|
||||||
15
|
|
||||||
</a>{' '}
|
|
||||||
(SAMU)
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── Composants ─── */
|
|
||||||
|
|
||||||
function MiniStat({ icon, value, label }: { icon: React.ReactNode; value: string; label: string }) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="w-10 h-10 bg-white/70 dark:bg-gray-800/70 backdrop-blur-sm rounded-xl flex items-center justify-center text-malachite-600 dark:text-malachite-400 shadow-sm border border-gray-200/50 dark:border-gray-700/50">
|
|
||||||
{icon}
|
|
||||||
</div>
|
|
||||||
<div className="text-left">
|
|
||||||
<p className="font-bold text-gray-900 dark:text-white text-sm">{value}</p>
|
|
||||||
<p className="text-gray-500 dark:text-gray-500 text-xs">{label}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function StepCard({
|
|
||||||
step,
|
|
||||||
icon,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
color,
|
|
||||||
}: {
|
|
||||||
step: number;
|
|
||||||
icon: React.ReactNode;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
color: 'malachite' | 'candlelight';
|
|
||||||
}) {
|
|
||||||
const colors = color === 'malachite'
|
|
||||||
? { bg: 'bg-malachite-50 dark:bg-malachite-950/30', icon: 'text-malachite-600 dark:text-malachite-400', iconBg: 'bg-malachite-100 dark:bg-malachite-900/50', badge: 'bg-malachite-500' }
|
|
||||||
: { bg: 'bg-candlelight-50 dark:bg-candlelight-950/30', icon: 'text-candlelight-600 dark:text-candlelight-400', iconBg: 'bg-candlelight-100 dark:bg-candlelight-900/50', badge: 'bg-candlelight-500' };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${colors.bg} rounded-3xl p-8 text-center relative transition-all duration-300 hover:shadow-xl hover:-translate-y-2 border border-transparent hover:border-gray-200 dark:hover:border-gray-700 h-full`}>
|
|
||||||
<div className={`${colors.badge} text-white w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm absolute -top-3 left-1/2 -translate-x-1/2 shadow-lg`}>
|
|
||||||
{step}
|
|
||||||
</div>
|
|
||||||
<div className={`${colors.iconBg} ${colors.icon} w-16 h-16 rounded-2xl flex items-center justify-center mx-auto mb-5 mt-2`}>
|
|
||||||
{icon}
|
|
||||||
</div>
|
|
||||||
<h3 className="font-display font-semibold text-lg text-gray-900 dark:text-white mb-3">{title}</h3>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400 text-sm leading-relaxed">{description}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FeatureCard({
|
|
||||||
icon,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
color,
|
|
||||||
}: {
|
|
||||||
icon: React.ReactNode;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
color: 'malachite' | 'candlelight';
|
|
||||||
}) {
|
|
||||||
const bgColor = color === 'malachite'
|
|
||||||
? 'bg-white dark:bg-gray-800/50'
|
|
||||||
: 'bg-white dark:bg-gray-800/50';
|
|
||||||
const iconColor = color === 'malachite'
|
|
||||||
? 'text-malachite-600 dark:text-malachite-400'
|
|
||||||
: 'text-candlelight-600 dark:text-candlelight-400';
|
|
||||||
const iconBg = color === 'malachite'
|
|
||||||
? 'bg-malachite-100 dark:bg-malachite-900/50'
|
|
||||||
: 'bg-candlelight-100 dark:bg-candlelight-900/50';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${bgColor} rounded-3xl p-7 transition-all duration-300 hover:shadow-xl hover:-translate-y-2 border border-gray-100 dark:border-gray-800 hover:border-gray-200 dark:hover:border-gray-700 h-full`}>
|
|
||||||
<div className={`${iconBg} ${iconColor} w-14 h-14 rounded-2xl flex items-center justify-center mb-5`}>
|
|
||||||
{icon}
|
|
||||||
</div>
|
|
||||||
<h3 className="font-display font-semibold text-lg text-gray-900 dark:text-white mb-3">
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400 text-sm leading-relaxed">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,103 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Phone } from "lucide-react";
|
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-gray-50 dark:bg-gray-950 border-t border-gray-200 dark:border-gray-800">
|
<footer className="fr-footer" role="contentinfo" id="footer">
|
||||||
{/* Bandeau urgence */}
|
<div className="fr-container">
|
||||||
<div className="bg-red-50 dark:bg-red-950/50 border-b border-red-100 dark:border-red-900">
|
<div className="fr-footer__body">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
<div className="fr-footer__brand fr-enlarge-link">
|
||||||
<p className="flex items-center justify-center gap-2 text-sm text-red-700 dark:text-red-400 font-medium text-center">
|
<Link href="/" title="Accueil - CESIZen">
|
||||||
<Phone size={16} className="shrink-0" />
|
<p className="fr-logo">
|
||||||
En cas d'urgence : 3114 (numéro national de prévention du suicide)
|
République
|
||||||
</p>
|
<br />
|
||||||
|
Française
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="fr-footer__content">
|
||||||
|
<p className="fr-footer__content-desc">
|
||||||
|
CESIZen est une plateforme publique de prévention et de suivi en
|
||||||
|
santé mentale, éditée pour le Ministère des Solidarités et de la
|
||||||
|
Santé. En cas de détresse, contactez le 3114 (numéro national de
|
||||||
|
prévention du suicide).
|
||||||
|
</p>
|
||||||
|
<ul className="fr-footer__content-list">
|
||||||
|
<li className="fr-footer__content-item">
|
||||||
|
<a
|
||||||
|
className="fr-footer__content-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer external"
|
||||||
|
href="https://legifrance.gouv.fr"
|
||||||
|
>
|
||||||
|
legifrance.gouv.fr
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="fr-footer__content-item">
|
||||||
|
<a
|
||||||
|
className="fr-footer__content-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer external"
|
||||||
|
href="https://gouvernement.fr"
|
||||||
|
>
|
||||||
|
gouvernement.fr
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="fr-footer__content-item">
|
||||||
|
<a
|
||||||
|
className="fr-footer__content-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer external"
|
||||||
|
href="https://service-public.fr"
|
||||||
|
>
|
||||||
|
service-public.fr
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="fr-footer__content-item">
|
||||||
|
<a
|
||||||
|
className="fr-footer__content-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer external"
|
||||||
|
href="https://data.gouv.fr"
|
||||||
|
>
|
||||||
|
data.gouv.fr
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="fr-footer__bottom">
|
||||||
|
<ul className="fr-footer__bottom-list">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<li className="fr-footer__bottom-item">
|
||||||
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
|
<Link className="fr-footer__bottom-link" href="/mentions-legales">
|
||||||
{/* Logo + Copyright */}
|
Mentions légales
|
||||||
<div className="flex flex-col items-center md:items-start gap-2">
|
</Link>
|
||||||
<div className="flex items-center gap-0.5">
|
</li>
|
||||||
<span className="text-xl font-bold font-display text-malachite-500">
|
<li className="fr-footer__bottom-item">
|
||||||
CESI
|
<Link
|
||||||
</span>
|
className="fr-footer__bottom-link"
|
||||||
<span className="text-xl font-bold font-display text-candlelight-500">
|
href="/politique-de-confidentialite"
|
||||||
ZEN
|
>
|
||||||
</span>
|
Données personnelles
|
||||||
</div>
|
</Link>
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400 text-center md:text-left">
|
</li>
|
||||||
CESIZen 2024 - Ministère des Solidarités et de la Santé
|
<li className="fr-footer__bottom-item">
|
||||||
|
<Link className="fr-footer__bottom-link" href="/contacts-urgence">
|
||||||
|
Contacts d'urgence
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div className="fr-footer__bottom-copy">
|
||||||
|
<p>
|
||||||
|
Sauf mention contraire, tous les contenus de ce site sont sous{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer external"
|
||||||
|
>
|
||||||
|
licence etalab-2.0
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Links */}
|
|
||||||
<nav className="flex flex-wrap items-center justify-center gap-6">
|
|
||||||
<Link
|
|
||||||
href="/mentions-legales"
|
|
||||||
className="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
|
|
||||||
>
|
|
||||||
Mentions légales
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/politique-de-confidentialite"
|
|
||||||
className="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
|
|
||||||
>
|
|
||||||
Politique de confidentialité
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/contacts-urgence"
|
|
||||||
className="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
|
|
||||||
>
|
|
||||||
Contacts d'urgence
|
|
||||||
</Link>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -1,245 +1,158 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import {
|
|
||||||
Menu,
|
|
||||||
X,
|
|
||||||
User,
|
|
||||||
LogOut,
|
|
||||||
Settings,
|
|
||||||
BarChart3,
|
|
||||||
BookOpen,
|
|
||||||
Home,
|
|
||||||
} from "lucide-react";
|
|
||||||
import ThemeToggle from "@/components/ui/ThemeToggle";
|
|
||||||
import { useAuthStore } from "@/lib/auth-store";
|
import { useAuthStore } from "@/lib/auth-store";
|
||||||
|
|
||||||
interface NavLink {
|
interface NavLink {
|
||||||
href: string;
|
href: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
||||||
const [userDropdownOpen, setUserDropdownOpen] = useState(false);
|
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
const { user, token, logout } = useAuthStore();
|
const { user, token, logout } = useAuthStore();
|
||||||
const isAuthenticated = !!token;
|
const isAuthenticated = !!token;
|
||||||
const isAdmin = user?.role?.nom === "administrateur";
|
const isAdmin = user?.role?.nom === "administrateur";
|
||||||
|
|
||||||
const publicLinks: NavLink[] = [
|
const navLinks: NavLink[] = [
|
||||||
{ href: "/", label: "Accueil", icon: <Home size={18} /> },
|
{ href: "/", label: "Accueil" },
|
||||||
{ href: "/informations", label: "Informations", icon: <BookOpen size={18} /> },
|
{ href: "/informations", label: "Informations" },
|
||||||
|
{ href: "/contacts-urgence", label: "Contacts d'urgence" },
|
||||||
|
...(isAuthenticated
|
||||||
|
? [
|
||||||
|
{ href: "/journal", label: "Mon journal" },
|
||||||
|
{ href: "/journal/rapports", label: "Mes rapports" },
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(isAdmin ? [{ href: "/admin/dashboard", label: "Administration" }] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
const authLinks: NavLink[] = [
|
const isActive = (href: string) =>
|
||||||
{ href: "/journal", label: "Journal", icon: <BookOpen size={18} /> },
|
href === "/" ? pathname === "/" : pathname.startsWith(href);
|
||||||
{ href: "/journal/rapports", label: "Rapports", icon: <BarChart3 size={18} /> },
|
|
||||||
];
|
|
||||||
|
|
||||||
const adminLinks: NavLink[] = [
|
|
||||||
{ href: "/admin", label: "Admin", icon: <Settings size={18} /> },
|
|
||||||
];
|
|
||||||
|
|
||||||
const navLinks = [
|
|
||||||
...publicLinks,
|
|
||||||
...(isAuthenticated ? authLinks : []),
|
|
||||||
...(isAdmin ? adminLinks : []),
|
|
||||||
];
|
|
||||||
|
|
||||||
const isActive = (href: string) => {
|
|
||||||
if (href === "/") return pathname === "/";
|
|
||||||
return pathname.startsWith(href);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
logout();
|
|
||||||
setUserDropdownOpen(false);
|
|
||||||
setMobileMenuOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 bg-white/80 dark:bg-gray-950/80 backdrop-blur-lg border-b border-gray-200 dark:border-gray-800">
|
<header role="banner" className="fr-header">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="fr-header__body">
|
||||||
<div className="flex items-center justify-between h-16">
|
<div className="fr-container">
|
||||||
{/* Logo */}
|
<div className="fr-header__body-row">
|
||||||
<Link href="/" className="flex items-center gap-0.5 shrink-0">
|
<div className="fr-header__brand fr-enlarge-link">
|
||||||
<span className="text-2xl font-bold font-display text-malachite-500">
|
<div className="fr-header__brand-top">
|
||||||
CESI
|
<div className="fr-header__logo">
|
||||||
</span>
|
<p className="fr-logo">
|
||||||
<span className="text-2xl font-bold font-display text-candlelight-500">
|
République
|
||||||
ZEN
|
<br />
|
||||||
</span>
|
Française
|
||||||
</Link>
|
</p>
|
||||||
|
</div>
|
||||||
{/* Desktop Navigation */}
|
<div className="fr-header__navbar">
|
||||||
<nav className="hidden md:flex items-center gap-1">
|
|
||||||
{navLinks.map((link) => (
|
|
||||||
<Link
|
|
||||||
key={link.href}
|
|
||||||
href={link.href}
|
|
||||||
className={`
|
|
||||||
flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200
|
|
||||||
${
|
|
||||||
isActive(link.href)
|
|
||||||
? "bg-candlelight-100 dark:bg-candlelight-950 text-candlelight-700 dark:text-candlelight-400"
|
|
||||||
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-200"
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{link.icon}
|
|
||||||
{link.label}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Right actions */}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<ThemeToggle />
|
|
||||||
|
|
||||||
{/* Desktop auth */}
|
|
||||||
<div className="hidden md:flex items-center gap-2">
|
|
||||||
{isAuthenticated ? (
|
|
||||||
<div className="relative">
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setUserDropdownOpen(!userDropdownOpen)}
|
className="fr-btn--menu fr-btn"
|
||||||
className="flex items-center gap-2 px-3 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
data-fr-opened="false"
|
||||||
|
aria-controls="modal-menu-mobile"
|
||||||
|
aria-haspopup="menu"
|
||||||
|
id="button-menu-mobile"
|
||||||
|
title="Menu"
|
||||||
>
|
>
|
||||||
<div className="h-8 w-8 rounded-full bg-malachite-100 dark:bg-malachite-900 flex items-center justify-center">
|
Menu
|
||||||
<User
|
|
||||||
size={16}
|
|
||||||
className="text-malachite-600 dark:text-malachite-400"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
||||||
{user?.prenom || "Utilisateur"}
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
{userDropdownOpen && (
|
</div>
|
||||||
|
<div className="fr-header__service">
|
||||||
|
<Link href="/" title="Accueil - CESIZen">
|
||||||
|
<span className="fr-header__service-title">CESIZen</span>
|
||||||
|
</Link>
|
||||||
|
<p className="fr-header__service-tagline">
|
||||||
|
Votre santé mentale — Ministère des Solidarités et de la Santé
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="fr-header__tools">
|
||||||
|
<div className="fr-header__tools-links">
|
||||||
|
<ul className="fr-btns-group">
|
||||||
|
{isAuthenticated ? (
|
||||||
<>
|
<>
|
||||||
<div
|
<li>
|
||||||
className="fixed inset-0 z-10"
|
|
||||||
onClick={() => setUserDropdownOpen(false)}
|
|
||||||
/>
|
|
||||||
<div className="absolute right-0 mt-2 w-48 z-20 bg-white dark:bg-gray-900 rounded-xl shadow-lg border border-gray-100 dark:border-gray-800 py-1 animate-in fade-in zoom-in-95 duration-150">
|
|
||||||
<Link
|
<Link
|
||||||
|
className="fr-btn fr-icon-account-line"
|
||||||
href="/profil"
|
href="/profil"
|
||||||
onClick={() => setUserDropdownOpen(false)}
|
|
||||||
className="flex items-center gap-2 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
||||||
>
|
>
|
||||||
<Settings size={16} />
|
{user?.prenom || "Mon profil"}
|
||||||
Mon profil
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
className="fr-btn fr-icon-logout-box-r-line"
|
||||||
className="w-full flex items-center gap-2 px-4 py-2.5 text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950 transition-colors"
|
onClick={() => logout()}
|
||||||
>
|
>
|
||||||
<LogOut size={16} />
|
|
||||||
Déconnexion
|
Déconnexion
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</li>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
className="fr-btn fr-icon-account-line"
|
||||||
|
href="/connexion"
|
||||||
|
>
|
||||||
|
Connexion
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
className="fr-btn fr-icon-add-line"
|
||||||
|
href="/inscription"
|
||||||
|
>
|
||||||
|
Créer un compte
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</ul>
|
||||||
) : (
|
</div>
|
||||||
<>
|
|
||||||
<Link
|
|
||||||
href="/connexion"
|
|
||||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors"
|
|
||||||
>
|
|
||||||
Connexion
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/inscription"
|
|
||||||
className="px-4 py-2 text-sm font-semibold bg-candlelight-500 hover:bg-candlelight-600 text-black rounded-xl transition-all duration-200 shadow-sm hover:shadow-md"
|
|
||||||
>
|
|
||||||
Inscription
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile hamburger */}
|
|
||||||
<button
|
|
||||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
||||||
className="md:hidden p-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors text-gray-600 dark:text-gray-400"
|
|
||||||
aria-label="Menu"
|
|
||||||
>
|
|
||||||
{mobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile menu */}
|
{/* Navigation principale (et menu mobile DSFR) */}
|
||||||
{mobileMenuOpen && (
|
<div
|
||||||
<div className="md:hidden border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 animate-in slide-in-from-top duration-200">
|
className="fr-header__menu fr-modal"
|
||||||
<nav className="px-4 py-4 space-y-1">
|
id="modal-menu-mobile"
|
||||||
{navLinks.map((link) => (
|
aria-labelledby="button-menu-mobile"
|
||||||
<Link
|
>
|
||||||
key={link.href}
|
<div className="fr-container">
|
||||||
href={link.href}
|
<button
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
className="fr-btn--close fr-btn"
|
||||||
className={`
|
aria-controls="modal-menu-mobile"
|
||||||
flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium transition-all duration-200
|
title="Fermer"
|
||||||
${
|
>
|
||||||
isActive(link.href)
|
Fermer
|
||||||
? "bg-candlelight-100 dark:bg-candlelight-950 text-candlelight-700 dark:text-candlelight-400"
|
</button>
|
||||||
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"
|
<nav
|
||||||
}
|
className="fr-nav"
|
||||||
`}
|
id="navigation-principale"
|
||||||
>
|
role="navigation"
|
||||||
{link.icon}
|
aria-label="Menu principal"
|
||||||
{link.label}
|
>
|
||||||
</Link>
|
<ul className="fr-nav__list">
|
||||||
))}
|
{navLinks.map((link) => (
|
||||||
|
<li className="fr-nav__item" key={link.href}>
|
||||||
<div className="pt-4 border-t border-gray-200 dark:border-gray-800 mt-4 space-y-1">
|
|
||||||
{isAuthenticated ? (
|
|
||||||
<>
|
|
||||||
<Link
|
<Link
|
||||||
href="/profil"
|
className="fr-nav__link"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
href={link.href}
|
||||||
className="flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
target="_self"
|
||||||
|
aria-current={isActive(link.href) ? "page" : undefined}
|
||||||
>
|
>
|
||||||
<User size={18} />
|
{link.label}
|
||||||
Mon profil
|
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
</li>
|
||||||
onClick={handleLogout}
|
))}
|
||||||
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950 transition-colors"
|
</ul>
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
Déconnexion
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Link
|
|
||||||
href="/connexion"
|
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
|
||||||
className="flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
|
||||||
>
|
|
||||||
Connexion
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/inscription"
|
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
|
||||||
className="flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-sm font-semibold bg-candlelight-500 hover:bg-candlelight-600 text-black transition-all duration-200"
|
|
||||||
>
|
|
||||||
Inscription
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,23 +11,19 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mappe les variantes internes sur les classes du Système de Design de l'État (DSFR).
|
||||||
const variantClasses: Record<ButtonVariant, string> = {
|
const variantClasses: Record<ButtonVariant, string> = {
|
||||||
primary:
|
primary: "",
|
||||||
"bg-candlelight-500 hover:bg-candlelight-600 text-black font-semibold shadow-sm hover:shadow-md",
|
secondary: "fr-btn--secondary",
|
||||||
secondary:
|
outline: "fr-btn--secondary",
|
||||||
"bg-malachite-500 hover:bg-malachite-600 text-white font-semibold shadow-sm hover:shadow-md",
|
ghost: "fr-btn--tertiary-no-outline",
|
||||||
outline:
|
danger: "fr-btn--secondary",
|
||||||
"border-2 border-candlelight-500 text-candlelight-700 dark:text-candlelight-400 hover:bg-candlelight-50 dark:hover:bg-candlelight-950",
|
|
||||||
ghost:
|
|
||||||
"hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300",
|
|
||||||
danger:
|
|
||||||
"bg-red-500 hover:bg-red-600 text-white font-semibold shadow-sm hover:shadow-md",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizeClasses: Record<ButtonSize, string> = {
|
const sizeClasses: Record<ButtonSize, string> = {
|
||||||
sm: "px-3 py-1.5 text-sm",
|
sm: "fr-btn--sm",
|
||||||
md: "px-5 py-2.5 text-base",
|
md: "",
|
||||||
lg: "px-7 py-3.5 text-lg",
|
lg: "fr-btn--lg",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Button({
|
export default function Button({
|
||||||
@@ -39,43 +35,18 @@ export default function Button({
|
|||||||
className = "",
|
className = "",
|
||||||
...rest
|
...rest
|
||||||
}: ButtonProps) {
|
}: ButtonProps) {
|
||||||
|
const classes = [
|
||||||
|
"fr-btn",
|
||||||
|
variantClasses[variant],
|
||||||
|
sizeClasses[size],
|
||||||
|
loading ? "fr-btn--icon-left fr-icon-refresh-line" : "",
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button className={classes} disabled={disabled || loading} {...rest}>
|
||||||
disabled={disabled || loading}
|
|
||||||
className={`
|
|
||||||
inline-flex items-center justify-center gap-2
|
|
||||||
rounded-xl transition-all duration-200
|
|
||||||
focus:outline-none focus:ring-2 focus:ring-candlelight-500 focus:ring-offset-2
|
|
||||||
dark:focus:ring-offset-gray-900
|
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed
|
|
||||||
${variantClasses[variant]}
|
|
||||||
${sizeClasses[size]}
|
|
||||||
${className}
|
|
||||||
`}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{loading && (
|
|
||||||
<svg
|
|
||||||
className="animate-spin h-4 w-4"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
className="opacity-25"
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="10"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="4"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
className="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user