Dev #1

Merged
jessy-david-dev merged 7 commits from dev into main 2026-01-27 17:08:37 +00:00
8 changed files with 1359 additions and 200 deletions
+12 -12
View File
@@ -2,21 +2,21 @@
Portfolio personnel de développeur web, construit avec **Next.js** et un ensemble de technologies modernes. Portfolio personnel de développeur web, construit avec **Next.js** et un ensemble de technologies modernes.
![Next.js](https://img.shields.io/badge/Next.js-15-black?style=flat-square&logo=next.js) ![Next.js](https://img.shields.io/badge/Next.js-16.1.5-black?style=flat-square&logo=next.js)
![TypeScript](https://img.shields.io/badge/TypeScript-5-blue?style=flat-square&logo=typescript) ![TypeScript](https://img.shields.io/badge/TypeScript-5.9.3-blue?style=flat-square&logo=typescript)
![Tailwind CSS](https://img.shields.io/badge/Tailwind-4-38B2AC?style=flat-square&logo=tailwind-css) ![Tailwind CSS](https://img.shields.io/badge/Tailwind-4.1.18-38B2AC?style=flat-square&logo=tailwind-css)
![License](https://img.shields.io/badge/License-MIT-green?style=flat-square) ![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)
--- ---
## ✨ Fonctionnalités ## ✨ Fonctionnalités
- **Next.js App Router** & Server Components -**Next.js App Router** & Server Components
- 🎨 **Tailwind CSS** pour un design moderne et responsive - 🎨 **Tailwind CSS** pour un design moderne et responsive
- 📧 **Formulaire de contact** via API Route - 📧 **Formulaire de contact** via API Route
- 🗺️ **Sitemap dynamique** pour un SEO optimisé - 🗺️ **Sitemap dynamique** pour un SEO optimisé
- 🌐 **PWA Ready** (manifest + icônes) - 🌐 **PWA Ready** (manifest + icônes)
- 🔥 **Animations fluides** et transitions modernes - 🔥 **Animations fluides** et transitions modernes
--- ---
@@ -115,9 +115,9 @@ Ce projet est sous licence **MIT** voir le fichier [LICENSE](LICENSE) pour p
**Jessy David** **Jessy David**
- 🌐 Website : [jessy-david.dev](https://jessy-david.dev) - 🌐 Website : [jessy-david.dev](https://jessy-david.dev)
- 🐙 GitHub : [@jessydavid-dev](https://github.com/jessydavid-dev) - 🐙 GitHub : [@jessydavid-dev](https://github.com/jessydavid-dev)
- 📧 Email : [contact@jessy-david.dev](mailto:contact@jessy-david.dev) - 📧 Email : [contact@jessy-david.dev](mailto:contact@jessy-david.dev)
--- ---
+227 -185
View File
@@ -1,5 +1,6 @@
import Footer from "@/component/layout/Footer"; import Footer from "@/component/layout/Footer";
import NavBar from "@/component/layout/NavBar"; import NavBar from "@/component/layout/NavBar";
import CookieBanner from "@/component/ui/CookieBanner";
import GridBackground from "@/component/ui/GridBackground"; import GridBackground from "@/component/ui/GridBackground";
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
@@ -7,216 +8,257 @@ import { ReactNode } from "react";
import "./globals.css"; import "./globals.css";
const inter = Inter({ const inter = Inter({
subsets: ["latin"], subsets: ["latin"],
display: "swap", display: "swap",
variable: "--font-inter", variable: "--font-inter",
}); });
const siteConfig = { const siteConfig = {
name: "Jessy David", name: "Jessy David",
title: "Jessy David | Développeur Web Full-Stack", title: "Jessy David | Développeur Web Full-Stack",
description: description:
"Développeur Web Full-Stack passionné, spécialisé en React, Next.js et Node.js. Création de sites web modernes, applications web performantes et solutions digitales sur mesure.", "Développeur Web Full-Stack passionné, spécialisé en React, Next.js et Node.js. Création de sites web modernes, applications web performantes et solutions digitales sur mesure.",
url: "https://jessy-david.dev", url: "https://jessy-david.dev",
ogImage: "https://jessy-david.dev/og-image.jpg", ogImage: "https://jessy-david.dev/og-image.jpg",
author: "Jessy David", author: "Jessy David",
keywords: [ keywords: [
"développeur web", "développeur web",
"développeur full-stack", "développeur full-stack",
"React", "React",
"Next.js", "Next.js",
"Node.js", "Node.js",
"TypeScript", "TypeScript",
"JavaScript", "JavaScript",
"création site web", "création site web",
"freelance", "freelance",
"France", "France",
"portfolio", "portfolio",
"Jessy David", "Jessy David",
], ],
twitterHandle: "@jessydavid", twitterHandle: "@jessydavid",
}; };
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {
default: siteConfig.title, default: siteConfig.title,
template: `%s | ${siteConfig.name}`, template: `%s | ${siteConfig.name}`,
},
description: siteConfig.description,
keywords: siteConfig.keywords,
authors: [{ name: siteConfig.author, url: siteConfig.url }],
creator: siteConfig.author,
publisher: siteConfig.author,
metadataBase: new URL(siteConfig.url),
alternates: {
canonical: "/",
languages: {
"fr-FR": "/",
}, },
},
robots: {
index: true,
follow: true,
nocache: false,
googleBot: {
index: true,
follow: true,
noimageindex: false,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
openGraph: {
type: "website",
locale: "fr_FR",
url: siteConfig.url,
siteName: siteConfig.name,
title: siteConfig.title,
description: siteConfig.description, description: siteConfig.description,
keywords: siteConfig.keywords, images: [
authors: [{ name: siteConfig.author, url: siteConfig.url }], {
creator: siteConfig.author, url: siteConfig.ogImage,
publisher: siteConfig.author, width: 1200,
height: 630,
alt: `${siteConfig.name} - Portfolio Développeur Web`,
type: "image/jpeg",
},
],
},
metadataBase: new URL(siteConfig.url), twitter: {
alternates: { card: "summary_large_image",
canonical: "/", title: siteConfig.title,
languages: { description: siteConfig.description,
"fr-FR": "/", creator: siteConfig.twitterHandle,
}, images: [siteConfig.ogImage],
}, },
robots: { icons: {
index: true, icon: [
follow: true, { url: "/favicon.ico", sizes: "any" },
nocache: false, { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
googleBot: { { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
index: true, ],
follow: true, apple: [
noimageindex: false, {
"max-video-preview": -1, url: "/apple-touch-icon.png",
"max-image-preview": "large", sizes: "180x180",
"max-snippet": -1, type: "image/png",
}, },
}, ],
},
openGraph: { manifest: "/site.webmanifest",
type: "website",
locale: "fr_FR",
url: siteConfig.url,
siteName: siteConfig.name,
title: siteConfig.title,
description: siteConfig.description,
images: [
{
url: siteConfig.ogImage,
width: 1200,
height: 630,
alt: `${siteConfig.name} - Portfolio Développeur Web`,
type: "image/jpeg",
},
],
},
twitter: { category: "technology",
card: "summary_large_image",
title: siteConfig.title,
description: siteConfig.description,
creator: siteConfig.twitterHandle,
images: [siteConfig.ogImage],
},
icons: { verification: {
icon: [ google: "ton-code-google-search-console",
{ url: "/favicon.ico", sizes: "any" }, },
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
],
apple: [
{
url: "/apple-touch-icon.png",
sizes: "180x180",
type: "image/png",
},
],
},
manifest: "/site.webmanifest", other: {
"msapplication-TileColor": "#0f172a",
category: "technology", "theme-color": "#0f172a",
},
verification: {
google: "ton-code-google-search-console",
},
other: {
"msapplication-TileColor": "#0f172a",
"theme-color": "#0f172a",
},
}; };
export const viewport: Viewport = { export const viewport: Viewport = {
themeColor: [ themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#ffffff" }, { media: "(prefers-color-scheme: light)", color: "#ffffff" },
{ media: "(prefers-color-scheme: dark)", color: "#0f172a" }, { media: "(prefers-color-scheme: dark)", color: "#0f172a" },
], ],
width: "device-width", width: "device-width",
initialScale: 1, initialScale: 1,
maximumScale: 5, maximumScale: 5,
userScalable: true, userScalable: true,
colorScheme: "dark", colorScheme: "dark",
}; };
const jsonLd = { const jsonLd = {
"@context": "https://schema.org", "@context": "https://schema.org",
"@graph": [ "@graph": [
{ {
"@type": "WebSite", "@type": "WebSite",
"@id": `${siteConfig.url}/#website`, "@id": `${siteConfig.url}/#website`,
url: siteConfig.url, url: siteConfig.url,
name: siteConfig.name, name: siteConfig.name,
description: siteConfig.description, description: siteConfig.description,
inLanguage: "fr-FR", inLanguage: "fr-FR",
}, },
{ {
"@type": "Person", "@type": "Person",
"@id": `${siteConfig.url}/#person`, "@id": `${siteConfig.url}/#person`,
name: siteConfig.name, name: siteConfig.name,
url: siteConfig.url, url: siteConfig.url,
image: siteConfig.ogImage, image: siteConfig.ogImage,
jobTitle: "Développeur Web Full-Stack", jobTitle: "Développeur Web Full-Stack",
description: siteConfig.description, description: siteConfig.description,
sameAs: [ sameAs: [
"https://github.com/jessydavid-dev", "https://github.com/jessydavid-dev",
"https://linkedin.com/in/jessy-david", "https://linkedin.com/in/jessy-david",
"https://twitter.com/jessydavid", "https://twitter.com/jessydavid",
], ],
knowsAbout: [ knowsAbout: [
"React", "React",
"Next.js", "Next.js",
"TypeScript", "TypeScript",
"Node.js", "Node.js",
"JavaScript", "JavaScript",
"Tailwind CSS", "Tailwind CSS",
"PostgreSQL", "PostgreSQL",
"MongoDB", "MongoDB",
], ],
}, },
{ {
"@type": "ProfilePage", "@type": "ProfilePage",
"@id": `${siteConfig.url}/#profilepage`, "@id": `${siteConfig.url}/#profilepage`,
url: siteConfig.url, url: siteConfig.url,
name: `Portfolio de ${siteConfig.name}`, name: `Portfolio de ${siteConfig.name}`,
description: siteConfig.description, description: siteConfig.description,
mainEntity: { "@id": `${siteConfig.url}/#person` }, mainEntity: { "@id": `${siteConfig.url}/#person` },
inLanguage: "fr-FR", inLanguage: "fr-FR",
}, },
], ],
}; };
export default function RootLayout({ export default function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: ReactNode; children: ReactNode;
}>) { }>) {
return ( return (
<html lang="fr" className={inter.variable}> <html lang="fr" className={inter.variable}>
<head> <head>
<script {/* Google Tag Manager - Consent Mode par défaut */}
type="application/ld+json" <script
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} dangerouslySetInnerHTML={{
/> __html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
<link rel="preconnect" href="https://fonts.googleapis.com" /> // Consentement par défaut : tout refusé (RGPD compliant)
<link gtag('consent', 'default', {
rel="preconnect" 'analytics_storage': 'denied',
href="https://fonts.gstatic.com" 'ad_storage': 'denied',
crossOrigin="anonymous" 'ad_user_data': 'denied',
/> 'ad_personalization': 'denied',
'wait_for_update': 500
});
`,
}}
/>
{/* Google Tag Manager */}
<script
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];
w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TJDQ3XJD');
`,
}}
/>
<link <script
rel="dns-prefetch" type="application/ld+json"
href="https://www.google-analytics.com" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/> />
</head>
<body className={`${inter.className} antialiased`}> <link rel="preconnect" href="https://fonts.googleapis.com" />
<GridBackground variant="light" /> <link
<NavBar /> rel="preconnect"
<main id="main-content" className="relative z-10"> href="https://fonts.gstatic.com"
{children} crossOrigin="anonymous"
</main> />
<Footer />
</body> <link rel="dns-prefetch" href="https://www.googletagmanager.com" />
</html> </head>
); <body className={`${inter.className} antialiased`}>
{/* Google Tag Manager (noscript) */}
<noscript>
<iframe
src="https://www.googletagmanager.com/ns.html?id=GTM-TJDQ3XJD"
height="0"
width="0"
style={{ display: "none", visibility: "hidden" }}
/>
</noscript>
<GridBackground variant="light" />
<NavBar />
<main id="main-content" className="relative z-10">
{children}
</main>
<Footer />
<CookieBanner />
</body>
</html>
);
} }
+277
View File
@@ -0,0 +1,277 @@
"use client";
import { motion } from "framer-motion";
import { ArrowLeft, FileText, Scale, User } from "lucide-react";
import Link from "next/link";
export default function MentionsLegales() {
return (
<section className="min-h-screen py-20 bg-slate-900 relative overflow-hidden">
{/* Grid Background */}
<div
className="absolute inset-0 opacity-20"
style={{
backgroundImage:
"linear-gradient(to right, #1e293b 1px, transparent 1px), linear-gradient(to bottom, #1e293b 1px, transparent 1px)",
backgroundSize: "60px 60px",
}}
></div>
{/* Decorative blobs */}
<div className="absolute top-20 left-10 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl"></div>
<div className="absolute bottom-20 right-10 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl"></div>
<div className="container mx-auto px-4 max-w-4xl relative z-10">
{/* Back button */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
className="mb-8"
>
<Link
href="/"
className="inline-flex items-center gap-2 text-slate-400 hover:text-white transition-colors group"
>
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
Retour à l&apos;accueil
</Link>
</motion.div>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-center mb-12"
>
<div className="inline-flex items-center gap-2 bg-linear-to-r from-blue-500/10 to-purple-500/10 rounded-full px-4 py-2 mb-4">
<Scale className="w-4 h-4 text-blue-400" />
<span className="text-sm font-semibold text-slate-300">
Informations légales
</span>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-white mb-4">
Mentions Légales
</h1>
<p className="text-xl text-slate-400">
Conformément à la loi n° 2004-575 du 21 juin 2004 pour la confiance
dans l&apos;économie numérique
</p>
</motion.div>
{/* Content */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="space-y-8"
>
{/* Éditeur du site */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-blue-500/20 rounded-lg">
<User className="w-5 h-5 text-blue-400" />
</div>
<h2 className="text-xl font-bold text-white">
1. Éditeur du site
</h2>
</div>
<div className="text-slate-300 space-y-2">
<p>
<strong>Nom :</strong> Jessy DAVID
</p>
<p>
<strong>Adresse :</strong> Angers, France
</p>
<p>
<strong>Email :</strong>{" "}
<a
href="mailto:contact@jessy-david.dev"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
contact@jessy-david.dev
</a>
</p>
<p>
<strong>Site web :</strong>{" "}
<a
href="https://jessy-david.dev"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
https://jessy-david.dev
</a>
</p>
</div>
</div>
{/* Hébergeur */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-purple-500/20 rounded-lg">
<FileText className="w-5 h-5 text-purple-400" />
</div>
<h2 className="text-xl font-bold text-white">2. Hébergeur</h2>
</div>
<div className="text-slate-300 space-y-2">
<p>
<strong>Nom :</strong> QuantumCraft Studios
</p>
<p>
<strong>Adresse :</strong> 58 RUE DE MONCEAU 75008 PARIS, FRANCE
</p>
<p>
<strong>Site web :</strong>{" "}
<a
href="https://quantumcraft-studios.com"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
https://quantumcraft-studios.com
</a>
</p>
</div>
</div>
{/* Propriété intellectuelle */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
3. Propriété intellectuelle
</h2>
<div className="text-slate-300 space-y-4">
<p>
L&apos;ensemble du contenu de ce site (textes, images, vidéos,
logos, graphismes, etc.) est la propriété exclusive de Jessy
DAVID, sauf mention contraire. Toute reproduction,
représentation, modification, publication, adaptation de tout ou
partie des éléments du site, quel que soit le moyen ou le
procédé utilisé, est interdite sans l&apos;autorisation écrite
préalable de Jessy DAVID.
</p>
<p>
Toute exploitation non autorisée du site ou de l&apos;un
quelconque des éléments qu&apos;il contient sera considérée
comme constitutive d&apos;une contrefaçon et poursuivie
conformément aux dispositions des articles L.335-2 et suivants
du Code de Propriété Intellectuelle.
</p>
</div>
</div>
{/* Limitation de responsabilité */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
4. Limitation de responsabilité
</h2>
<div className="text-slate-300 space-y-4">
<p>
Jessy DAVID s&apos;efforce d&apos;assurer au mieux de ses
possibilités l&apos;exactitude et la mise à jour des
informations diffusées sur ce site, dont il se réserve le droit
de corriger, à tout moment et sans préavis, le contenu.
Toutefois, Jessy DAVID ne peut garantir l&apos;exactitude, la
précision ou l&apos;exhaustivité des informations mises à
disposition sur ce site.
</p>
<p>
En conséquence, Jessy DAVID décline toute responsabilité pour
toute imprécision, inexactitude ou omission portant sur des
informations disponibles sur ce site.
</p>
</div>
</div>
{/* Liens hypertextes */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
5. Liens hypertextes
</h2>
<div className="text-slate-300 space-y-4">
<p>
Le site jessy-david.dev peut contenir des liens hypertextes vers
d&apos;autres sites. Jessy DAVID n&apos;exerce aucun contrôle
sur ces sites et décline toute responsabilité quant à leur
contenu. La décision d&apos;activer les liens appartient
exclusivement aux visiteurs du site.
</p>
</div>
</div>
{/* Protection des données */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
6. Protection des données personnelles
</h2>
<div className="text-slate-300 space-y-4">
<p>
Conformément au Règlement Général sur la Protection des Données
(RGPD) et à la loi n° 78-17 du 6 janvier 1978 modifiée (loi
Informatique et Libertés), vous disposez d&apos;un droit
d&apos;accès, de rectification, de suppression et
d&apos;opposition aux données personnelles vous concernant.
</p>
<p>
Pour plus d&apos;informations sur la collecte et le traitement
de vos données, veuillez consulter la{" "}
<Link
href="/politique-de-confidentialite"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
Politique de Confidentialité
</Link>
.
</p>
<p>
Pour exercer vos droits, vous pouvez me contacter à
l&apos;adresse suivante :{" "}
<a
href="mailto:contact@jessy-david.dev"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
contact@jessy-david.dev
</a>
</p>
</div>
</div>
{/* Cookies */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">7. Cookies</h2>
<div className="text-slate-300 space-y-4">
<p>
Ce site utilise des cookies pour améliorer l&apos;expérience
utilisateur. Pour plus d&apos;informations sur
l&apos;utilisation des cookies, veuillez consulter la{" "}
<Link
href="/politique-de-confidentialite"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
Politique de Confidentialité
</Link>
.
</p>
</div>
</div>
{/* Loi applicable */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
8. Droit applicable et juridiction
</h2>
<div className="text-slate-300 space-y-4">
<p>
Les présentes mentions légales sont régies par le droit
français. En cas de litige, les tribunaux français seront seuls
compétents.
</p>
</div>
</div>
{/* Date de mise à jour */}
<div className="text-center text-slate-500 text-sm pt-8">
Dernière mise à jour : Janvier 2026
</div>
</motion.div>
</div>
</section>
);
}
+470
View File
@@ -0,0 +1,470 @@
"use client";
import { motion } from "framer-motion";
import {
ArrowLeft,
Cookie,
Database,
Eye,
Lock,
Mail,
Shield,
UserCheck,
} from "lucide-react";
import Link from "next/link";
export default function PolitiqueDeConfidentialite() {
return (
<section className="min-h-screen py-20 bg-slate-900 relative overflow-hidden">
{/* Grid Background */}
<div
className="absolute inset-0 opacity-20"
style={{
backgroundImage:
"linear-gradient(to right, #1e293b 1px, transparent 1px), linear-gradient(to bottom, #1e293b 1px, transparent 1px)",
backgroundSize: "60px 60px",
}}
></div>
{/* Decorative blobs */}
<div className="absolute top-20 left-10 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl"></div>
<div className="absolute bottom-20 right-10 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl"></div>
<div className="container mx-auto px-4 max-w-4xl relative z-10">
{/* Back button */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
className="mb-8"
>
<Link
href="/"
className="inline-flex items-center gap-2 text-slate-400 hover:text-white transition-colors group"
>
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
Retour à l&apos;accueil
</Link>
</motion.div>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-center mb-12"
>
<div className="inline-flex items-center gap-2 bg-linear-to-r from-blue-500/10 to-purple-500/10 rounded-full px-4 py-2 mb-4">
<Shield className="w-4 h-4 text-blue-400" />
<span className="text-sm font-semibold text-slate-300">
Protection de vos données
</span>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-white mb-4">
Politique de Confidentialité
</h1>
<p className="text-xl text-slate-400">
Conformément au RGPD et à la loi n° 78-17 du 6 janvier 1978 modifiée
</p>
</motion.div>
{/* Content */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="space-y-8"
>
{/* Introduction */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-blue-500/20 rounded-lg">
<Lock className="w-5 h-5 text-blue-400" />
</div>
<h2 className="text-xl font-bold text-white">1. Introduction</h2>
</div>
<div className="text-slate-300 space-y-4">
<p>
La présente politique de confidentialité a pour but
d&apos;informer les utilisateurs du site jessy-david.dev de la
manière dont leurs données personnelles sont collectées,
traitées et protégées, conformément au Règlement Général sur la
Protection des Données (RGPD - Règlement UE 2016/679) et à la
loi n° 78-17 du 6 janvier 1978 relative à l&apos;informatique,
aux fichiers et aux libertés, modifiée par la loi n° 2004-801 du
6 août 2004.
</p>
<p>
<strong>Responsable du traitement :</strong> Jessy DAVID
<br />
<strong>Email :</strong>{" "}
<a
href="mailto:contact@jessy-david.dev"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
contact@jessy-david.dev
</a>
</p>
</div>
</div>
{/* Données collectées */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-purple-500/20 rounded-lg">
<Database className="w-5 h-5 text-purple-400" />
</div>
<h2 className="text-xl font-bold text-white">
2. Données personnelles collectées
</h2>
</div>
<div className="text-slate-300 space-y-4">
<p>
Je collecte les données personnelles suivantes dans le cadre de
l&apos;utilisation de ce site :
</p>
<div className="bg-slate-900/50 rounded-xl p-4">
<h3 className="font-semibold text-white mb-3">
Via le formulaire de contact :
</h3>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>
<strong>Nom et prénom</strong> - pour vous identifier
</li>
<li>
<strong>Adresse email</strong> - pour vous répondre
</li>
<li>
<strong>Message</strong> - pour comprendre votre demande
</li>
</ul>
</div>
<div className="bg-slate-900/50 rounded-xl p-4">
<h3 className="font-semibold text-white mb-3">
Données techniques automatiquement collectées :
</h3>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>Adresse IP (anonymisée)</li>
<li>Type de navigateur et système d&apos;exploitation</li>
<li>Pages visitées et durée de visite</li>
<li>
Source de trafic (moteur de recherche, lien direct, etc.)
</li>
</ul>
</div>
</div>
</div>
{/* Finalités */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-green-500/20 rounded-lg">
<Eye className="w-5 h-5 text-green-400" />
</div>
<h2 className="text-xl font-bold text-white">
3. Finalités du traitement
</h2>
</div>
<div className="text-slate-300 space-y-4">
<p>
Les données personnelles sont collectées pour les finalités
suivantes :
</p>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>
<strong>Répondre à vos demandes de contact</strong> - Base
légale : consentement
</li>
<li>
<strong>Améliorer l&apos;expérience utilisateur</strong> -
Base légale : intérêt légitime
</li>
<li>
<strong>Analyser les statistiques de fréquentation</strong> -
Base légale : consentement (cookies)
</li>
<li>
<strong>Assurer la sécurité du site</strong> (protection
anti-spam via Cloudflare Turnstile) - Base légale : intérêt
légitime
</li>
</ul>
</div>
</div>
{/* Durée de conservation */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
4. Durée de conservation des données
</h2>
<div className="text-slate-300 space-y-4">
<p>
Vos données personnelles sont conservées pour une durée limitée
:
</p>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>
<strong>Données du formulaire de contact :</strong> 3 ans à
compter du dernier contact
</li>
<li>
<strong>Cookies analytiques :</strong> 13 mois maximum
</li>
<li>
<strong>Logs de sécurité :</strong> 12 mois
</li>
</ul>
<p>
À l&apos;expiration de ces délais, vos données sont supprimées
ou anonymisées.
</p>
</div>
</div>
{/* Destinataires */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
5. Destinataires des données
</h2>
<div className="text-slate-300 space-y-4">
<p>
Vos données personnelles sont strictement confidentielles et ne
sont communiquées qu&apos;aux destinataires suivants :
</p>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>
<strong>Jessy DAVID</strong> - Responsable du traitement
</li>
<li>
<strong>Hébergeur (QuantumCraft Studios)</strong> - pour le
stockage technique
</li>
<li>
<strong>Cloudflare</strong> - pour la protection anti-spam
(Turnstile)
</li>
<li>
<strong>Fournisseur d&apos;emails</strong> - pour l&apos;envoi
des messages de contact
</li>
</ul>
<p>
Je ne vends, ne loue, ni ne partage vos données personnelles
avec des tiers à des fins commerciales.
</p>
</div>
</div>
{/* Transferts hors UE */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
6. Transferts de données hors UE
</h2>
<div className="text-slate-300 space-y-4">
<p>
Certains de mes prestataires (hébergeur, Cloudflare) peuvent
être situés en dehors de l&apos;Union Européenne. Dans ce cas,
je m&apos;assure que ces transferts sont encadrés par des
garanties appropriées conformément au RGPD (Clauses
Contractuelles Types, Data Privacy Framework, etc.).
</p>
</div>
</div>
{/* Cookies */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-orange-500/20 rounded-lg">
<Cookie className="w-5 h-5 text-orange-400" />
</div>
<h2 className="text-xl font-bold text-white">
7. Cookies et traceurs
</h2>
</div>
<div className="text-slate-300 space-y-4">
<p>
Ce site utilise des cookies pour assurer son bon fonctionnement
et améliorer votre expérience de navigation.
</p>
<div className="bg-slate-900/50 rounded-xl p-4">
<h3 className="font-semibold text-white mb-3">
Cookies strictement nécessaires (exemptés de consentement) :
</h3>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>
<strong>Session</strong> - Maintien de la session de
navigation
</li>
<li>
<strong>Sécurité (Cloudflare Turnstile)</strong> -
Protection anti-spam
</li>
<li>
<strong>Préférences cookies</strong> - Mémorisation de vos
choix
</li>
</ul>
</div>
<div className="bg-slate-900/50 rounded-xl p-4">
<h3 className="font-semibold text-white mb-3">
Cookies analytiques (soumis à consentement) :
</h3>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>
<strong>Google Analytics</strong> - Analyse du trafic et
comportement des visiteurs
</li>
</ul>
</div>
<p>
Vous pouvez à tout moment modifier vos préférences en matière de
cookies via la bannière de gestion des cookies accessible depuis
le bas de page.
</p>
</div>
</div>
{/* Droits des utilisateurs */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-cyan-500/20 rounded-lg">
<UserCheck className="w-5 h-5 text-cyan-400" />
</div>
<h2 className="text-xl font-bold text-white">8. Vos droits</h2>
</div>
<div className="text-slate-300 space-y-4">
<p>
Conformément au RGPD et à la loi Informatique et Libertés, vous
disposez des droits suivants sur vos données personnelles :
</p>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>
<strong>Droit d&apos;accès</strong> - Obtenir une copie de vos
données
</li>
<li>
<strong>Droit de rectification</strong> - Corriger des données
inexactes
</li>
<li>
<strong>Droit à l&apos;effacement</strong> - Demander la
suppression de vos données
</li>
<li>
<strong>Droit à la limitation</strong> - Limiter le traitement
de vos données
</li>
<li>
<strong>Droit d&apos;opposition</strong> - Vous opposer au
traitement
</li>
<li>
<strong>Droit à la portabilité</strong> - Recevoir vos données
dans un format structuré
</li>
<li>
<strong>Droit de retirer votre consentement</strong> - À tout
moment
</li>
</ul>
<div className="bg-blue-500/10 border border-blue-500/20 rounded-xl p-4 mt-4">
<p className="flex items-start gap-2">
<Mail className="w-5 h-5 text-blue-400 mt-0.5 shrink-0" />
<span>
Pour exercer vos droits, contactez-moi à :{" "}
<a
href="mailto:contact@jessy-david.dev"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
contact@jessy-david.dev
</a>
<br />
Je répondrai à votre demande dans un délai maximum de 30
jours.
</span>
</p>
</div>
</div>
</div>
{/* Réclamation CNIL */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
9. Réclamation auprès de la CNIL
</h2>
<div className="text-slate-300 space-y-4">
<p>
Si vous estimez que le traitement de vos données personnelles
constitue une violation de vos droits, vous pouvez introduire
une réclamation auprès de la Commission Nationale de
l&apos;Informatique et des Libertés (CNIL) :
</p>
<div className="bg-slate-900/50 rounded-xl p-4">
<p>
<strong>CNIL</strong>
</p>
<p>3 Place de Fontenoy - TSA 80715</p>
<p>75334 PARIS CEDEX 07</p>
<p>
Site web :{" "}
<a
href="https://www.cnil.fr"
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 transition-colors"
>
www.cnil.fr
</a>
</p>
</div>
</div>
</div>
{/* Sécurité */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
10. Sécurité des données
</h2>
<div className="text-slate-300 space-y-4">
<p>
Je mets en œuvre toutes les mesures techniques et
organisationnelles appropriées pour garantir un niveau de
sécurité adapté au risque :
</p>
<ul className="list-disc list-inside space-y-2 text-slate-400">
<li>Chiffrement SSL/TLS des communications</li>
<li>Protection contre les attaques (Cloudflare)</li>
<li>Accès restreint aux données personnelles</li>
<li>Mises à jour régulières de sécurité</li>
</ul>
</div>
</div>
{/* Modifications */}
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-2xl p-6">
<h2 className="text-xl font-bold text-white mb-4">
11. Modifications de la politique
</h2>
<div className="text-slate-300 space-y-4">
<p>
Je me réserve le droit de modifier cette politique de
confidentialité à tout moment. En cas de modification
substantielle, je vous en informerai par une notification
visible sur ce site. La date de dernière mise à jour sera
toujours indiquée en bas de cette page.
</p>
</div>
</div>
{/* Date de mise à jour */}
<div className="text-center text-slate-500 text-sm pt-8">
Dernière mise à jour : Janvier 2026
</div>
</motion.div>
</div>
</section>
);
}
+48 -1
View File
@@ -2,7 +2,15 @@
import { siteConfig } from "@/config/site"; import { siteConfig } from "@/config/site";
import { motion, type Variants } from "framer-motion"; import { motion, type Variants } from "framer-motion";
import { Github, Heart, Linkedin, Mail } from "lucide-react"; import {
Cookie,
Github,
Heart,
Linkedin,
Mail,
Scale,
Shield,
} from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
@@ -16,6 +24,19 @@ const socials = [
{ name: "Email", href: "mailto:contact@jessy-david.dev", icon: Mail }, { name: "Email", href: "mailto:contact@jessy-david.dev", icon: Mail },
]; ];
const legalLinks = [
{
name: "Mentions légales",
href: "/mentions-legales",
icon: Scale,
},
{
name: "Politique de confidentialité",
href: "/politique-de-confidentialite",
icon: Shield,
},
];
const containerVariants: Variants = { const containerVariants: Variants = {
hidden: { opacity: 0 }, hidden: { opacity: 0 },
visible: { visible: {
@@ -112,6 +133,32 @@ export default function Footer() {
QuantumCraft Studios QuantumCraft Studios
</Link> </Link>
</motion.div> </motion.div>
{/* Liens légaux */}
<motion.div
variants={itemVariants}
className="flex flex-wrap items-center justify-center gap-4"
>
{legalLinks.map((item) => (
<Link
key={item.name}
href={item.href}
className="flex items-center gap-1.5 text-slate-500 hover:text-blue-400 text-sm transition-colors"
>
<item.icon className="w-3.5 h-3.5" />
{item.name}
</Link>
))}
<button
onClick={() =>
window.dispatchEvent(new Event("openCookieSettings"))
}
className="flex items-center gap-1.5 text-slate-500 hover:text-blue-400 text-sm transition-colors cursor-pointer"
>
<Cookie className="w-3.5 h-3.5" />
Gérer les cookies
</button>
</motion.div>
</motion.div> </motion.div>
{/* Copyright */} {/* Copyright */}
+2 -2
View File
@@ -25,7 +25,7 @@ interface NavLink {
// Configuration des liens de navigation avec leurs icônes // Configuration des liens de navigation avec leurs icônes
const navLinks: NavLink[] = [ const navLinks: NavLink[] = [
{ href: "#home", label: "Accueil", icon: Home }, { href: "/", label: "Accueil", icon: Home },
{ href: "#about", label: "À propos", icon: User }, { href: "#about", label: "À propos", icon: User },
{ href: "#skills", label: "Compétences", icon: Code2 }, { href: "#skills", label: "Compétences", icon: Code2 },
{ href: "#projects", label: "Projets", icon: FolderKanban }, { href: "#projects", label: "Projets", icon: FolderKanban },
@@ -90,7 +90,7 @@ export default function NavBar() {
<div className="flex items-center justify-between h-16"> <div className="flex items-center justify-between h-16">
{/* Logo et nom - lien vers l'accueil */} {/* Logo et nom - lien vers l'accueil */}
<SmoothLink <SmoothLink
href="#home" href="/"
className="relative group flex items-center gap-3" className="relative group flex items-center gap-3"
onClick={handleLinkClick} onClick={handleLinkClick}
> >
1
+29
View File
@@ -291,6 +291,35 @@ export default function Contact() {
/> />
</div> </div>
{/* Consentement RGPD */}
<div className="flex items-start gap-3">
<input
type="checkbox"
id="rgpd-consent"
name="rgpd-consent"
required
disabled={isSubmitting}
className="mt-1 w-4 h-4 rounded border-slate-600 bg-slate-900/50 text-blue-500 focus:ring-blue-500 focus:ring-offset-0 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
/>
<label
htmlFor="rgpd-consent"
className="text-sm text-slate-400 cursor-pointer"
>
J&apos;accepte que mes données personnelles (nom, email,
message) soient collectées et traitées pour répondre à ma
demande, conformément à la{" "}
<a
href="/politique-de-confidentialite"
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 underline transition-colors"
>
Politique de Confidentialité
</a>
. *
</label>
</div>
copilot-pull-request-reviewer[bot] commented 2026-01-27 17:12:41 +00:00 (Migrated from github.com)
Review

Le checkbox "Consentement RGPD" est bien required côté UI, mais sa valeur n’est jamais lue dans handleSubmit (seuls name/email/message/captchaToken sont envoyés). Du coup, le backend ne peut pas tracer/valider le consentement (et un appel direct à /api/contact peut contourner la contrainte). Solution : inclure explicitement un champ de consentement (ex: booléen dérivé de formData.get(...)) dans le payload, et le valider côté API.

Le checkbox "Consentement RGPD" est bien `required` côté UI, mais sa valeur n’est jamais lue dans `handleSubmit` (seuls `name/email/message/captchaToken` sont envoyés). Du coup, le backend ne peut pas tracer/valider le consentement (et un appel direct à `/api/contact` peut contourner la contrainte). Solution : inclure explicitement un champ de consentement (ex: booléen dérivé de `formData.get(...)`) dans le payload, et le valider côté API.
{/* Message d'erreur */} {/* Message d'erreur */}
{error && ( {error && (
<motion.div <motion.div
+294
View File
@@ -0,0 +1,294 @@
"use client";
import { motion, AnimatePresence } from "framer-motion";
import { Cookie, X, Check, Settings } from "lucide-react";
import Link from "next/link";
import { useState, useEffect } from "react";
type CookiePreferences = {
necessary: boolean;
analytics: boolean;
};
const COOKIE_CONSENT_KEY = "cookie-consent";
const COOKIE_PREFERENCES_KEY = "cookie-preferences";
function getInitialPreferences(): CookiePreferences {
if (typeof window === "undefined") {
return { necessary: true, analytics: false };
}
const savedPreferences = localStorage.getItem(COOKIE_PREFERENCES_KEY);
if (savedPreferences) {
try {
return JSON.parse(savedPreferences);
} catch {
return { necessary: true, analytics: false };
}
}
return { necessary: true, analytics: false };
copilot-pull-request-reviewer[bot] commented 2026-01-27 17:12:41 +00:00 (Migrated from github.com)
Review

getInitialPreferences fait JSON.parse(savedPreferences) et retourne le résultat tel quel, sans validation. Si le localStorage contient une forme inattendue (ou des types non booléens), l’état preferences peut devenir incohérent (ex: necessary manquant/false alors qu’il est supposé toujours true). Solution : valider/coercer la forme (booleans) et forcer necessary: true au chargement avant de l’utiliser.

`getInitialPreferences` fait `JSON.parse(savedPreferences)` et retourne le résultat tel quel, sans validation. Si le localStorage contient une forme inattendue (ou des types non booléens), l’état `preferences` peut devenir incohérent (ex: `necessary` manquant/false alors qu’il est supposé toujours true). Solution : valider/coercer la forme (booleans) et forcer `necessary: true` au chargement avant de l’utiliser.
}
export default function CookieBanner() {
const [showBanner, setShowBanner] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [preferences, setPreferences] = useState<CookiePreferences>(
getInitialPreferences,
);
useEffect(() => {
// Vérifier si l'utilisateur a déjà donné son consentement
const consent = localStorage.getItem(COOKIE_CONSENT_KEY);
if (!consent) {
// Délai pour ne pas afficher immédiatement au chargement
const timer = setTimeout(() => setShowBanner(true), 1000);
return () => clearTimeout(timer);
} else {
// Appliquer les préférences sauvegardées au chargement
const savedPreferences = localStorage.getItem(COOKIE_PREFERENCES_KEY);
if (savedPreferences) {
try {
const prefs = JSON.parse(savedPreferences);
if (prefs.analytics) {
// @ts-expect-error - gtag n'est pas typé
window.gtag?.("consent", "update", {
analytics_storage: "granted",
});
}
} catch {
// Ignorer les erreurs de parsing
}
}
}
}, []);
const handleAcceptAll = () => {
const newPreferences = { necessary: true, analytics: true };
saveConsent(newPreferences);
};
const handleRejectAll = () => {
const newPreferences = { necessary: true, analytics: false };
saveConsent(newPreferences);
};
const handleSavePreferences = () => {
saveConsent(preferences);
};
const saveConsent = (prefs: CookiePreferences) => {
localStorage.setItem(COOKIE_CONSENT_KEY, "true");
localStorage.setItem(COOKIE_PREFERENCES_KEY, JSON.stringify(prefs));
setPreferences(prefs);
setShowBanner(false);
setShowSettings(false);
// Activer/désactiver Google Analytics selon le consentement
if (prefs.analytics) {
enableAnalytics();
} else {
disableAnalytics();
}
};
const enableAnalytics = () => {
// Active Google Analytics via GTM Consent Mode
if (typeof window !== "undefined") {
// @ts-expect-error - gtag n'est pas typé
window.gtag?.("consent", "update", {
analytics_storage: "granted",
});
// Envoyer un événement pour GTM
// @ts-expect-error - dataLayer n'est pas typé
window.dataLayer?.push({ event: "cookie_consent_analytics" });
}
copilot-pull-request-reviewer[bot] commented 2026-01-27 17:12:42 +00:00 (Migrated from github.com)
Review

Le fichier utilise plusieurs @ts-expect-error pour window.gtag / window.dataLayer. Ici ce n’est pas une erreur ponctuelle mais une API globale attendue : il serait préférable de déclarer ces propriétés dans un fichier de types global (ex: global.d.ts avec interface Window { gtag?: ...; dataLayer?: ... }) pour éviter de masquer d’éventuelles vraies erreurs et réduire le bruit TS.

Le fichier utilise plusieurs `@ts-expect-error` pour `window.gtag` / `window.dataLayer`. Ici ce n’est pas une erreur ponctuelle mais une API globale attendue : il serait préférable de déclarer ces propriétés dans un fichier de types global (ex: `global.d.ts` avec `interface Window { gtag?: ...; dataLayer?: ... }`) pour éviter de masquer d’éventuelles vraies erreurs et réduire le bruit TS.
};
const disableAnalytics = () => {
// Désactive Google Analytics via GTM Consent Mode
if (typeof window !== "undefined") {
// @ts-expect-error - gtag n'est pas typé
window.gtag?.("consent", "update", {
analytics_storage: "denied",
});
}
};
// Fonction pour rouvrir la bannière (appelable depuis le footer)
useEffect(() => {
const handleOpenCookieSettings = () => {
setShowBanner(true);
setShowSettings(true);
};
window.addEventListener("openCookieSettings", handleOpenCookieSettings);
return () =>
window.removeEventListener(
"openCookieSettings",
handleOpenCookieSettings,
);
}, []);
return (
<AnimatePresence>
{showBanner && (
<motion.div
initial={{ y: 100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 100, opacity: 0 }}
transition={{ type: "spring", damping: 25, stiffness: 200 }}
className="fixed bottom-0 left-0 right-0 z-50 p-4 md:p-6"
>
<div className="container mx-auto max-w-4xl">
<div className="bg-slate-800/95 backdrop-blur-xl border border-slate-700 rounded-2xl shadow-2xl shadow-black/20 overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between p-4 md:p-6 border-b border-slate-700">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-500/20 rounded-lg">
<Cookie className="w-5 h-5 text-blue-400" />
</div>
<h3 className="text-lg font-bold text-white">
Gestion des cookies
</h3>
</div>
<button
onClick={() => setShowBanner(false)}
className="p-2 text-slate-400 hover:text-white transition-colors rounded-lg hover:bg-slate-700"
aria-label="Fermer"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Content */}
<div className="p-4 md:p-6">
{!showSettings ? (
<>
<p className="text-slate-300 mb-6">
Ce site utilise des cookies pour améliorer votre
expérience. Les cookies nécessaires sont essentiels au bon
fonctionnement du site. Vous pouvez choisir
d&apos;accepter ou de refuser les cookies analytiques.{" "}
<Link
href="/politique-de-confidentialite"
className="text-blue-400 hover:text-blue-300 transition-colors underline"
>
En savoir plus
</Link>
</p>
{/* Boutons */}
<div className="flex flex-col sm:flex-row gap-3">
<button
onClick={handleRejectAll}
className="flex-1 px-6 py-3 bg-slate-700 hover:bg-slate-600 text-white font-semibold rounded-xl transition-colors"
>
Refuser tout
</button>
<button
onClick={() => setShowSettings(true)}
className="flex-1 px-6 py-3 bg-slate-700 hover:bg-slate-600 text-white font-semibold rounded-xl transition-colors flex items-center justify-center gap-2"
>
<Settings className="w-4 h-4" />
Personnaliser
</button>
<button
onClick={handleAcceptAll}
className="flex-1 px-6 py-3 bg-linear-to-r from-blue-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 text-white font-semibold rounded-xl transition-all"
>
Accepter tout
</button>
</div>
</>
) : (
<>
<p className="text-slate-300 mb-6">
Personnalisez vos préférences de cookies ci-dessous.
</p>
{/* Options de cookies */}
<div className="space-y-4 mb-6">
{/* Cookies nécessaires */}
<div className="flex items-center justify-between p-4 bg-slate-900/50 rounded-xl">
<div className="flex-1">
<h4 className="font-semibold text-white mb-1">
Cookies nécessaires
</h4>
<p className="text-sm text-slate-400">
Essentiels au fonctionnement du site (session,
sécurité, préférences).
</p>
</div>
<div className="ml-4">
<div className="w-12 h-6 bg-blue-600 rounded-full flex items-center justify-end px-1 cursor-not-allowed">
<div className="w-4 h-4 bg-white rounded-full flex items-center justify-center">
<Check className="w-3 h-3 text-blue-600" />
</div>
</div>
</div>
</div>
{/* Cookies analytiques */}
<div className="flex items-center justify-between p-4 bg-slate-900/50 rounded-xl">
<div className="flex-1">
<h4 className="font-semibold text-white mb-1">
Cookies analytiques
</h4>
<p className="text-sm text-slate-400">
Google Analytics pour comprendre comment vous
utilisez ce site.
</p>
</div>
<div className="ml-4">
<button
onClick={() =>
setPreferences((prev) => ({
...prev,
analytics: !prev.analytics,
}))
}
className={`w-12 h-6 rounded-full flex items-center px-1 transition-colors ${
preferences.analytics
? "bg-blue-600 justify-end"
: "bg-slate-600 justify-start"
}`}
aria-label={
preferences.analytics
? "Désactiver les cookies analytiques"
: "Activer les cookies analytiques"
}
>
<div className="w-4 h-4 bg-white rounded-full flex items-center justify-center">
{preferences.analytics && (
<Check className="w-3 h-3 text-blue-600" />
)}
</div>
</button>
</div>
</div>
</div>
{/* Boutons */}
<div className="flex flex-col sm:flex-row gap-3">
<button
onClick={() => setShowSettings(false)}
className="flex-1 px-6 py-3 bg-slate-700 hover:bg-slate-600 text-white font-semibold rounded-xl transition-colors"
>
Retour
</button>
<button
onClick={handleSavePreferences}
className="flex-1 px-6 py-3 bg-linear-to-r from-blue-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 text-white font-semibold rounded-xl transition-all"
>
Sauvegarder mes choix
</button>
</div>
</>
)}
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
);
}