Correction page detail + enonce final fun (boss de fin: detail complet, random, podium, dark mode)
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# 🏁 Énoncé FINAL — « Boss de fin » 😈
|
||||
|
||||
Dernier jour ! 🎉 Tu as déjà tout l'arsenal : composants, props, state, lifting state up, `useEffect`, formulaire, tri, filtre, **React Router**. Aujourd'hui on **s'amuse** : 4 modules courts et stylés, du plus utile au plus fun. 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 **🎲 « Je tente ma chance »** (page au hasard) | ⭐⭐ | 😄 |
|
||||
| N | **🏆 Le Podium** des trilogies les plus likées | ⭐⭐ | 🥇 |
|
||||
| O | **🌗 Thème sombre** qui se souvient de ton choix | ⭐⭐ | 🌙 |
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Module L — Le boss : faire suivre TOUT jusqu'au détail
|
||||
|
||||
Dans `CORRECTION_page_detail.md` tu as fait une version simple avec `data.js`. Le souci : les trilogies **ajoutées au formulaire** et les **likes** n'arrivent pas dans la page détail. On corrige ça **proprement**.
|
||||
|
||||
**L'idée :** la donnée doit vivre **au-dessus** des deux pages, donc dans `App`. `App` la passe ensuite **en props** aux deux routes.
|
||||
|
||||
### L.1 — Remonter le state dans `App.jsx`
|
||||
```jsx
|
||||
import { useState } from "react";
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import { donneesInitiales } from "./data";
|
||||
import Accueil from "./pages/Accueil";
|
||||
import Details from "./pages/Details";
|
||||
|
||||
function App() {
|
||||
const [trilogies, setTrilogies] = useState(donneesInitiales);
|
||||
const [likes, setLikes] = useState({});
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<Accueil
|
||||
trilogies={trilogies}
|
||||
setTrilogies={setTrilogies}
|
||||
likes={likes}
|
||||
setLikes={setLikes}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/trilogie/:id"
|
||||
element={<Details trilogies={trilogies} likes={likes} />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
> 🧩 **Le déclic :** une route peut recevoir des props comme n'importe quel composant — `element={<Details trilogies={trilogies} />}`. C'est ça qui « branche » la donnée sur les deux pages.
|
||||
|
||||
### L.2 — `Accueil` lit/écrit via les props (au lieu de son state local)
|
||||
Enlève les `useState` de `trilogies` et `likes` dans `Accueil` : ils viennent maintenant de `props`. Garde en local ce qui est **propre à la page d'accueil** (`recherche`, `tri`, les champs du formulaire).
|
||||
```jsx
|
||||
function Accueil({ trilogies, setTrilogies, likes, setLikes }) {
|
||||
// recherche, tri, nom, niveaux restent en useState local
|
||||
// ajouter() fait toujours setTrilogies([...trilogies, nouvelle])
|
||||
// liker() fait toujours setLikes(prev => ({...prev, [id]: (prev[id]||0)+1}))
|
||||
}
|
||||
```
|
||||
|
||||
### L.3 — `Details` affiche la vraie trilogie **avec ses likes**
|
||||
```jsx
|
||||
function Details({ trilogies, likes }) {
|
||||
const { id } = useParams();
|
||||
const trilogie = trilogies.find((t) => t.id === Number(id));
|
||||
if (!trilogie) return <p>Introuvable 🤷 <Link to="/">← retour</Link></p>;
|
||||
|
||||
return (
|
||||
<div className="detail">
|
||||
<Link to="/">← Retour</Link>
|
||||
<h1>{trilogie.nom}</h1>
|
||||
<p>🎮 Niveaux : {trilogie.niveaux}</p>
|
||||
<p>❤️ Likes : {likes[trilogie.id] || 0}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Test du boss :** ajoute une trilogie au formulaire → clique « détails » dessus → **elle s'affiche** (alors qu'avec `data.js` elle était introuvable). Like-la 3 fois sur l'accueil → son détail affiche bien « ❤️ Likes : 3 ». 🏆
|
||||
|
||||
> 🧠 **Ce que tu viens de faire** s'appelle « **lifting state up** » poussé jusqu'au routeur : l'état monte au plus petit ancêtre commun des composants qui en ont besoin. C'est **le** réflexe d'archi en React.
|
||||
|
||||
---
|
||||
|
||||
## 🎲 Module M — « Je tente ma chance »
|
||||
|
||||
Un bouton qui t'envoie sur la page détail d'une trilogie **au hasard**. On découvre `useNavigate` (naviguer en JS, pas avec un clic sur un `<Link>`).
|
||||
|
||||
```jsx
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
function Accueil({ trilogies, /* ... */ }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
function hasard() {
|
||||
const i = Math.floor(Math.random() * trilogies.length);
|
||||
navigate(`/trilogie/${trilogies[i].id}`);
|
||||
}
|
||||
// ...
|
||||
return (
|
||||
<>
|
||||
<button onClick={hasard}>🎲 Je tente ma chance</button>
|
||||
{/* ... reste de la page ... */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
> 🔎 `<Link>` = 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 trilogies différentes.
|
||||
|
||||
---
|
||||
|
||||
## 🏆 Module N — Le Podium
|
||||
|
||||
En haut de l'accueil, affiche le **Top 3** des trilogies les plus likées, avec des médailles.
|
||||
|
||||
```jsx
|
||||
const medailles = ["🥇", "🥈", "🥉"];
|
||||
|
||||
const podium = [...trilogies]
|
||||
.sort((a, b) => (likes[b.id] || 0) - (likes[a.id] || 0))
|
||||
.slice(0, 3); // on ne garde que les 3 premières
|
||||
|
||||
// dans le JSX :
|
||||
<div className="podium">
|
||||
<h2>🏆 Podium</h2>
|
||||
{podium.map((t, index) => (
|
||||
<p key={t.id}>{medailles[index]} {t.nom} — {likes[t.id] || 0} ❤️</p>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
> 🔎 `.slice(0, 3)` = « garde les éléments d'indice 0, 1, 2 ». Et le **2ᵉ paramètre** de `.map((t, index) => ...)`, c'est la **position** dans la liste → parfait pour choisir la médaille.
|
||||
|
||||
**✅ Test :** like beaucoup une trilogie → elle grimpe sur le podium en temps réel.
|
||||
|
||||
---
|
||||
|
||||
## 🌗 Module O — Thème sombre (qui se souvient)
|
||||
|
||||
Un bouton 🌙 / ☀️ qui bascule toute la page en sombre, **et garde ton choix** même après un refresh (`localStorage`, comme les likes du Module H).
|
||||
|
||||
### O.1 — L'état + la mémoire
|
||||
```jsx
|
||||
const [sombre, setSombre] = useState(
|
||||
() => localStorage.getItem("theme") === "sombre"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("theme", sombre ? "sombre" : "clair");
|
||||
}, [sombre]);
|
||||
```
|
||||
|
||||
### O.2 — Appliquer une classe sur le conteneur
|
||||
```jsx
|
||||
<div className={sombre ? "body sombre" : "body"}>
|
||||
<button onClick={() => setSombre(!sombre)}>
|
||||
{sombre ? "☀️ Clair" : "🌙 Sombre"}
|
||||
</button>
|
||||
{/* ... */}
|
||||
</div>
|
||||
```
|
||||
|
||||
### 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`, et le navigateur applique les bonnes règles. C'est comme ça que marchent **tous** les dark modes que tu connais.
|
||||
|
||||
**✅ Test :** passe en sombre → recharge la page (F5) → c'est **toujours** sombre. 🌙
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist du dernier jour
|
||||
- [ ] L — state remonté dans `App`, page détail complète (ajouts + likes inclus) 🔑
|
||||
- [ ] M — bouton 🎲 avec `useNavigate`
|
||||
- [ ] N — podium Top 3 (`sort` + `slice` + `index` dans `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** : choisis 2 trilogies dans 2 `<select>`, compare leurs likes, affiche la gagnante.
|
||||
- 🗑️ **Supprimer / éditer** une trilogie depuis sa page détail.
|
||||
- 📱 Soigner le **responsive** (flex/grid) pour que ce soit beau sur téléphone.
|
||||
|
||||
## 🧠 Nouveaux concepts du jour
|
||||
`useNavigate` (naviguer depuis du code) · état remonté **au niveau du routeur** + props passées à une `<Route>` · `slice` + `index` du `map` pour un classement · **CSS piloté par le state** (`className` conditionnel) · persistance `localStorage`.
|
||||
|
||||
GG pour ces deux semaines. 🫡 Montre-moi ton boss quand il est battu !
|
||||
Reference in New Issue
Block a user