cda-hackathon-quiz-app-frontend

cda-hackathon-quiz-app-frontend

Application frontend de quiz thématique (Front-end, Back-end, DevOps) avec parcours joueur complet (sélection, réponses, score, historique), développée en React/Redux Toolkit avec routing protégé et persistance locale. Le projet s'appuie sur une consommation d'API REST et une gestion d'état centralisée pour maintenir la cohérence de session.

🎯 Contexte et objectifs

  • Concevoir une interface web de jeu orientée session utilisateur, avec un enchaînement d’écrans guidé et des états métier persistés.
  • Synchroniser les interactions frontend avec une API backend (questions, classements, historique).
  • Industrialiser la livraison front avec une pipeline CI/CD GitLab basée sur build Vite et déploiement automatisé.

🛠️ Réalisations

🧩 Conception

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@reduxjs/toolkit": "^1.7.0",
    "axios": "^0.24.0",
    "react": "^17.0.2",
    "react-redux": "^7.2.6",
    "react-router-dom": "^6.1.1",
    "redux": "^4.1.2",
    "redux-logger": "^3.0.6"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^1.0.7",
    "vite": "^2.7.2"
  }
}
import { configureStore } from "@reduxjs/toolkit";
import logger from "redux-logger";

import auth from "../features/auth/authSlice";
import game from "../features/game/gameSlice"

const reducer = {
  auth:auth,
  game:game,
}

export const store = configureStore({
  reducer,
});

💻 Développement

import axios from 'axios'
import { API_BASE_URL } from './constants'

const instance = axios.create({
    baseURL: API_BASE_URL
})

export default instance
<Routes>
  <Route path="/home" element={<HomePage />} />
  <Route path="/" element={<Navigate replace to="/home" />} />

  <Route path="/games/nickname" element={<ChooseNickname />}></Route>
  <Route
    path="/games/choose-category"
    element={<RequireAuth><ChooseCategoryQuestion /></RequireAuth>}
  ></Route>
  <Route path="/games/choose-mode" element={<RequireAuth><ChooseModeQuestion /></RequireAuth>}></Route>
  <Route path="/games/questions" element={<RequireAuth><ChooseResponsePage /></RequireAuth>}></Route>

  <Route path="/score" element={<RequireAuth><ScorePage /></RequireAuth>}></Route>
  <Route path="/history" element={<RequireAuth><HistoricPage /></RequireAuth>}></Route>
  <Route path="/historyDetails" element={<RequireAuth><HistoricDetailsPage /></RequireAuth>}></Route>
</Routes>

La garde d’authentification restaure une session depuis localStorage et redirige vers /home si le pseudo n’est pas disponible. Source: cda-hackathon-quiz-app-frontend/Hackathon-Quiz-App/src/js/features/auth/requireAuth.jsx

export const RequireAuth = ({ children }) => {
  let location = useLocation();
  const dispatch = useDispatch()
  const currentPlayer = getLocalStorageItem("nickname");

  useEffect(() => {
    const currentPlayer = getLocalStorageItem("nickname");
    if (typeof(currentPlayer) === "string") {
      dispatch(login(currentPlayer));
    }
  }, []);

  if (typeof(currentPlayer) !== "string") {
    return <Navigate to="/home" state={{ from: location }} />;
  }

  return children;
};

La logique de jeu implémente le chargement des questions par catégorie, puis stocke l’état de partie (questionsFetched, gameInfo) pour piloter les écrans suivants. Source: cda-hackathon-quiz-app-frontend/Hackathon-Quiz-App/src/js/components/CategoryQuestion.jsx

async function fetchQuestions() {
  let body = "";
  if (category !== "" && getLocalStorageItem("currentCategory") !== category) {
    body = category;
  } else if (
    category === "" &&
    (getLocalStorageItem("currentCategory") !== null ||
      getLocalStorageItem("currentCategory") !== undefined)
  ) {
    body = getLocalStorageItem("currentCategory");
  } else {
    return;
  }

  const result = await api.get(`/questions/${body}`);
  if (result.status === 200 && result.data.data !== false) {
    await dispatch(categoryChosen(result.data.data[0].categoryId));
    setLocalStorageItem(result.data.data,"questionsFetched")
  }
}

La page score orchestre un classement filtrable (catégorie + mode de score) et propage les résultats dans Redux. Source: cda-hackathon-quiz-app-frontend/Hackathon-Quiz-App/src/js/views/ScorePage.jsx

async function fetchRankings(cat = 'total', avg = false) {
  try {
    const rankings = await api.get(`/rankings/${cat}/${avg}`);
    if (rankings.status === 200) {
      if (rankings.data.data) {
        dispatch(updateRankings(rankings.data.data));
        return rankings.data.data;
      }
    }
  } catch (err) {
    console.log(`Erreur lors de la requête : /rankings/${cat}/${avg}`);
  }
}

🏗️ DevOps & Qualité

image: node:13

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/

stages:
  - build
  - deploy

build_parcel_master:
  stage: build
  script:
    - cd ./Hackathon-Quiz-App/
    - npm install
    - npm run build
  artifacts:
    expire_in: 20 mins
    paths:
      - ./Hackathon-Quiz-App/dist

📈 Résultats

  • Sur la base de l’historique Git du dépôt analysé, le travail couvre la période 2021-12-13 -> 2026-02-14, avec 91 commits réalisés par 8 contributeurs. L’implémentation délivre un frontend de quiz complet avec parcours protégé, pilotage d’état Redux, orchestration des appels API et affichage de classements/historique. Le dépôt montre aussi une logique de livraison continue avec build et packaging automatisés. Le bénéfice technique est une base SPA immédiatement exécutable et déployable pour un jeu orienté API.

🔧 Environnement technique

  • Frontend: React 17, React Router 6, Redux Toolkit, React Redux, Redux Logger, Vite.
  • Intégration API: Axios, configuration import.meta.env.
  • State management: slices auth et game, persistance localStorage.
  • UX métier: flow catégorie/mode/question, score, historique, filtre de classement.
  • DevOps: GitLab CI (build + artifacts + déploiement automatisé).
🌐 Voir le projet

Technologies utilisées

DevOps
GitLab CI/CD
Frontend
JavaScript
React
Redux
Vite