cda-hackathon-quiz-app-frontend

cda-hackathon-quiz-app-frontend

Frontend quiz application with themed tracks (Front-end, Back-end, DevOps) and a complete player journey (selection, answers, score, history), built with React/Redux Toolkit, protected routing, and local persistence. The project uses REST API consumption and centralized state management to maintain session consistency.

🎯 Context and goals

  • Build a web game interface with a guided multi-screen flow and persistent session/game state.
  • Synchronize frontend interactions with backend endpoints (questions, rankings, history).
  • Industrialize frontend delivery through a GitLab CI/CD pipeline based on Vite build and automated deployment.

🛠️ Deliverables

🧩 Design

{
  "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,
});

💻 Development

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>

The auth guard restores the session from localStorage and redirects to /home when no nickname is available. 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;
};

The game flow implements category-based question loading and persists game state (questionsFetched, gameInfo) for downstream screens. 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")
  }
}

The score page orchestrates filterable ranking retrieval (category + scoring mode) and pushes ranking state into 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 & Quality

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

📈 Results

  • Based on the analyzed Git history, the work spans 2021-12-13 -> 2026-02-14, with 91 commits across 8 contributors. The implementation delivers a complete quiz frontend with protected flows, Redux-based game/session state, API-driven ranking/history rendering, and persisted session behavior. The repository also includes continuous delivery logic with automated build and packaging. The technical benefit is a production-ready SPA baseline that can be executed and deployed quickly for API-driven game scenarios.

🔧 Technical environment

  • Frontend: React 17, React Router 6, Redux Toolkit, React Redux, Redux Logger, Vite.
  • API integration: Axios, import.meta.env-based configuration.
  • State management: auth and game slices with localStorage persistence.
  • Product features: category/mode/question flow, score, history, ranking filters.
  • DevOps: GitLab CI (build + artifacts + automated deployment).
🌐 View the project

Tech Stack

DevOps
GitLab CI/CD
Frontend
JavaScript
React
Redux
Vite