Skip to content

Commit ec88241

Browse files
feat(frontend): add RGPD compliance features
- Add privacy policy page with data retention documentation - Add informational cookie banner (localStorage only, no tracking) - Add account deletion modal with password confirmation in Settings - Disable self-deletion for Employee/Admin roles with explanation - Add footer link to privacy policy
1 parent 1164b4d commit ec88241

File tree

10 files changed

+389
-20
lines changed

10 files changed

+389
-20
lines changed

src/frontend/src/App.tsx

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
import { Routes, Route } from 'react-router-dom';
22
import { HomePage, RegisterPage, LoginPage, ForgotPasswordPage, DashboardPage, ContactPage, UnauthorizedPage, CreateCharacterPage, EditCharacterPage, CharacterDetailPage, GalleryPage, LegalPage, ModerationPage, AdminPage, SettingsPage } from './pages';
33
import { ProtectedRoute } from './components/auth';
4+
import { CookieBanner } from './components/layout';
45

56
function App() {
67
return (
7-
<Routes>
8-
<Route path="/" element={<HomePage />} />
9-
<Route path="/register" element={<RegisterPage />} />
10-
<Route path="/login" element={<LoginPage />} />
11-
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
12-
<Route path="/dashboard" element={<ProtectedRoute requiredRole="User"><DashboardPage /></ProtectedRoute>} />
13-
<Route path="/characters/create" element={<ProtectedRoute requiredRole="User"><CreateCharacterPage /></ProtectedRoute>} />
14-
<Route path="/characters/:id/edit" element={<ProtectedRoute requiredRole="User"><EditCharacterPage /></ProtectedRoute>} />
15-
<Route path="/characters/:id" element={<CharacterDetailPage />} />
16-
<Route path="/galerie" element={<GalleryPage />} />
17-
<Route path="/moderation" element={<ProtectedRoute requiredRole="Employee"><ModerationPage /></ProtectedRoute>} />
18-
<Route path="/administration" element={<ProtectedRoute requiredRole="Admin"><AdminPage /></ProtectedRoute>} />
19-
<Route path="/parametres" element={<ProtectedRoute requiredRole="User"><SettingsPage /></ProtectedRoute>} />
20-
<Route path="/contact" element={<ContactPage />} />
21-
<Route path="/mentions-legales" element={<LegalPage slug="mentions-legales" />} />
22-
<Route path="/cgu" element={<LegalPage slug="cgu" />} />
23-
<Route path="/unauthorized" element={<UnauthorizedPage />} />
24-
</Routes>
8+
<>
9+
<Routes>
10+
<Route path="/" element={<HomePage />} />
11+
<Route path="/register" element={<RegisterPage />} />
12+
<Route path="/login" element={<LoginPage />} />
13+
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
14+
<Route path="/dashboard" element={<ProtectedRoute requiredRole="User"><DashboardPage /></ProtectedRoute>} />
15+
<Route path="/characters/create" element={<ProtectedRoute requiredRole="User"><CreateCharacterPage /></ProtectedRoute>} />
16+
<Route path="/characters/:id/edit" element={<ProtectedRoute requiredRole="User"><EditCharacterPage /></ProtectedRoute>} />
17+
<Route path="/characters/:id" element={<CharacterDetailPage />} />
18+
<Route path="/galerie" element={<GalleryPage />} />
19+
<Route path="/moderation" element={<ProtectedRoute requiredRole="Employee"><ModerationPage /></ProtectedRoute>} />
20+
<Route path="/administration" element={<ProtectedRoute requiredRole="Admin"><AdminPage /></ProtectedRoute>} />
21+
<Route path="/parametres" element={<ProtectedRoute requiredRole="User"><SettingsPage /></ProtectedRoute>} />
22+
<Route path="/contact" element={<ContactPage />} />
23+
<Route path="/mentions-legales" element={<LegalPage slug="mentions-legales" />} />
24+
<Route path="/cgu" element={<LegalPage slug="cgu" />} />
25+
<Route path="/politique-de-confidentialite" element={<LegalPage slug="politique-de-confidentialite" />} />
26+
<Route path="/unauthorized" element={<UnauthorizedPage />} />
27+
</Routes>
28+
<CookieBanner />
29+
</>
2530
);
2631
}
2732

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { useState, type FormEvent } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
import { Modal, ModalHeader, ModalBody, ModalFooter } from '../ui/Modal/Modal';
4+
import { useAuth } from '../../context/AuthContext';
5+
import { accountService } from '../../services/accountService';
6+
import type { ApiError } from '../../services/api';
7+
8+
interface DeleteAccountModalProps {
9+
isOpen: boolean;
10+
onClose: () => void;
11+
}
12+
13+
const DeleteAccountModal = ({ isOpen, onClose }: DeleteAccountModalProps) => {
14+
const { token, logout } = useAuth();
15+
const navigate = useNavigate();
16+
const [password, setPassword] = useState('');
17+
const [isSubmitting, setIsSubmitting] = useState(false);
18+
const [error, setError] = useState<string | null>(null);
19+
20+
const handleClose = () => {
21+
setPassword('');
22+
setError(null);
23+
onClose();
24+
};
25+
26+
const handleSubmit = async (e: FormEvent) => {
27+
e.preventDefault();
28+
setError(null);
29+
setIsSubmitting(true);
30+
31+
try {
32+
await accountService.deleteAccount({ password }, token!);
33+
logout();
34+
navigate('/', { state: { accountDeleted: true } });
35+
} catch (err) {
36+
const apiError = err as ApiError;
37+
setError(apiError.message || 'Une erreur est survenue lors de la suppression.');
38+
} finally {
39+
setIsSubmitting(false);
40+
}
41+
};
42+
43+
return (
44+
<Modal isOpen={isOpen} onClose={handleClose} size="md" closeOnOverlay={false}>
45+
<form onSubmit={handleSubmit}>
46+
<ModalHeader onClose={handleClose}>
47+
Supprimer mon compte
48+
</ModalHeader>
49+
50+
<ModalBody>
51+
<div
52+
className="mb-4 p-4 bg-red-500/10 border border-red-500/30 rounded-lg"
53+
role="alert"
54+
>
55+
<p className="text-red-400 text-sm font-medium mb-2">
56+
Cette action est irréversible.
57+
</p>
58+
<p className="text-red-400/80 text-sm">
59+
Toutes vos données seront définitivement supprimées : personnages,
60+
commentaires et informations de compte. Un email de confirmation
61+
vous sera envoyé.
62+
</p>
63+
</div>
64+
65+
{error && (
66+
<div
67+
className="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-red-400 text-sm"
68+
role="alert"
69+
>
70+
{error}
71+
</div>
72+
)}
73+
74+
<div>
75+
<label
76+
htmlFor="delete-account-password"
77+
className="block text-sm font-medium text-cream-300 mb-1.5"
78+
>
79+
Confirmez votre mot de passe
80+
</label>
81+
<input
82+
id="delete-account-password"
83+
type="password"
84+
autoComplete="current-password"
85+
value={password}
86+
onChange={(e) => setPassword(e.target.value)}
87+
className="w-full px-4 py-2.5 bg-dark-800 border border-dark-600 rounded-lg text-cream-100 placeholder-dark-400 focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent transition-colors"
88+
placeholder="Votre mot de passe actuel"
89+
required
90+
/>
91+
</div>
92+
</ModalBody>
93+
94+
<ModalFooter>
95+
<button
96+
type="button"
97+
onClick={handleClose}
98+
className="px-4 py-2.5 text-sm font-medium text-cream-300 hover:text-cream-100 bg-dark-700 hover:bg-dark-600 rounded-lg transition-colors cursor-pointer"
99+
>
100+
Annuler
101+
</button>
102+
<button
103+
type="submit"
104+
disabled={!password || isSubmitting}
105+
className="px-4 py-2.5 text-sm font-semibold text-white bg-red-600 hover:bg-red-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:ring-offset-dark-900 cursor-pointer"
106+
>
107+
{isSubmitting ? 'Suppression en cours...' : 'Supprimer définitivement'}
108+
</button>
109+
</ModalFooter>
110+
</form>
111+
</Modal>
112+
);
113+
};
114+
115+
export { DeleteAccountModal };

src/frontend/src/components/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export { LoginForm } from './LoginForm';
33
export { ForgotPasswordForm } from './ForgotPasswordForm';
44
export { PasswordStrengthIndicator } from './PasswordStrengthIndicator';
55
export { ChangePasswordModal } from './ChangePasswordModal';
6+
export { DeleteAccountModal } from './DeleteAccountModal';
67
export { ProtectedRoute } from './ProtectedRoute';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useState } from 'react';
2+
import { Link } from 'react-router-dom';
3+
4+
const STORAGE_KEY = 'fantasyrealm_cookie_dismissed';
5+
6+
const CookieBanner = () => {
7+
const [isDismissed, setIsDismissed] = useState(
8+
() => localStorage.getItem(STORAGE_KEY) === 'true',
9+
);
10+
11+
if (isDismissed) return null;
12+
13+
const handleDismiss = () => {
14+
localStorage.setItem(STORAGE_KEY, 'true');
15+
setIsDismissed(true);
16+
};
17+
18+
return (
19+
<div
20+
role="region"
21+
aria-label="Information sur les cookies et le stockage local"
22+
className="fixed bottom-0 left-0 right-0 z-50 border-t border-dark-700 bg-dark-900/95 backdrop-blur-sm px-4 py-4 sm:px-6"
23+
>
24+
<div className="max-w-5xl mx-auto flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4">
25+
<p className="text-sm text-cream-300 flex-1">
26+
Ce site n'utilise aucun cookie de traçage ou publicitaire. Seul un
27+
jeton d'authentification est stocké dans votre navigateur pour
28+
maintenir votre session.{' '}
29+
<Link
30+
to="/politique-de-confidentialite"
31+
className="text-gold-400 hover:text-gold-300 underline transition-colors"
32+
>
33+
En savoir plus
34+
</Link>
35+
</p>
36+
<button
37+
type="button"
38+
onClick={handleDismiss}
39+
className="shrink-0 px-4 py-2 bg-gold-500 text-dark-950 text-sm font-semibold rounded-lg hover:bg-gold-400 focus:outline-none focus:ring-2 focus:ring-gold-500 focus:ring-offset-2 focus:ring-offset-dark-900 transition-colors cursor-pointer"
40+
>
41+
J'ai compris
42+
</button>
43+
</div>
44+
</div>
45+
);
46+
};
47+
48+
export default CookieBanner;

src/frontend/src/components/layout/Footer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const Footer = () => {
77
left: [
88
{ label: 'Mentions légales', to: '/mentions-legales' },
99
{ label: 'CGU', to: '/cgu' },
10+
{ label: 'Confidentialité', to: '/politique-de-confidentialite' },
1011
],
1112
right: [
1213
{ label: 'Contact', to: '/contact' },
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as Header } from './Header';
22
export { default as Footer } from './Footer';
3+
export { default as CookieBanner } from './CookieBanner';

src/frontend/src/data/legalContent.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,141 @@ export const legalDocuments: Record<string, LegalDocument> = {
166166
},
167167
],
168168
},
169+
170+
'politique-de-confidentialite': {
171+
title: 'Politique de confidentialité',
172+
lastUpdated: '2025-01-01',
173+
sections: [
174+
{
175+
title: 'Responsable du traitement',
176+
content: `
177+
<p><strong>PixelVerse Studios SAS</strong> (société fictive)</p>
178+
<ul>
179+
<li>Siège social : 42 rue du Pixel, 75001 Paris, France</li>
180+
<li>Contact DPO : contact@pixelverse-studios.com</li>
181+
</ul>
182+
<p>PixelVerse Studios, en tant que responsable du traitement, s'engage à protéger la vie privée des utilisateurs de la plateforme FantasyRealm Character Manager, conformément au Règlement Général sur la Protection des Données (RGPD — Règlement UE 2016/679) et à la loi Informatique et Libertés du 6 janvier 1978 modifiée.</p>
183+
`,
184+
},
185+
{
186+
title: 'Données collectées',
187+
content: `
188+
<p>Dans le cadre de l'utilisation du service, les données personnelles suivantes sont collectées :</p>
189+
<ul>
190+
<li><strong>Adresse email</strong> : utilisée pour l'inscription, la connexion, la réinitialisation de mot de passe et les notifications</li>
191+
<li><strong>Pseudo</strong> : identifiant public choisi lors de l'inscription</li>
192+
<li><strong>Mot de passe</strong> : stocké uniquement sous forme de hash sécurisé (algorithme Argon2id), jamais en clair</li>
193+
<li><strong>Personnages créés</strong> : données de personnalisation (nom, genre, traits faciaux, accessoires)</li>
194+
<li><strong>Commentaires</strong> : contenus textuels publiés sur les fiches de personnages</li>
195+
<li><strong>Journaux d'activité</strong> : actions de modération et d'administration (stockés dans MongoDB)</li>
196+
</ul>
197+
<p>Aucune donnée sensible au sens de l'article 9 du RGPD n'est collectée (origines, opinions politiques, données de santé, etc.).</p>
198+
`,
199+
},
200+
{
201+
title: 'Finalités du traitement',
202+
content: `
203+
<p>Les données personnelles sont traitées pour les finalités suivantes :</p>
204+
<ul>
205+
<li><strong>Gestion des comptes utilisateurs</strong> : inscription, authentification, gestion du profil</li>
206+
<li><strong>Fourniture du service</strong> : création et gestion de personnages, publication de commentaires</li>
207+
<li><strong>Modération</strong> : vérification des contenus créés par les utilisateurs (personnages, commentaires)</li>
208+
<li><strong>Sécurité</strong> : détection des comportements abusifs, traçabilité des actions d'administration</li>
209+
<li><strong>Communication</strong> : envoi d'emails transactionnels (confirmation, réinitialisation, notifications de modération)</li>
210+
</ul>
211+
`,
212+
},
213+
{
214+
title: 'Base légale des traitements',
215+
content: `
216+
<p>Les traitements de données reposent sur les bases légales suivantes :</p>
217+
<ul>
218+
<li><strong>Consentement</strong> (article 6.1.a du RGPD) : lors de l'inscription, l'utilisateur consent au traitement de ses données</li>
219+
<li><strong>Exécution du contrat</strong> (article 6.1.b) : les données sont nécessaires à la fourniture du service (création de personnages, commentaires)</li>
220+
<li><strong>Intérêt légitime</strong> (article 6.1.f) : la modération des contenus et la sécurité de la plateforme</li>
221+
</ul>
222+
`,
223+
},
224+
{
225+
title: 'Durée de conservation des données',
226+
content: `
227+
<p>Les données personnelles sont conservées selon les durées suivantes :</p>
228+
<ul>
229+
<li><strong>Comptes actifs</strong> : les données sont conservées tant que le compte utilisateur est actif</li>
230+
<li><strong>Suppression de compte</strong> : en cas de suppression du compte (par l'utilisateur ou par un modérateur), toutes les données personnelles sont supprimées immédiatement et définitivement (email, pseudo, mot de passe hashé, personnages, commentaires)</li>
231+
<li><strong>Journaux d'activité</strong> (MongoDB) : conservés 12 mois à des fins d'audit de sécurité, puis supprimés automatiquement</li>
232+
<li><strong>Messages de contact</strong> : les messages envoyés via le formulaire de contact sont conservés 6 mois</li>
233+
<li><strong>Comptes suspendus</strong> : les données sont conservées pendant la durée de la suspension. L'utilisateur peut demander la suppression de son compte à tout moment</li>
234+
</ul>
235+
`,
236+
},
237+
{
238+
title: 'Destinataires des données',
239+
content: `
240+
<p>Les données personnelles ne sont transmises à aucun tiers à des fins commerciales ou publicitaires.</p>
241+
<p>Les seuls destinataires sont les prestataires techniques nécessaires au fonctionnement du service :</p>
242+
<ul>
243+
<li><strong>Vercel Inc.</strong> (États-Unis) : hébergement du frontend</li>
244+
<li><strong>Railway Corp.</strong> (États-Unis) : hébergement du backend et de la base de données PostgreSQL</li>
245+
<li><strong>MongoDB, Inc.</strong> (États-Unis) : hébergement de la base de données des journaux d'activité</li>
246+
<li><strong>Brevo (Sendinblue)</strong> (France) : envoi d'emails transactionnels</li>
247+
</ul>
248+
<p>Ces prestataires agissent en qualité de sous-traitants au sens du RGPD et sont soumis à des obligations contractuelles de protection des données.</p>
249+
`,
250+
},
251+
{
252+
title: 'Vos droits',
253+
content: `
254+
<p>Conformément au RGPD et à la loi Informatique et Libertés, vous disposez des droits suivants sur vos données personnelles :</p>
255+
<ul>
256+
<li><strong>Droit d'accès</strong> (article 15) : obtenir la confirmation que vos données sont traitées et en recevoir une copie</li>
257+
<li><strong>Droit de rectification</strong> (article 16) : corriger vos données inexactes ou incomplètes</li>
258+
<li><strong>Droit à l'effacement</strong> (article 17) : demander la suppression de vos données. Vous pouvez supprimer votre compte directement depuis la page Paramètres</li>
259+
<li><strong>Droit à la portabilité</strong> (article 20) : recevoir vos données dans un format structuré et lisible par machine</li>
260+
<li><strong>Droit à la limitation</strong> (article 18) : demander la limitation du traitement de vos données</li>
261+
<li><strong>Droit d'opposition</strong> (article 21) : vous opposer au traitement de vos données</li>
262+
</ul>
263+
<p>Pour exercer ces droits, vous pouvez :</p>
264+
<ul>
265+
<li>Supprimer votre compte directement depuis la page <strong>Paramètres</strong> de votre espace personnel</li>
266+
<li>Nous contacter par email à <strong>contact@pixelverse-studios.com</strong></li>
267+
<li>Utiliser le <strong>formulaire de contact</strong> disponible sur le site</li>
268+
</ul>
269+
<p>Nous nous engageons à répondre à votre demande dans un délai d'un mois. En cas de réclamation, vous pouvez également contacter la CNIL (Commission Nationale de l'Informatique et des Libertés) : <strong>www.cnil.fr</strong>.</p>
270+
`,
271+
},
272+
{
273+
title: 'Mesures de sécurité',
274+
content: `
275+
<p>PixelVerse Studios met en œuvre les mesures techniques et organisationnelles suivantes pour protéger vos données :</p>
276+
<ul>
277+
<li><strong>Hachage des mots de passe</strong> : algorithme Argon2id avec paramètres conformes aux recommandations OWASP (sel de 16 octets, hash de 32 octets, 65 536 KiB de mémoire, 3 itérations)</li>
278+
<li><strong>Chiffrement des communications</strong> : toutes les communications sont chiffrées via HTTPS (TLS)</li>
279+
<li><strong>Authentification sécurisée</strong> : jetons JWT avec expiration, transmis uniquement via des connexions sécurisées</li>
280+
<li><strong>Contrôle d'accès</strong> : système de rôles (Utilisateur, Employé, Administrateur) avec autorisations granulaires</li>
281+
<li><strong>Politique de mots de passe</strong> : minimum 12 caractères, 1 majuscule, 1 minuscule, 1 chiffre, 1 caractère spécial (conforme aux recommandations CNIL)</li>
282+
</ul>
283+
`,
284+
},
285+
{
286+
title: 'Cookies et traceurs',
287+
content: `
288+
<p>Ce site <strong>n'utilise aucun cookie</strong> de traçage, publicitaire ou analytique.</p>
289+
<p>Le seul mécanisme de stockage local utilisé est le <strong>localStorage</strong> du navigateur, qui contient :</p>
290+
<ul>
291+
<li>Un jeton d'authentification (JWT) pour maintenir la session de l'utilisateur connecté</li>
292+
<li>La préférence de fermeture du bandeau d'information cookies</li>
293+
</ul>
294+
<p>Ces données sont stockées uniquement sur votre appareil et ne sont jamais transmises à des tiers. Elles sont automatiquement supprimées lors de la déconnexion ou du nettoyage des données du navigateur.</p>
295+
`,
296+
},
297+
{
298+
title: 'Modification de cette politique',
299+
content: `
300+
<p>PixelVerse Studios se réserve le droit de modifier la présente politique de confidentialité à tout moment. En cas de modification substantielle, les utilisateurs seront informés par email.</p>
301+
<p>La date de dernière mise à jour est indiquée en haut de cette page.</p>
302+
`,
303+
},
304+
],
305+
},
169306
};

0 commit comments

Comments
 (0)