# 🏁 Énoncé FINAL — « Boss de fin » 😈
Dernier jour ! 🎉 Tu as déjà tout l'arsenal : composants, props, state, lifting state up, `useEffect`, **`fetch`**, formulaire, tri, filtre, **React Router**, **`localStorage`**. Aujourd'hui on **s'amuse** : 4 modules courts et stylés. Fais-en autant que tu veux, dans l'ordre que tu veux — mais le **Module L** est le vrai boss, attaque-le en premier. 💪
| Module | Tu fais | Niveau | Fun |
|---|---|---|---|
| L | La page détail **complète** (state remonté au routeur) | ⭐⭐⭐ | 🔑 |
| M | Bouton **🎲 « Au hasard »** (page d'un jeu aléatoire) | ⭐⭐ | 😄 |
| N | **🏆 Le Hall of Fame** des jeux les plus likés | ⭐⭐ | 🥇 |
| O | **🌗 Thème sombre** qui se souvient de ton choix | ⭐⭐ | 🌙 |
---
## 🔑 Module L — Le boss : centraliser les données dans `App`
Dans `CORRECTION_page_detail.md` tu as fait une version simple (la page détail re-`fetch` toute seule). Ses limites : les jeux **ajoutés au formulaire** et les **likes** n'arrivent pas dans le détail, parce que chacun vit dans son coin. On corrige ça **proprement**.
**L'idée :** le `fetch`, la liste `jeux` **et** les likes doivent vivre **au-dessus** des deux pages → dans `App`. `App` les passe ensuite **en props** aux routes.
### L.1 — Remonter le `fetch` et les états dans `App.jsx`
```jsx
import { useState, useEffect } from "react";
import { Routes, Route } from "react-router-dom";
import Header from "./components/Header";
import Footer from "./components/Footer";
import Accueil from "./pages/Accueil";
import Detail from "./pages/Detail";
function App() {
const [jeux, setJeux] = useState([]);
const [likes, setLikes] = useState(() =>
JSON.parse(localStorage.getItem("likes") || "{}")
);
useEffect(() => {
fetch("/zelda.json").then((r) => r.json()).then(setJeux);
}, []);
useEffect(() => {
localStorage.setItem("likes", JSON.stringify(likes));
}, [likes]);
function liker(id) {
setLikes((prev) => ({ ...prev, [id]: (prev[id] || 0) + 1 }));
}
return (
<>
} />
} />
>
);
}
```
> 🧩 **Le déclic :** une route reçoit des props comme n'importe quel composant — `element={}`. C'est ça qui « branche » la donnée sur les deux pages.
> 🔄 **Nouveau réflexe likes :** avant, chaque `Carte` gardait son like dans son coin (`localStorage.getItem("likes-" + id)`). Maintenant c'est **un seul objet** `likes` (genre `{ "2": 3, "4": 1 }`) rangé d'un coup. C'est ce qui rend possible le **podium** du Module N (impossible quand chaque like est éparpillé).
### L.2 — `Accueil` et `Carte` lisent via les props
- `Accueil({ jeux, setJeux, likes, onLike })` : enlève son `useState(jeux)` et son `fetch` (ils sont dans `App`). Garde `recherche`, `tri`, les champs du formulaire en local.
- Passe à chaque carte le bon compteur : `likes={likes[jeu.id] || 0}` et `onLike={() => onLike(jeu.id)}`.
- Dans `Carte`, supprime le `useState`/`useEffect` de likes : affiche `props.likes` et appelle `props.onLike` au clic.
### L.3 — `Detail` affiche le vrai jeu **avec ses likes**
```jsx
import { useParams, Link } from "react-router-dom";
function Detail({ jeux, likes }) {
const { id } = useParams();
const jeu = jeux.find((j) => String(j.id) === id);
if (!jeu) return
Introuvable 🤷 ← retour
;
return (
← Retour
{jeu.titre}
🕹️ {jeu.console} — 📅 {jeu.annee} — 🎮 {jeu.Type}
❤️ Likes : {likes[jeu.id] || 0}
);
}
```
**✅ Test du boss :** ajoute un jeu au formulaire → clique « détails » dessus → **il s'affiche** (alors qu'avant il était introuvable). Like-le sur l'accueil → son détail affiche bien le bon nombre. Recharge la page → les likes sont **toujours là**. 🏆
> 🧠 **Ce que tu viens de faire** s'appelle « **lifting state up** » poussé jusqu'au routeur : l'état monte au plus petit ancêtre commun (`App`) des composants qui en ont besoin. C'est **le** réflexe d'archi en React.
---
## 🎲 Module M — « Au hasard »
Un bouton qui t'envoie sur la fiche d'un jeu **au hasard**. On découvre `useNavigate` (naviguer en JS, pas via un clic sur un ``).
```jsx
import { useNavigate } from "react-router-dom";
function Accueil({ jeux /* ... */ }) {
const navigate = useNavigate();
function hasard() {
const i = Math.floor(Math.random() * jeux.length);
navigate(`/jeu/${jeux[i].id}`);
}
// ...
}
```
> 🔎 `` = naviguer **au clic** sur un lien. `useNavigate()` = naviguer **depuis du code** (après un calcul, un envoi de formulaire…). Deux outils, deux usages.
**✅ Test :** clique 🎲 plusieurs fois → tu tombes sur des jeux différents.
---
## 🏆 Module N — Le Hall of Fame
En haut de l'accueil, le **Top 3** des jeux les plus likés, avec des médailles. (Possible **uniquement** parce que les likes sont centralisés depuis le Module L 😉)
```jsx
const medailles = ["🥇", "🥈", "🥉"];
const top = [...jeux]
.sort((a, b) => (likes[b.id] || 0) - (likes[a.id] || 0))
.slice(0, 3);
```
> 🔎 `.slice(0, 3)` = « garde les indices 0, 1, 2 ». Le **2ᵉ paramètre** de `.map((j, index) => ...)`, c'est la **position** → parfait pour choisir la médaille.
**✅ Test :** spamme le like sur un jeu → il grimpe en haut du Hall of Fame en direct.
---
## 🌗 Module O — Thème sombre (qui se souvient)
Un bouton 🌙 / ☀️ qui bascule toute la page en sombre **et garde ton choix** après un refresh — exactement le réflexe `localStorage` que tu connais déjà des likes.
### O.1 — L'état + la mémoire (dans `App`)
```jsx
const [sombre, setSombre] = useState(
() => localStorage.getItem("theme") === "sombre"
);
useEffect(() => {
localStorage.setItem("theme", sombre ? "sombre" : "clair");
}, [sombre]);
```
### O.2 — Une classe pilotée par le state
```jsx
{/* Header + Routes + Footer */}
```
### O.3 — Le CSS (dans `App.css`)
```css
.sombre { background-color: #1a1a2e; color: #eaeaea; }
.sombre .carte { background-color: #16213e; color: #eaeaea; }
```
> 🧩 Tu fais piloter du **CSS par du state** : `className` change selon `sombre`, le navigateur applique les bonnes règles. C'est comme ça que marchent **tous** les dark modes.
**✅ Test :** passe en sombre → recharge (F5) → c'est **toujours** sombre. 🌙
---
## ✅ Checklist du dernier jour
- [ ] L — `fetch` + `jeux` + `likes` remontés dans `App`, page détail complète (ajouts + likes) 🔑
- [ ] M — bouton 🎲 avec `useNavigate`
- [ ] N — Hall of Fame Top 3 (`sort` + `slice` + `index` du `map`)
- [ ] O — thème sombre persistant (`localStorage` + classe CSS pilotée par state)
## 🚀 Si tu finis tout (true endgame)
- 🎉 **Confetti au like** : une petite animation CSS quand le compteur monte.
- ⚔️ **Mode Versus** : 2 `