Stack et structure
Le frontend web est l'interface navigateur de BlockLucky. Il réunit dans une seule application Next.js quatre espaces : la page publique de la Ville, l'espace citoyen, le dashboard de la Mairie et l'espace des commerçants partenaires. Il ne contient aucune logique blockchain : il appelle l'API REST du backend et écoute ses événements Socket.IO. Les transactions sont signées côté serveur par le portefeuille intégré de chaque citoyen (voir Portefeuilles intégrés).
Le code se trouve dans frontend-web/. Toutes les pages sont des composants client ('use client') : l'App Router sert surtout de routeur de fichiers et de coquille HTML, pas de rendu serveur de données.
Stack technique
Les versions sont épinglées sans plage dans frontend-web/package.json.
| Rôle | Paquet | Version |
|---|---|---|
| Framework et routeur | next (App Router) |
16.4.5 |
| Bibliothèque UI | react, react-dom |
19.3.1 |
| État global et appels API | @reduxjs/toolkit (RTK Query), react-redux |
2.14.0, 9.3.1 |
| Temps réel | socket.io-client |
4.8.4 |
| Styles | tailwindcss, @tailwindcss/postcss |
4.3.5 |
| Langage | typescript |
~6.0.3 |
| Tests | vitest, @testing-library/react, jsdom |
5.1.0, 16.3.3, 31.0.0 |
| Qualité | eslint, eslint-config-next |
10.14.0, 16.4.5 |
Node.js 22.12 ou plus est requis (engines). La CI lit la version dans le fichier .nvmrc à la racine du dépôt.
Aucune bibliothèque de composants, d'icônes, de graphiques ou de formulaires n'est installée. Les graphiques du dashboard sont dessinés en SVG et en CSS dans frontend-web/src/components/charts/Charts.tsx. Les icônes sont des SVG recopiés depuis l'application mobile dans frontend-web/src/components/ui/iconData.ts, et la police Inter est fournie en fichiers locaux (voir Design system). Il n'y a pas non plus de bibliothèque Web3 (ni ethers, ni wagmi, ni connecteur MetaMask) : les citoyens n'utilisent pas d'extension de portefeuille.
Arborescence
frontend-web/
├── __tests__/ tests Vitest (lib.test.ts, components.test.tsx, consent.test.tsx)
├── public/
│ ├── .well-known/ security.txt
│ └── sounds/ roue-tirage.mp3 et chrono.mp3, fournis par l'équipe
├── src/
│ ├── app/ routes App Router, layouts, globals.css, icon.svg (favicon)
│ │ ├── fonts/ Inter en 5 graisses (.ttf) et LICENSE-inter.txt
│ │ ├── (citoyen)/ espace citoyen (groupe de routes sans segment d'URL)
│ │ ├── mairie/ dashboard Mairie
│ │ ├── partenaire/ espace partenaires
│ │ └── ... pages publiques et légales (cgu, reglement, traceurs...), connexion, erreurs
│ ├── components/
│ │ ├── auth/ OnboardingCarousel
│ │ ├── charts/ BarList, RingMeter, KpiTile
│ │ ├── layout/ AppShell, PublicShell, LegalPage, ConsentBanner, Brand, SecurityFooter, RequireRole...
│ │ ├── lottery/ WalletCard, WinnerClaimCard, CountdownTimer, SoundToggle, ChatBox...
│ │ ├── marketplace/ PointsBalanceCard, RewardCard, ScratchCanvas
│ │ ├── partner/ PartnerSelection (choix de l'établissement)
│ │ └── ui/ Button, Input, Card, Sheet, Loader, Icon (et iconData), Misc...
│ ├── content/ legal.ts : textes des six documents légaux, en français et en anglais
│ ├── hooks/ useAuth, useBlockLuckyWallet, useAgeVerification, useSocket, usePoints, useSound...
│ ├── lib/ config, storage, analytics, consent, legalPaths, socket, validators...
│ ├── store/ store Redux, RTK Query (api/) et slices
│ ├── theme/ ThemeProvider (thème et langue)
│ └── types/ types des réponses de l'API
├── Dockerfile
├── next.config.mjs en-têtes de sécurité, sortie standalone
├── vitest.config.mts
└── .env.exampleLe dossier (citoyen) est un groupe de routes : son nom n'apparaît pas dans l'URL. La page frontend-web/src/app/(citoyen)/accueil/page.tsx répond donc à /accueil. Le détail des 35 pages est dans Pages et espaces.
Point d'entrée et providers
frontend-web/src/app/layout.tsx est le layout racine. Il définit les métadonnées (titre, description, Open Graph en fr_FR), la couleur de thème du navigateur pour les modes clair et sombre, et injecte dans <head> un petit script inline (themeInitScript) qui applique le thème et la langue avant le premier rendu pour éviter un flash. Il enveloppe ensuite toute l'application dans Providers.
frontend-web/src/app/providers.tsx monte, dans cet ordre :
- le
ProviderRedux, avec un store créé une seule fois parmakeStore; - le
ThemeProvider(thème clair ou sombre, français ou anglais) ; SessionBootstrap, qui au montage efface la source UTM stockée si la mesure d'audience n'est pas acceptée (discardAudienceDataWithoutConsent), captureutm_sourcedans l'URL, puis restaure la session depuislocalStorage(hydrateSession) ;- les pages ;
GlobalOverlays, les feuilles modales globales (points crédités, réseau encombré, file d'attente, erreur réseau, tiroir de notifications) ;ConsentBanner, le bandeau de choix des traceurs (voir Analytique et consentement).
flowchart TD L["layout.tsx : html, head, themeInitScript"] --> P["Providers"] P --> R["Provider Redux"] R --> T["ThemeProvider"] T --> S["SessionBootstrap : UTM et session"] T --> C["Pages de src/app"] T --> O["GlobalOverlays"] T --> B["ConsentBanner"] C --> G["Layouts d'espace : RequireRole et AppShell"]
Les quatre espaces et leurs layouts
| Espace | Layout | Garde | Coquille |
|---|---|---|---|
| Public | aucun layout dédié | aucune | PublicShell ou page autonome |
| Citoyen | frontend-web/src/app/(citoyen)/layout.tsx |
RequireRole sans rôle : tout compte connecté |
AppShell avec actions citoyennes (portefeuille, points, alertes) |
| Mairie | frontend-web/src/app/mairie/layout.tsx |
RequireRole roles={['ADMIN']} |
AppShell |
| Partenaires | frontend-web/src/app/partenaire/layout.tsx |
RequireRole roles={['PARTNER', 'ADMIN']} |
PartnerSelectionProvider puis AppShell |
Chaque layout déclare sa liste d'entrées de navigation (NavItem). AppShell affiche une barre latérale fixe à partir de 1024 px (lg) et, en dessous, une barre d'onglets basse avec les entrées sans desktopOnly. Au-delà de cinq entrées (MAX_MOBILE_ITEMS), la barre montre les quatre premières et un bouton « Plus » qui ouvre un petit menu avec les suivantes ; le menu se ferme au clic sur un lien, avec Échap, au clic en dehors ou au changement de page. L'espace Mairie compte sept entrées (Pilotage, Analytique, Partenaires, Identités, Comptes, Sécurité, Audit) : sous 1024 px, « Comptes », « Sécurité » et « Audit » sont dans le menu « Plus ». L'espace citoyen, qui en a cinq, n'a pas de bouton « Plus ». Le fonctionnement de la garde est détaillé dans Authentification et routes protégées.
Organisation du code applicatif
| Dossier | Contenu | Page détaillée |
|---|---|---|
src/store |
Store Redux, baseApi RTK Query et cinq fichiers d'endpoints, trois slices |
État et appels API |
src/hooks |
Hooks métier qui combinent store, API et effets | Authentification, Portefeuille et loterie, Temps réel |
src/lib |
Fonctions sans React : configuration, stockage, analytique, client Socket.IO, validateurs, formatage, export | voir ci-dessous |
src/components/ui |
Composants de base du design system | Design system |
src/theme |
Thème et traduction t(fr, en) |
Internationalisation |
Fichiers de src/lib :
| Fichier | Rôle |
|---|---|
config.ts |
URL de l'API et du WebSocket, constantes d'affichage (prix 0,01 ETH, 50 points par ticket, 75 points par cadeau, objectif 10 000 participants) |
storage.ts |
Accès protégé à localStorage et liste des clés bl.* |
analytics.ts |
Événements du parcours et source UTM, envoyés seulement avec l'accord du visiteur |
consent.ts |
Choix de mesure d'audience (bl.consent.choice, 6 mois), ouverture du gestionnaire « Gérer mes traceurs » |
legalPaths.ts |
URL de chaque document légal (/cgu, /reglement, /mentions-legales, /confidentialite, /traceurs, /transparence) |
socket.ts |
Client Socket.IO unique |
navigation.ts |
safeInternalPath, filtre anti redirection ouverte |
validators.ts |
Email, adresse Ethereum, code OTP à 6 chiffres |
formatters.ts, date.ts |
Raccourcis d'adresse et de hash, montants, dates |
download.ts |
Téléchargement de fichiers texte et rapport imprimable |
codes.ts |
Analyse des codes promo collés ou importés en CSV |
wallet.ts |
Adresse factice calculée depuis l'email, envoyée au login (voir Authentification) |
L'alias @/ pointe vers src/ (tsconfig.json et vitest.config.mts).
Configuration
Deux variables publiques, lues au build et intégrées au bundle, sont documentées dans frontend-web/.env.example :
| Variable | Valeur par défaut | Usage |
|---|---|---|
NEXT_PUBLIC_API_URL |
http://localhost:2053/api |
Base des appels RTK Query et de trackEvent |
NEXT_PUBLIC_WS_URL |
http://localhost:2053 |
Serveur Socket.IO |
Les mêmes valeurs servent de repli dans frontend-web/src/lib/config.ts et sont reprises par frontend-web/next.config.mjs pour construire la directive connect-src de la CSP. Une troisième variable, NEXT_OUTPUT=standalone, n'est utilisée qu'au build Docker (voir Tests et build). Le préfixe NEXT_PUBLIC_ rend la valeur visible dans le JavaScript du navigateur : aucun secret ne doit y être placé.
Important
Les variables NEXT_PUBLIC_* sont figées au moment de npm run build. Changer l'URL de l'API impose de reconstruire l'application ou l'image Docker, un redémarrage ne suffit pas.
Scripts npm
| Commande | Effet |
|---|---|
npm run dev |
Serveur de développement sur le port 3000 |
npm run build |
Build de production |
npm run start |
Serveur de production sur le port 3000 |
npm run lint |
ESLint avec les règles core-web-vitals et typescript de Next.js |
npm run typecheck |
tsc --noEmit |
npm test |
Tests Vitest en une passe |
npm run test:watch |
Vitest en mode surveillance |
Le démarrage complet avec le backend est décrit dans Lancer les applications.
Code mort à connaître
Le hook useGaslessWeb3.ts, qui présentait les achats comme « sans frais » (gasless), a été supprimé : plus aucun fichier ne l'importait. Il est remplacé par useBlockLuckyWallet : chaque achat est signé par le portefeuille intégré du citoyen et paie les frais réseau sur sa dotation d'ethers fictifs.