diff --git a/.DS_Store b/.DS_Store
index 04a508e6f..92e51b349 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.coverage b/.coverage
new file mode 100644
index 000000000..672bf3c37
Binary files /dev/null and b/.coverage differ
diff --git a/.flaskenv b/.flaskenv
new file mode 100644
index 000000000..865919fdd
--- /dev/null
+++ b/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app.server
+FLASK_ENV=development
diff --git a/.gitignore b/.gitignore
index 2cba99d87..cefb4c172 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,6 @@ bin
 include
 lib
 .Python
-tests/
+
 .envrc
 __pycache__
\ No newline at end of file
diff --git a/CACHEDIR.TAG b/CACHEDIR.TAG
new file mode 100644
index 000000000..837feeff9
--- /dev/null
+++ b/CACHEDIR.TAG
@@ -0,0 +1,4 @@
+Signature: 8a477f597d28d172789f06886806bc55
+# This file is a cache directory tag created by Python virtualenv.
+# For information about cache directory tags, see:
+#   https://bford.info/cachedir/
\ No newline at end of file
diff --git a/app/__init__.py b/app/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/app/booking_manager/__init__.py b/app/booking_manager/__init__.py
new file mode 100644
index 000000000..9ec577cc3
--- /dev/null
+++ b/app/booking_manager/__init__.py
@@ -0,0 +1,6 @@
+# app/booking_manager/__init__.py
+
+from .booking_service import BookingService
+from .club_manager import ClubManager
+from .competition_manager import CompetitionManager
+from .data_loader import JSONDataLoader
diff --git a/app/booking_manager/booking_service.py b/app/booking_manager/booking_service.py
new file mode 100644
index 000000000..3c21d53b8
--- /dev/null
+++ b/app/booking_manager/booking_service.py
@@ -0,0 +1,36 @@
+# app/booking_manager/booking_service.py
+
+from app.booking_manager.club_manager import ClubManager
+from app.booking_manager.competition_manager import CompetitionManager
+
+
+class BookingService:
+    """
+    Ordonne le processus de réservation en utilisant les gestionnaires de clubs et de compétitions.
+    """
+
+    def __init__(self, clubs_file: str, competitions_file: str):
+        self.club_manager = ClubManager(clubs_file)
+        self.competition_manager = CompetitionManager(competitions_file)
+
+    def purchase_places(self, club_name: str, competition_name: str, places_requested: int) -> bool:
+        club = self.club_manager.find_by_name(club_name)
+        competition = self.competition_manager.find_by_name(competition_name)
+
+        if not club or not competition:
+            return False
+        if places_requested > 12:
+            return False
+        if places_requested > club.points:
+            return False
+        if places_requested > competition.number_of_places:
+            return False
+
+        competition.number_of_places -= places_requested
+        club.points -= places_requested
+
+        # Sauvegarde des modifications dans les fichiers JSON
+        self.club_manager.save_clubs()
+        self.competition_manager.save_competitions()
+
+        return True
diff --git a/app/booking_manager/club_manager.py b/app/booking_manager/club_manager.py
new file mode 100644
index 000000000..802b643fb
--- /dev/null
+++ b/app/booking_manager/club_manager.py
@@ -0,0 +1,73 @@
+# app/booking_manager/club_manager.py
+
+import json
+import os
+import shutil
+from typing import List, Optional
+from app.models import Club
+from .data_loader import JSONDataLoader
+
+
+class ClubManager:
+    """
+    Gère le chargement, la recherche et la sauvegarde des clubs.
+    """
+
+    def __init__(self, clubs_file: str):
+        self.clubs_file = clubs_file  # Fichier "de travail"
+        self.loader = JSONDataLoader(clubs_file)
+        data = self.loader.load_data()
+        self.clubs: List[Club] = self._parse_clubs(data)
+
+    def _parse_clubs(self, data: dict) -> List[Club]:
+        clubs = []
+        for c in data.get("clubs", []):
+            clubs.append(
+                Club(
+                    name=c["name"],
+                    email=c["email"],
+                    points=int(c["points"]),
+                    id=c.get("id")
+                )
+            )
+        return clubs
+
+    def find_by_email(self, email: str) -> Optional[Club]:
+        return next((club for club in self.clubs if club.email == email), None)
+
+    def find_by_name(self, name: str) -> Optional[Club]:
+        return next((club for club in self.clubs if club.name == name), None)
+
+    def save_clubs(self, filepath: Optional[str] = None) -> None:
+        """
+        Sauvegarde l'état actuel des clubs dans un fichier JSON.
+        """
+        if filepath is None:
+            filepath = self.clubs_file
+        clubs_data = {"clubs": []}
+        for club in self.clubs:
+            club_dict = {
+                "id": club.id,
+                "name": club.name,
+                "email": club.email,
+                "points": str(club.points)
+            }
+            clubs_data["clubs"].append(club_dict)
+        with open(filepath, "w") as f:
+            json.dump(clubs_data, f, indent=4)
+
+    def reset_data(self, fresh_filepath: str) -> None:
+        """
+        Copie le fichier fresh_filepath dans self.clubs_file,
+        puis recharge la liste des clubs en mémoire.
+        """
+        if not os.path.exists(fresh_filepath):
+            raise FileNotFoundError(
+                f"Fichier source introuvable : {fresh_filepath}")
+
+        # 1. Copie fresh_filepath => self.clubs_file
+        shutil.copy(fresh_filepath, self.clubs_file)
+
+        # 2. Recharge en mémoire
+        data = self.loader.load_data()
+        self.clubs = self._parse_clubs(data)
diff --git a/app/booking_manager/competition_manager.py b/app/booking_manager/competition_manager.py
new file mode 100644
index 000000000..1f33520ff
--- /dev/null
+++ b/app/booking_manager/competition_manager.py
@@ -0,0 +1,68 @@
+# app/booking_manager/competition_manager.py
+
+import json
+import os
+import shutil
+from typing import List, Optional
+from app.models import Competition
+from .data_loader import JSONDataLoader
+
+
+class CompetitionManager:
+    """
+    Gère le chargement, la recherche et la sauvegarde des compétitions.
+    """
+
+    def __init__(self, competitions_file: str):
+        self.competitions_file = competitions_file  # Fichier "de travail"
+        self.loader = JSONDataLoader(competitions_file)
+        data = self.loader.load_data()
+        self.competitions: List[Competition] = self._parse_competitions(data)
+
+    def _parse_competitions(self, data: dict) -> List[Competition]:
+        competitions = []
+        for c in data.get("competitions", []):
+            competitions.append(
+                Competition(
+                    name=c["name"],
+                    date=c["date"],
+                    number_of_places=int(c["numberOfPlaces"])
+                )
+            )
+        return competitions
+
+    def find_by_name(self, name: str) -> Optional[Competition]:
+        return next((comp for comp in self.competitions if comp.name == name), None)
+
+    def save_competitions(self, filepath: Optional[str] = None) -> None:
+        """
+        Sauvegarde l'état actuel des compétitions dans un fichier JSON.
+        """
+        if filepath is None:
+            filepath = self.competitions_file
+        competitions_data = {"competitions": []}
+        for comp in self.competitions:
+            comp_dict = {
+                "name": comp.name,
+                "date": comp.date,
+                "numberOfPlaces": str(comp.number_of_places)
+            }
+            competitions_data["competitions"].append(comp_dict)
+        with open(filepath, "w") as f:
+            json.dump(competitions_data, f, indent=4)
+
+    def reset_data(self, fresh_filepath: str) -> None:
+        """
+        Copie le fichier fresh_filepath dans self.competitions_file,
+        puis recharge les competitions en mémoire.
+        """
+        if not os.path.exists(fresh_filepath):
+            raise FileNotFoundError(
+                f"Fichier source introuvable : {fresh_filepath}")
+
+        # 1. Copie fresh_filepath => self.competitions_file
+        shutil.copy(fresh_filepath, self.competitions_file)
+
+        # 2. Recharge en mémoire
+        data = self.loader.load_data()  # relit le fichier de travail
+        self.competitions = self._parse_competitions(data)
diff --git a/app/booking_manager/data_loader.py b/app/booking_manager/data_loader.py
new file mode 100644
index 000000000..82da6b023
--- /dev/null
+++ b/app/booking_manager/data_loader.py
@@ -0,0 +1,21 @@
+# app/booking_manager/data_loader.py
+
+import json
+import os
+from typing import Any
+
+
+class JSONDataLoader:
+    """
+    Classe générique pour charger des données depuis un fichier JSON.
+    """
+
+    def __init__(self, filepath: str):
+        self.filepath = filepath
+
+    def load_data(self) -> Any:
+        if not os.path.exists(self.filepath):
+            raise FileNotFoundError(f"Fichier introuvable : {self.filepath}")
+        with open(self.filepath, "r") as f:
+            data = json.load(f)
+        return data
diff --git a/app/models.py b/app/models.py
new file mode 100644
index 000000000..1783b24c5
--- /dev/null
+++ b/app/models.py
@@ -0,0 +1,15 @@
+# app/models.py
+
+class Competition:
+    def __init__(self, name, date, number_of_places):
+        self.name = name
+        self.date = date
+        self.number_of_places = number_of_places
+
+
+class Club:
+    def __init__(self, name, email, points, id=None):
+        self.name = name
+        self.email = email
+        self.points = points
+        self.id = id
diff --git a/app/server.py b/app/server.py
new file mode 100644
index 000000000..d3b0b42c2
--- /dev/null
+++ b/app/server.py
@@ -0,0 +1,134 @@
+# app/server.py
+
+from flask import Flask, render_template, request, redirect, flash, url_for, session
+from app.booking_manager import BookingService
+
+app = Flask(__name__, static_folder="../static")
+app.secret_key = "secret_key_xyz"
+
+# Instanciation du service de réservation
+booking_service = BookingService(
+    clubs_file="data/clubs.json",
+    competitions_file="data/competitions.json"
+)
+
+
+@app.context_processor
+def inject_club_email():
+    """
+    Permet d'accéder à session['club_email'] dans les templates,
+    afin de savoir si un utilisateur est connecté.
+    """
+    return dict(club_email=session.get('club_email'))
+
+
+@app.route("/")
+def index():
+    """
+    Page d'accueil.
+    """
+    return render_template("index.html")
+
+
+@app.route("/showSummary", methods=["POST"])
+def show_summary():
+    """
+    Récupère l'email du club et affiche la page de résumé (welcome.html).
+    Si le club n'est pas trouvé, on redirige avec un message d'erreur.
+    """
+    email = request.form.get("email", "")
+    club = booking_service.club_manager.find_by_email(email)
+    if not club:
+        flash("Email inconnu ou invalide.")
+        return redirect(url_for("index"))
+
+    session['club_email'] = club.email
+    competitions = booking_service.competition_manager.competitions
+    return render_template("welcome.html", club=club, competitions=competitions)
+
+
+@app.route("/book/<competition>/<club>")
+def book(competition, club):
+    """
+    Affiche la page de réservation (booking.html) pour un club/compétition donné.
+    """
+    found_competition = booking_service.competition_manager.find_by_name(
+        competition)
+    found_club = booking_service.club_manager.find_by_name(club)
+    if not found_competition or not found_club:
+        flash("Something went wrong - please try again")
+        return redirect(url_for("index"))
+
+    return render_template("booking.html", club=found_club, competition=found_competition)
+
+
+@app.route("/purchasePlaces", methods=["POST"])
+def purchase_places():
+    """
+    Tente d'acheter 'places' places pour un club et une compétition.
+    Après avoir flashé un message de succès ou d'erreur,
+    on redirige vers /showPurchaseResult/<club_name> pour afficher la page finale.
+    """
+    competition_name = request.form.get("competition")
+    club_name = request.form.get("club")
+    places_str = request.form.get("places")
+
+    try:
+        places_requested = int(places_str)
+    except ValueError:
+        flash("Le nombre de places est invalide.")
+        return redirect(url_for("index"))
+
+    found_competition = booking_service.competition_manager.find_by_name(
+        competition_name)
+    found_club = booking_service.club_manager.find_by_name(club_name)
+
+    if not found_competition or not found_club:
+        flash("Something went wrong - please try again.")
+        return redirect(url_for("index"))
+
+    # Logique de réservation
+    if places_requested > 12:
+        flash("Vous ne pouvez pas réserver plus de 12 places.")
+        success = False
+    else:
+        success = booking_service.purchase_places(
+            club_name, competition_name, places_requested)
+
+    if success:
+        flash(
+            f"Great-booking complete! Vous avez réservé {places_requested} places.")
+    else:
+        if places_requested <= 12:
+            flash("Le concours est complet ou vous n'avez pas assez de points.")
+
+    # Redirection vers la route qui affiche le résultat
+    return redirect(url_for("show_purchase_result", club_name=club_name))
+
+
+@app.route("/showPurchaseResult/<club_name>")
+def show_purchase_result(club_name):
+    """
+    Affiche la page welcome.html, lit le message flash si besoin.
+    """
+    updated_club = booking_service.club_manager.find_by_name(club_name)
+    competitions = booking_service.competition_manager.competitions
+    return render_template("welcome.html", club=updated_club, competitions=competitions)
+
+
+@app.route("/clubsPoints")
+def clubs_points():
+    """
+    Liste des clubs et leurs points.
+    """
+    clubs = booking_service.club_manager.clubs
+    return render_template("clubs_points.html", clubs=clubs)
+
+
+@app.route("/logout")
+def logout():
+    """
+    Déconnecte le club.
+    """
+    session.pop('club_email', None)
+    return redirect(url_for("index"))
diff --git a/app/templates/base.html b/app/templates/base.html
new file mode 100644
index 000000000..fe5cb6d58
--- /dev/null
+++ b/app/templates/base.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>{% block title %}GUDLFT{% endblock %}</title>
+  <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
+</head>
+<body>
+  <nav>
+    <ul>
+      {% if club_email %}
+        <!-- Si connecté, 'Accueil' renvoie à show_summary, en POST -->
+        <li>
+          <form action="{{ url_for('show_summary') }}" method="POST" style="display: inline;">
+            <input type="hidden" name="email" value="{{ club_email }}">
+            <button type="submit" style="background:none;border:none;color:blue;cursor:pointer;">
+              Accueil
+            </button>
+          </form>
+        </li>
+      {% else %}
+        <li><a href="{{ url_for('index') }}">Accueil</a></li>
+      {% endif %}
+      <li><a href="{{ url_for('clubs_points') }}">Points Clubs</a></li>
+      <li><a href="{{ url_for('logout') }}">Déconnexion</a></li>
+    </ul>
+  </nav>
+
+  <div class="container">
+    {% with messages = get_flashed_messages() %}
+      {% if messages %}
+        <ul class="flashes">
+          {% for message in messages %}
+            <li>{{ message }}</li>
+          {% endfor %}
+        </ul>
+      {% endif %}
+    {% endwith %}
+    {% block content %}{% endblock %}
+  </div>
+</body>
+</html>
diff --git a/app/templates/booking.html b/app/templates/booking.html
new file mode 100644
index 000000000..ae7f33ba6
--- /dev/null
+++ b/app/templates/booking.html
@@ -0,0 +1,17 @@
+<!-- app/templates/booking.html -->
+{% extends "base.html" %}
+
+{% block title %}Réservation | {{ competition['name'] }}{% endblock %}
+
+{% block content %}
+  <h2>{{ competition['name'] }}</h2>
+  <p>Places disponibles: {{ competition['number_of_places'] }}</p>
+  <form action="{{ url_for('purchase_places') }}" method="post">
+    <input type="hidden" name="club" value="{{ club['name'] }}">
+    <input type="hidden" name="competition" value="{{ competition['name'] }}">
+    <label for="places">Combien de places ?</label>
+    <input type="number" name="places" id="places" min="1" required>
+    <!-- Ajout d'un id -->
+    <button id="submit-booking" type="submit">Réserver</button>
+  </form>
+{% endblock %}
diff --git a/app/templates/clubs_points.html b/app/templates/clubs_points.html
new file mode 100644
index 000000000..f7a06436c
--- /dev/null
+++ b/app/templates/clubs_points.html
@@ -0,0 +1,24 @@
+<!-- app/templates/clubs_points.html -->
+{% extends "base.html" %}
+
+{% block title %}Points des Clubs{% endblock %}
+
+{% block content %}
+  <h2>Points des Clubs</h2>
+  <table border="1">
+    <thead>
+      <tr>
+        <th>Club</th>
+        <th>Points</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for club in clubs %}
+        <tr>
+          <td>{{ club['name'] }}</td>
+          <td>{{ club['points'] }}</td>
+        </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+{% endblock %}
diff --git a/app/templates/index.html b/app/templates/index.html
new file mode 100644
index 000000000..4ef54b808
--- /dev/null
+++ b/app/templates/index.html
@@ -0,0 +1,13 @@
+<!-- app/templates/index.html -->
+{% extends "base.html" %}
+
+{% block title %}Accueil | GUDLFT{% endblock %}
+
+{% block content %}
+  <h1>Bienvenue sur GUDLFT</h1>
+  <form action="/showSummary" method="post">
+    <label for="email">Email :</label>
+    <input type="email" name="email" id="email" required>
+    <button type="submit">Se connecter</button>
+  </form>
+{% endblock %}
diff --git a/app/templates/welcome.html b/app/templates/welcome.html
new file mode 100644
index 000000000..b0e3c68ee
--- /dev/null
+++ b/app/templates/welcome.html
@@ -0,0 +1,21 @@
+<!-- app/templates/welcome.html -->
+{% extends "base.html" %}
+
+{% block title %}Résumé | GUDLFT Registration{% endblock %}
+
+{% block content %}
+  <h2>Welcome, {{ club['email'] }}</h2>
+  <p>Points disponibles: {{ club['points'] }}</p>
+  <h3>Compétitions :</h3>
+  <ul>
+    {% for comp in competitions %}
+      <li>
+        {{ comp['name'] }}<br />
+        Date: {{ comp['date'] }}<br />
+        Places disponibles: {{ comp['number_of_places'] }}<br />
+        <a href="{{ url_for('book', competition=comp['name'], club=club['name']) }}">Réserver</a>
+      </li>
+      <hr />
+    {% endfor %}
+  </ul>
+{% endblock %}
diff --git a/clubs.json b/clubs.json
deleted file mode 100644
index 1d7ad1ffe..000000000
--- a/clubs.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{"clubs":[
-    {
-        "name":"Simply Lift",
-        "email":"john@simplylift.co",
-        "points":"13"
-    },
-    {
-        "name":"Iron Temple",
-        "email": "admin@irontemple.com",
-        "points":"4"
-    },
-    {   "name":"She Lifts",
-        "email": "kate@shelifts.co.uk",
-        "points":"12"
-    }
-]}
\ No newline at end of file
diff --git a/data/clubs.json b/data/clubs.json
new file mode 100644
index 000000000..0a44b28a4
--- /dev/null
+++ b/data/clubs.json
@@ -0,0 +1,22 @@
+{
+    "clubs": [
+        {
+            "id": "1",
+            "name": "Simply Lift",
+            "email": "john@simplylift.co",
+            "points": "12"
+        },
+        {
+            "id": "2",
+            "name": "Iron Temple",
+            "email": "admin@irontemple.com",
+            "points": "3"
+        },
+        {
+            "id": "3",
+            "name": "She Lifts",
+            "email": "kate@shelifts.co.uk",
+            "points": "14"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/competitions.json b/data/competitions.json
similarity index 76%
rename from competitions.json
rename to data/competitions.json
index 039fc61bd..6a2df7100 100644
--- a/competitions.json
+++ b/data/competitions.json
@@ -3,12 +3,12 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "25"
+            "numberOfPlaces": "8"
         },
         {
             "name": "Fall Classic",
             "date": "2020-10-22 13:30:00",
-            "numberOfPlaces": "13"
+            "numberOfPlaces": "34"
         }
     ]
 }
\ No newline at end of file
diff --git a/data/fresh_clubs.json b/data/fresh_clubs.json
new file mode 100644
index 000000000..e29743cee
--- /dev/null
+++ b/data/fresh_clubs.json
@@ -0,0 +1,22 @@
+{
+    "clubs": [
+        {
+            "id": "1",
+            "name": "Simply Lift",
+            "email": "john@simplylift.co",
+            "points": "12"
+        },
+        {
+            "id": "2",
+            "name": "Iron Temple",
+            "email": "admin@irontemple.com",
+            "points": "34"
+        },
+        {
+            "id": "3",
+            "name": "She Lifts",
+            "email": "kate@shelifts.co.uk",
+            "points": "14"
+        }
+    ]
+}
diff --git a/data/fresh_competitions.json b/data/fresh_competitions.json
new file mode 100644
index 000000000..8790b8a91
--- /dev/null
+++ b/data/fresh_competitions.json
@@ -0,0 +1,14 @@
+{
+    "competitions": [
+        {
+            "name": "Spring Festival",
+            "date": "2020-03-27 10:00:00",
+            "numberOfPlaces": "39"
+        },
+        {
+            "name": "Fall Classic",
+            "date": "2020-10-22 13:30:00",
+            "numberOfPlaces": "34"
+        }
+    ]
+}
diff --git a/launch.json b/launch.json
new file mode 100644
index 000000000..a0cc0ba8c
--- /dev/null
+++ b/launch.json
@@ -0,0 +1,17 @@
+{
+    "name": "Python Debugger: Flask",
+    "type": "debugpy",
+    "request": "launch",
+    "module": "flask",
+    "env": {
+        "FLASK_APP": "app/server.py",
+        "FLASK_DEBUG": "1"
+    },
+    "args": [
+        "run",
+        "--no-debugger",
+        "--no-reload"
+    ],
+    "jinja": true,
+    "justMyCode": true
+}
diff --git a/pyvenv.cfg b/pyvenv.cfg
new file mode 100644
index 000000000..9c71c33dd
--- /dev/null
+++ b/pyvenv.cfg
@@ -0,0 +1,8 @@
+home = /usr/local/bin
+implementation = CPython
+version_info = 3.12.2.final.0
+virtualenv = 20.28.0
+include-system-site-packages = false
+base-prefix = /Library/Frameworks/Python.framework/Versions/3.12
+base-exec-prefix = /Library/Frameworks/Python.framework/Versions/3.12
+base-executable = /Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12
diff --git a/server.py b/server.py
deleted file mode 100644
index 4084baeac..000000000
--- a/server.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import json
-from flask import Flask,render_template,request,redirect,flash,url_for
-
-
-def loadClubs():
-    with open('clubs.json') as c:
-         listOfClubs = json.load(c)['clubs']
-         return listOfClubs
-
-
-def loadCompetitions():
-    with open('competitions.json') as comps:
-         listOfCompetitions = json.load(comps)['competitions']
-         return listOfCompetitions
-
-
-app = Flask(__name__)
-app.secret_key = 'something_special'
-
-competitions = loadCompetitions()
-clubs = loadClubs()
-
-@app.route('/')
-def index():
-    return render_template('index.html')
-
-@app.route('/showSummary',methods=['POST'])
-def showSummary():
-    club = [club for club in clubs if club['email'] == request.form['email']][0]
-    return render_template('welcome.html',club=club,competitions=competitions)
-
-
-@app.route('/book/<competition>/<club>')
-def book(competition,club):
-    foundClub = [c for c in clubs if c['name'] == club][0]
-    foundCompetition = [c for c in competitions if c['name'] == competition][0]
-    if foundClub and foundCompetition:
-        return render_template('booking.html',club=foundClub,competition=foundCompetition)
-    else:
-        flash("Something went wrong-please try again")
-        return render_template('welcome.html', club=club, competitions=competitions)
-
-
-@app.route('/purchasePlaces',methods=['POST'])
-def purchasePlaces():
-    competition = [c for c in competitions if c['name'] == request.form['competition']][0]
-    club = [c for c in clubs if c['name'] == request.form['club']][0]
-    placesRequired = int(request.form['places'])
-    competition['numberOfPlaces'] = int(competition['numberOfPlaces'])-placesRequired
-    flash('Great-booking complete!')
-    return render_template('welcome.html', club=club, competitions=competitions)
-
-
-# TODO: Add route for points display
-
-
-@app.route('/logout')
-def logout():
-    return redirect(url_for('index'))
\ No newline at end of file
diff --git a/static/css/style.css b/static/css/style.css
new file mode 100644
index 000000000..2a656539d
--- /dev/null
+++ b/static/css/style.css
@@ -0,0 +1,119 @@
+/* static/css/style.css */
+
+/* Style global */
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  background-color: #f8f8f8;
+  margin: 0;
+  padding: 0;
+  color: #333;
+}
+
+/* Barre de navigation */
+nav {
+  background-color: #333;
+  color: #fff;
+  padding: 10px 20px;
+}
+
+nav ul {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  display: flex;
+}
+
+nav ul li {
+  margin-right: 15px;
+}
+
+nav ul li a {
+  color: #fff;
+  text-decoration: none;
+  font-weight: bold;
+}
+
+nav ul li a:hover {
+  text-decoration: underline;
+}
+
+/* Conteneur principal */
+.container {
+  width: 90%;
+  max-width: 1000px;
+  margin: 20px auto;
+  background-color: #fff;
+  padding: 20px;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+  border-radius: 5px;
+}
+
+/* Titres */
+h1, h2, h3 {
+  color: #444;
+}
+
+/* Formulaires */
+form label {
+  display: block;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+
+form input[type="email"],
+form input[type="number"],
+form input[type="text"] {
+  width: 100%;
+  max-width: 300px;
+  padding: 8px;
+  margin-bottom: 10px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+}
+
+form button {
+  background-color: #333;
+  color: #fff;
+  border: none;
+  padding: 10px 15px;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+form button:hover {
+  background-color: #555;
+}
+
+/* Messages flash */
+.flashes {
+  list-style: none;
+  padding: 0;
+  margin: 10px 0;
+}
+
+.flashes li {
+  background-color: #e74c3c;
+  color: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  margin-bottom: 5px;
+}
+
+/* Tableaux */
+table {
+  width: 100%;
+  border-collapse: collapse;
+  margin-top: 20px;
+}
+
+table th,
+table td {
+  border: 1px solid #ddd;
+  padding: 8px;
+  text-align: left;
+}
+
+table th {
+  background-color: #333;
+  color: #fff;
+}
diff --git a/templates/booking.html b/templates/booking.html
deleted file mode 100644
index 06ae1156c..000000000
--- a/templates/booking.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Booking for {{competition['name']}} || GUDLFT</title>
-</head>
-<body>
-    <h2>{{competition['name']}}</h2>
-    Places available: {{competition['numberOfPlaces']}}
-    <form action="/purchasePlaces" method="post">
-        <input type="hidden" name="club" value="{{club['name']}}">
-        <input type="hidden" name="competition" value="{{competition['name']}}">
-        <label for="places">How many places?</label><input type="number" name="places" id=""/>
-        <button type="submit">Book</button>
-    </form>
-</body>
-</html>
\ No newline at end of file
diff --git a/templates/index.html b/templates/index.html
deleted file mode 100644
index 926526b7d..000000000
--- a/templates/index.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>GUDLFT Registration</title>
-</head>
-<body>
-    <h1>Welcome to the GUDLFT Registration Portal!</h1>
-    Please enter your secretary email to continue:
-    <form action="showSummary" method="post">
-        <label for="email">Email:</label>
-        <input type="email" name="email" id=""/>
-        <button type="submit">Enter</button>
-    </form>
-</body>
-</html>
\ No newline at end of file
diff --git a/templates/welcome.html b/templates/welcome.html
deleted file mode 100644
index ff6b261a2..000000000
--- a/templates/welcome.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Summary | GUDLFT Registration</title>
-</head>
-<body>
-        <h2>Welcome, {{club['email']}} </h2><a href="{{url_for('logout')}}">Logout</a>
-
-    {% with messages = get_flashed_messages()%}
-    {% if messages %}
-        <ul>
-       {% for message in messages %}
-            <li>{{message}}</li>
-        {% endfor %}
-       </ul>
-    {% endif%}
-    Points available: {{club['points']}}
-    <h3>Competitions:</h3>
-    <ul>
-        {% for comp in competitions%}
-        <li>
-            {{comp['name']}}<br />
-            Date: {{comp['date']}}</br>
-            Number of Places: {{comp['numberOfPlaces']}}
-            {%if comp['numberOfPlaces']|int >0%}
-            <a href="{{ url_for('book',competition=comp['name'],club=club['name']) }}">Book Places</a>
-            {%endif%}
-        </li>
-        <hr />
-        {% endfor %}
-    </ul>
-    {%endwith%}
-
-</body>
-</html>
\ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/functional/test_functional_booking.py b/tests/functional/test_functional_booking.py
new file mode 100644
index 000000000..618f55bb4
--- /dev/null
+++ b/tests/functional/test_functional_booking.py
@@ -0,0 +1,49 @@
+# tests/functional/test_functional_booking.py
+
+import unittest
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+
+
+class TestFunctionalBooking(unittest.TestCase):
+
+    def setUp(self):
+        self.driver = webdriver.Chrome()
+        self.base_url = "http://127.0.0.1:5000"
+
+    def tearDown(self):
+        self.driver.quit()
+
+    def test_book_places_selenium(self):
+        driver = self.driver
+        driver.get(self.base_url)
+
+        # 1) Saisir l'email -> Se connecter
+        email_input = driver.find_element(By.ID, "email")
+        email_input.send_keys("admin@irontemple.com")
+        driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
+
+        # 2) welcome.html => "Réserver" (Spring Festival)
+        link = driver.find_element(By.LINK_TEXT, "Réserver")
+        link.click()
+
+        # 3) booking.html => Saisir 3 places
+        places_input = driver.find_element(By.ID, "places")
+        places_input.send_keys("3")
+        # Désormais on clique spécifiquement le bouton id="submit-booking"
+        driver.find_element(By.ID, "submit-booking").click()
+
+        # 4) Attente explicite d'apparition du flash
+        wait = WebDriverWait(driver, 5)
+        wait.until(EC.presence_of_element_located(
+            (By.CSS_SELECTOR, ".flashes")))
+
+        # 5) Vérification
+        body_text = driver.find_element(By.TAG_NAME, "body").text.lower()
+        self.assertIn("great-booking complete!", body_text)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py
new file mode 100644
index 000000000..2c95192d6
--- /dev/null
+++ b/tests/integration/test_integration.py
@@ -0,0 +1,41 @@
+import unittest
+from app.server import app
+
+
+class TestServerRoutes(unittest.TestCase):
+    """
+    Test d'intégration pour vérifier qu'une réservation de 3 places
+    aboutit à un message de succès ('Great-booking complete!').
+    """
+
+    def setUp(self):
+        app.config['TESTING'] = True
+        self.client = app.test_client()
+
+    def test_purchase_places_route(self):
+        """
+        Envoie un POST à '/purchasePlaces' pour réserver 3 places
+        pour 'Iron Temple' dans 'Spring Festival'. Vérifie qu'on obtient
+        un code 200 et un message de succès ('Great-booking complete!').
+        """
+        data = {
+            "club": "Iron Temple",
+            "competition": "Spring Festival",
+            "places": "3"
+        }
+        # On suit la redirection pour voir le contenu final
+        response = self.client.post(
+            "/purchasePlaces", data=data, follow_redirects=True)
+        self.assertEqual(response.status_code, 200,
+                         "La requête /purchasePlaces doit retourner un code 200 (OK).")
+
+        lowercase_data = response.data.lower()
+        self.assertIn(
+            b"great-booking complete!",
+            lowercase_data,
+            "Doit contenir un message de réservation réussie ('Great-booking complete!')."
+        )
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/performance/__init__.py b/tests/performance/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/performance/locustfile.py b/tests/performance/locustfile.py
new file mode 100644
index 000000000..7aab74fd9
--- /dev/null
+++ b/tests/performance/locustfile.py
@@ -0,0 +1,70 @@
+# tests/performance/locustfile.py
+
+from locust import HttpUser, TaskSet, between
+import time
+
+
+class GUDLFTTaskSet(TaskSet):
+    """
+    Ensemble de tâches Locust pour GUDLFT.
+    On vérifie explicitement que les GET ne dépassent pas 5s et les POST 2s.
+    """
+
+    def on_start(self):
+        self.login_email = "admin@irontemple.com"
+
+    def index(self):
+        # 1) GET /
+        start_t = time.time()
+        resp = self.client.get("/", name="01-GET_Index")
+        duration = time.time() - start_t
+        if duration > 5:
+            resp.failure(f"/ took {duration:.2f}s > 5s")
+
+        # 2) POST /showSummary
+        start_t = time.time()
+        resp2 = self.client.post(
+            "/showSummary", data={"email": self.login_email}, name="02-POST_showSummary")
+        duration2 = time.time() - start_t
+        # ici, on considère "showSummary" comme un chargement, donc max 5s
+        if duration2 > 5:
+            resp2.failure(f"showSummary took {duration2:.2f}s > 5s")
+
+    def booking_scenario(self):
+        # GET /book/Spring%20Festival/Iron%20Temple
+        start_t = time.time()
+        resp = self.client.get(
+            "/book/Spring%20Festival/Iron%20Temple", name="03-GET_Book")
+        dur = time.time() - start_t
+        # C'est un GET => on impose < 5s
+        if dur > 5:
+            resp.failure(f"GET /book took {dur:.2f}s > 5s")
+
+        # POST /purchasePlaces => c'est la "mise à jour", on impose < 2s
+        start_t = time.time()
+        resp2 = self.client.post("/purchasePlaces", data={
+            "club": "Iron Temple",
+            "competition": "Spring Festival",
+            "places": "2"
+        }, name="04-POST_purchasePlaces")
+        dur2 = time.time() - start_t
+        if dur2 > 2:
+            resp2.failure(f"POST /purchasePlaces took {dur2:.2f}s > 2s")
+
+        # GET /clubsPoints => un chargement normal, impose < 5s
+        start_t = time.time()
+        resp3 = self.client.get("/clubsPoints", name="05-GET_clubsPoints")
+        dur3 = time.time() - start_t
+        if dur3 > 5:
+            resp3.failure(f"GET /clubsPoints took {dur3:.2f}s > 5s")
+
+    tasks = {
+        index: 1,
+        booking_scenario: 2
+    }
+
+
+class GUDLFTUser(HttpUser):
+    tasks = [GUDLFTTaskSet]
+    # Temps d'attente entre deux scénarios
+    wait_time = between(1, 3)
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/unit/test_booking_manager_competition_full.py b/tests/unit/test_booking_manager_competition_full.py
new file mode 100644
index 000000000..45c26436b
--- /dev/null
+++ b/tests/unit/test_booking_manager_competition_full.py
@@ -0,0 +1,24 @@
+import unittest
+from app.booking_manager.booking_service import BookingService
+
+
+class TestBookingManagerCompetitionFull(unittest.TestCase):
+    def setUp(self):
+        self.service = BookingService(
+            clubs_file="data/clubs.json",
+            competitions_file="data/competitions.json"
+        )
+
+    def test_purchase_places_competition_full(self):
+        competition = self.service.competition_manager.find_by_name(
+            "Spring Festival")
+        if competition:
+            competition.number_of_places = 2  # Forcer une compétition avec peu de places
+        success = self.service.purchase_places(
+            "Iron Temple", "Spring Festival", 3)
+        self.assertFalse(
+            success, "L'achat de 3 places doit échouer si la compétition n'a que 2 places disponibles.")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_booking_manager_inexistant.py b/tests/unit/test_booking_manager_inexistant.py
new file mode 100644
index 000000000..0d5a368c5
--- /dev/null
+++ b/tests/unit/test_booking_manager_inexistant.py
@@ -0,0 +1,26 @@
+import unittest
+from app.booking_manager.booking_service import BookingService
+
+
+class TestBookingManagerInexistant(unittest.TestCase):
+    def setUp(self):
+        self.service = BookingService(
+            clubs_file="data/clubs.json",
+            competitions_file="data/competitions.json"
+        )
+
+    def test_purchase_places_club_not_found(self):
+        success = self.service.purchase_places(
+            "NonExistentClub", "Spring Festival", 3)
+        self.assertFalse(
+            success, "L'achat doit échouer si le club n'existe pas.")
+
+    def test_purchase_places_competition_not_found(self):
+        success = self.service.purchase_places(
+            "Iron Temple", "NonExistentCompetition", 3)
+        self.assertFalse(
+            success, "L'achat doit échouer si la compétition n'existe pas.")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_booking_manager_insufficient_points.py b/tests/unit/test_booking_manager_insufficient_points.py
new file mode 100644
index 000000000..ae86b7579
--- /dev/null
+++ b/tests/unit/test_booking_manager_insufficient_points.py
@@ -0,0 +1,23 @@
+import unittest
+from app.booking_manager.booking_service import BookingService
+
+
+class TestBookingManagerInsufficientPoints(unittest.TestCase):
+    def setUp(self):
+        self.service = BookingService(
+            clubs_file="data/clubs.json",
+            competitions_file="data/competitions.json"
+        )
+
+    def test_purchase_places_insufficient_points(self):
+        club = self.service.club_manager.find_by_name("Iron Temple")
+        if club:
+            club.points = 2  # Forcer un club avec peu de points
+        success = self.service.purchase_places(
+            "Iron Temple", "Spring Festival", 3)
+        self.assertFalse(
+            success, "L'achat de 3 places doit échouer si le club n'a que 2 points.")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_booking_service_persistence.py b/tests/unit/test_booking_service_persistence.py
new file mode 100644
index 000000000..db423138a
--- /dev/null
+++ b/tests/unit/test_booking_service_persistence.py
@@ -0,0 +1,120 @@
+import unittest
+import os
+import json
+from app.booking_manager.booking_service import BookingService
+
+
+class TestBookingServicePersistence(unittest.TestCase):
+    """
+    Teste la persistance des données lors de l'achat de places via BookingService.
+    """
+
+    def setUp(self):
+        """
+        Prépare deux fichiers JSON de test (pour clubs et compétitions)
+        afin de ne pas affecter les fichiers de production.
+        """
+        self.test_clubs_file = "data/test_clubs.json"
+        self.test_competitions_file = "data/test_competitions.json"
+
+        clubs_data = {
+            "clubs": [
+                {
+                    "id": "1",
+                    "name": "Simply Lift",
+                    "email": "john@simplylift.co",
+                    "points": "13"
+                },
+                {
+                    "id": "2",
+                    "name": "Iron Temple",
+                    "email": "admin@irontemple.com",
+                    "points": "4"
+                },
+                {
+                    "id": "3",
+                    "name": "She Lifts",
+                    "email": "kate@shelifts.co.uk",
+                    "points": "12"
+                }
+            ]
+        }
+
+        competitions_data = {
+            "competitions": [
+                {
+                    "name": "Spring Festival",
+                    "date": "2020-03-27 10:00:00",
+                    "numberOfPlaces": "25"
+                },
+                {
+                    "name": "Fall Classic",
+                    "date": "2020-10-22 13:30:00",
+                    "numberOfPlaces": "13"
+                }
+            ]
+        }
+
+        # Écrit les données JSON dans des fichiers temporaires
+        with open(self.test_clubs_file, "w") as f:
+            json.dump(clubs_data, f, indent=4)
+        with open(self.test_competitions_file, "w") as f:
+            json.dump(competitions_data, f, indent=4)
+
+        # Instancie BookingService avec ces fichiers de test
+        self.service = BookingService(
+            clubs_file=self.test_clubs_file,
+            competitions_file=self.test_competitions_file
+        )
+
+    def tearDown(self):
+        """
+        Supprime les fichiers temporaires pour ne pas polluer l'environnement.
+        """
+        if os.path.exists(self.test_clubs_file):
+            os.remove(self.test_clubs_file)
+        if os.path.exists(self.test_competitions_file):
+            os.remove(self.test_competitions_file)
+
+    def test_booking_service_persistence(self):
+        """
+        Vérifie qu'après une réservation de 3 places pour "Iron Temple" dans "Spring Festival" :
+        - Le nombre de points du club est mis à jour (4 - 3 = 1).
+        - Le nombre de places dans la compétition est mis à jour (25 - 3 = 22).
+        - Les modifications sont bien persistées dans les fichiers JSON.
+        """
+        success = self.service.purchase_places(
+            "Iron Temple", "Spring Festival", 3)
+        self.assertTrue(success, "La réservation devrait réussir.")
+
+        # Vérification du fichier JSON des clubs
+        with open(self.test_clubs_file, "r") as f:
+            clubs_data = json.load(f)
+
+        iron_club = next(
+            (club for club in clubs_data["clubs"]
+             if club["name"] == "Iron Temple"),
+            None
+        )
+        self.assertIsNotNone(
+            iron_club, "Les données du club 'Iron Temple' doivent être présentes.")
+        self.assertEqual(
+            iron_club["points"], "1", "Les points doivent être mis à jour (4 - 3 = 1).")
+
+        # Vérification du fichier JSON des compétitions
+        with open(self.test_competitions_file, "r") as f:
+            competitions_data = json.load(f)
+
+        spring_comp = next(
+            (comp for comp in competitions_data["competitions"]
+             if comp["name"] == "Spring Festival"),
+            None
+        )
+        self.assertIsNotNone(
+            spring_comp, "Les données de 'Spring Festival' doivent être présentes.")
+        self.assertEqual(spring_comp["numberOfPlaces"], "22",
+                         "Les places doivent être mises à jour (25 - 3 = 22).")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_club_manager_save.py b/tests/unit/test_club_manager_save.py
new file mode 100644
index 000000000..2b91f0ab7
--- /dev/null
+++ b/tests/unit/test_club_manager_save.py
@@ -0,0 +1,51 @@
+import unittest
+import os
+import json
+from app.booking_manager.club_manager import ClubManager
+
+
+class TestClubManagerSave(unittest.TestCase):
+    def setUp(self):
+        # Créez un fichier temporaire de clubs pour les tests
+        self.test_file = "data/test_clubs.json"
+        clubs_data = {
+            "clubs": [
+                {"id": "1", "name": "Simply Lift",
+                    "email": "john@simplylift.co", "points": "13"},
+                {"id": "2", "name": "Iron Temple",
+                    "email": "admin@irontemple.com", "points": "4"},
+                {"id": "3", "name": "She Lifts",
+                    "email": "kate@shelifts.co.uk", "points": "12"}
+            ]
+        }
+        with open(self.test_file, "w") as f:
+            json.dump(clubs_data, f, indent=4)
+        self.manager = ClubManager(clubs_file=self.test_file)
+
+    def tearDown(self):
+        # Supprimez le fichier temporaire après le test
+        if os.path.exists(self.test_file):
+            os.remove(self.test_file)
+
+    def test_save_clubs_updates_json(self):
+        # Modifier les points pour un club
+        club = self.manager.find_by_name("Iron Temple")
+        self.assertIsNotNone(club, "Le club 'Iron Temple' doit exister.")
+        club.points = 2  # On simule une réduction de points
+
+        # Sauvegarder l'état actuel
+        self.manager.save_clubs()
+
+        # Relire le fichier pour vérifier la mise à jour
+        with open(self.test_file, "r") as f:
+            data = json.load(f)
+        iron_data = next(
+            (c for c in data["clubs"] if c["name"] == "Iron Temple"), None)
+        self.assertIsNotNone(
+            iron_data, "Les données pour 'Iron Temple' doivent être présentes dans le fichier JSON.")
+        self.assertEqual(
+            iron_data["points"], "2", "Les points du club doivent être mis à jour dans le fichier JSON.")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_data_reset.py b/tests/unit/test_data_reset.py
new file mode 100644
index 000000000..58d9f9568
--- /dev/null
+++ b/tests/unit/test_data_reset.py
@@ -0,0 +1,80 @@
+# tests/unit/test_data_reset.py
+
+import unittest
+import os
+import shutil
+from app.booking_manager.club_manager import ClubManager
+from app.booking_manager.competition_manager import CompetitionManager
+
+
+class TestDataReset(unittest.TestCase):
+
+    def setUp(self):
+        # Chemins vers vos fichiers "de travail"
+        self.clubs_file = "data/clubs.json"
+        self.competitions_file = "data/competitions.json"
+
+        # Chemins vers des fichiers "frais" (après clonage)
+        self.fresh_clubs = "data/fresh_clubs.json"
+        self.fresh_competitions = "data/fresh_competitions.json"
+
+        # On suppose que fresh_clubs.json / fresh_competitions.json existent déjà
+        # et contiennent l'état initial (points=34 etc.)
+
+        # S'assurer de partir sur un état connu
+        shutil.copy(self.fresh_clubs, self.clubs_file)
+        shutil.copy(self.fresh_competitions, self.competitions_file)
+
+        self.club_manager = ClubManager(self.clubs_file)
+        self.competition_manager = CompetitionManager(self.competitions_file)
+
+    def tearDown(self):
+        # (optionnel) remettre fresh ou faire rien
+        pass
+
+    def test_reset_data(self):
+        """
+        Vérifie qu'après une modification, reset_data() restaure l'état initial.
+        """
+        # 1) On modifie un club
+        club = self.club_manager.find_by_name("Iron Temple")
+        self.assertIsNotNone(club)
+        club.points = 0  # On simule un usage
+
+        # 2) Sauvegarder
+        self.club_manager.save_clubs()
+
+        # 3) Vérifie qu'il a bien 0 points
+        manager_check = ClubManager(self.clubs_file)
+        updated_club = manager_check.find_by_name("Iron Temple")
+        self.assertEqual(updated_club.points, 0)
+
+        # 4) On appelle reset_data
+        self.club_manager.reset_data(self.fresh_clubs)
+
+        # 5) Vérifie que c'est revenu à l'état frais (34 par exemple)
+        club_after_reset = self.club_manager.find_by_name("Iron Temple")
+        self.assertEqual(club_after_reset.points, 34,
+                         "Le club doit retrouver ses points initiaux.")
+
+        # Pareil pour les compétitions
+        comp = self.competition_manager.find_by_name("Spring Festival")
+        self.assertIsNotNone(comp)
+        comp.number_of_places = 10
+        self.competition_manager.save_competitions()
+
+        # re-load pour vérifier
+        manager_check_comp = CompetitionManager(self.competitions_file)
+        self.assertEqual(manager_check_comp.find_by_name(
+            "Spring Festival").number_of_places, 10)
+
+        # reset
+        self.competition_manager.reset_data(self.fresh_competitions)
+        comp_after_reset = self.competition_manager.find_by_name(
+            "Spring Festival")
+        self.assertEqual(comp_after_reset.number_of_places, 39,
+                         "La compétition doit retrouver ses places initiales (39).")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/unit/test_points_display.py b/tests/unit/test_points_display.py
new file mode 100644
index 000000000..970e373d2
--- /dev/null
+++ b/tests/unit/test_points_display.py
@@ -0,0 +1,35 @@
+# tests/unit/test_points_display.py
+
+import unittest
+from app.server import app
+
+
+class TestPointsDisplay(unittest.TestCase):
+    def setUp(self):
+        app.config['TESTING'] = True
+        self.client = app.test_client()
+
+    def test_points_display_on_index(self):
+        response = self.client.get("/")
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(b"Points Clubs", response.data)
+
+    def test_points_display_on_welcome(self):
+        response = self.client.post(
+            "/showSummary", data={"email": "admin@irontemple.com"}, follow_redirects=True)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(b"Points disponibles", response.data)
+
+    def test_clubs_points_route(self):
+        """
+        Vérifie GET /clubsPoints (200 OK, contient liste des clubs).
+        """
+        response = self.client.get("/clubsPoints")
+        self.assertEqual(response.status_code, 200,
+                         "/clubsPoints doit être accessible en GET.")
+        self.assertIn(b"Points des Clubs", response.data,
+                      "Doit afficher le titre 'Points des Clubs'.")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py
new file mode 100644
index 000000000..2c53c197d
--- /dev/null
+++ b/tests/unit/test_server.py
@@ -0,0 +1,56 @@
+import unittest
+from app.server import app
+
+
+class TestWelcomePage(unittest.TestCase):
+    """
+    Teste la page welcome en cas d'email inconnu.
+    """
+
+    def setUp(self):
+        """
+        Prépare l'application Flask en mode TEST.
+        """
+        app.config['TESTING'] = True
+        self.client = app.test_client()
+
+    def test_show_summary_no_undefined_error_for_unknown_email(self):
+        """
+        Vérifie que l'application redirige (302) pour un email inconnu
+        et ne produit pas d'erreur Jinja une fois la redirection suivie.
+        """
+        response = self.client.post(
+            '/showSummary',
+            data={'email': 'email_inconnu@example.com'}
+        )
+
+        # On s'attend à un code 302 (redirection)
+        self.assertEqual(
+            response.status_code,
+            302,
+            "La route doit rediriger pour un email inconnu."
+        )
+
+        # Optionnel : suivre la redirection pour valider le contenu final
+        response_followed = self.client.post(
+            '/showSummary',
+            data={'email': 'email_inconnu@example.com'},
+            follow_redirects=True
+        )
+        # Après la redirection, on devrait avoir un code 200 (page affichée)
+        self.assertEqual(
+            response_followed.status_code,
+            200,
+            "Après redirection, on doit avoir un code 200."
+        )
+
+        # Vérifie qu'on ne voit pas 'UndefinedError' dans la page
+        self.assertNotIn(
+            b'UndefinedError',
+            response_followed.data,
+            "Aucune erreur Jinja ne doit être présente dans la page finale."
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_server_misc.py b/tests/unit/test_server_misc.py
new file mode 100644
index 000000000..ce0e73487
--- /dev/null
+++ b/tests/unit/test_server_misc.py
@@ -0,0 +1,57 @@
+# tests/unit/test_server_misc.py
+
+import unittest
+from app.server import app
+
+
+class TestServerMisc(unittest.TestCase):
+
+    def setUp(self):
+        app.config['TESTING'] = True
+        self.client = app.test_client()
+
+    def test_logout_not_logged_in(self):
+        """
+        Vérifie que /logout redirige même si on n'est pas connecté.
+        """
+        response = self.client.get("/logout", follow_redirects=True)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(b"Bienvenue sur GUDLFT", response.data,
+                      "On doit se retrouver sur la page d'accueil.")
+
+    def test_book_inexisting_competition(self):
+        """
+        GET /book/<competition>/<club> avec competition inexistante => redirige + flash
+        """
+        response = self.client.get(
+            "/book/FakeCompetition/Iron Temple", follow_redirects=True)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(b"Something went wrong", response.data,
+                      "Flash message d'erreur attendue.")
+
+    def test_book_inexisting_club(self):
+        """
+        GET /book/<competition>/<club> avec club inexistant => redirige + flash
+        """
+        response = self.client.get(
+            "/book/Spring Festival/FakeClub", follow_redirects=True)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(b"Something went wrong", response.data)
+
+    def test_purchase_places_value_error(self):
+        """
+        Envoie un POST /purchasePlaces avec places = 'abc' => ValueError => flash + redirect
+        """
+        data = {
+            "club": "Iron Temple",
+            "competition": "Spring Festival",
+            "places": "abc"
+        }
+        response = self.client.post(
+            "/purchasePlaces", data=data, follow_redirects=True)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(b"Le nombre de places est invalide.", response.data)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_static_files.py b/tests/unit/test_static_files.py
new file mode 100644
index 000000000..f64387a4a
--- /dev/null
+++ b/tests/unit/test_static_files.py
@@ -0,0 +1,31 @@
+# tests/unit/test_static_files.py
+
+import unittest
+from app.server import app
+
+
+class TestStaticFiles(unittest.TestCase):
+    def setUp(self):
+        app.config['TESTING'] = True
+        self.client = app.test_client()
+
+    def test_css_loaded(self):
+        response = self.client.get('/static/css/style.css')
+        self.assertEqual(response.status_code, 200,
+                         "Le CSS doit être accessible.")
+        self.assertTrue(len(response.data) > 0,
+                        "Le fichier CSS ne doit pas être vide.")
+        self.assertIn(b"body", response.data,
+                      "Le CSS doit contenir la règle 'body'.")
+
+    def test_favicon_404(self):
+        """
+        Vérifie que /favicon.ico renvoie 404 (puisqu'on n'en a pas).
+        """
+        response = self.client.get('/favicon.ico')
+        self.assertEqual(response.status_code, 404,
+                         "On s'attend à un 404 pour /favicon.ico inexistant.")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_welcome_page.py b/tests/unit/test_welcome_page.py
new file mode 100644
index 000000000..2db47b285
--- /dev/null
+++ b/tests/unit/test_welcome_page.py
@@ -0,0 +1,54 @@
+# tests/unit/test_welcome_page.py
+
+import unittest
+from app.server import app
+
+
+class TestWelcomePage(unittest.TestCase):
+
+    def setUp(self):
+        app.config['TESTING'] = True
+        self.client = app.test_client()
+
+    def test_show_summary_unknown_email_redirects(self):
+        """
+        Vérifie qu'un email inconnu redirige (302) et ne produit pas d'erreur Jinja.
+        """
+        response = self.client.post(
+            '/showSummary', data={'email': 'unknown@example.com'})
+        self.assertEqual(response.status_code, 302,
+                         "Doit rediriger pour un email inconnu.")
+
+        response_followed = self.client.post(
+            '/showSummary', data={'email': 'unknown@example.com'}, follow_redirects=True)
+        self.assertEqual(response_followed.status_code, 200,
+                         "Après redirection, code doit être 200.")
+        self.assertNotIn(b"UndefinedError", response_followed.data,
+                         "Aucune erreur Jinja ne doit apparaître.")
+
+    def test_show_summary_no_undefined_error_for_unknown_email(self):
+        """
+        Variante du test (même scenario), on check juste qu'on a 200 au final.
+        """
+        response = self.client.post(
+            '/showSummary', data={'email': 'email_inconnu@example.com'})
+        self.assertEqual(response.status_code, 302,
+                         "Redirection pour email inconnu.")
+        response_followed = self.client.post(
+            '/showSummary', data={'email': 'email_inconnu@example.com'}, follow_redirects=True)
+        self.assertEqual(response_followed.status_code, 200)
+        self.assertNotIn(b'UndefinedError', response_followed.data)
+
+    def test_show_summary_get_method_not_allowed(self):
+        """
+        Tente un GET sur /showSummary (route prévue en POST). Vérifie qu'on a un code 405 (Method Not Allowed)
+        ou potentiellement 308/302 selon config.
+        """
+        response = self.client.get('/showSummary')
+        # Souvent Flask renvoie 405 si pas de route GET, mais ça dépend de la config
+        self.assertIn(response.status_code, [
+                      302, 405], "GET /showSummary n'est pas autorisé, on attend 405 ou une redirection.")
+
+
+if __name__ == '__main__':
+    unittest.main()