Files
stagiaire/ENONCE_final_boss.md
T

7.5 KiB

🏁 É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

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).

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

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>).

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.

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

const [sombre, setSombre] = useState(
  () => localStorage.getItem("theme") === "sombre"
);

useEffect(() => {
  localStorage.setItem("theme", sombre ? "sombre" : "clair");
}, [sombre]);

O.2 — Appliquer une classe sur le conteneur

<div className={sombre ? "body sombre" : "body"}>
  <button onClick={() => setSombre(!sombre)}>
    {sombre ? "☀️ Clair" : "🌙 Sombre"}
  </button>
  {/* ... */}
</div>

O.3 — Le CSS (dans App.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 !