Enregistrement du joueur
Créer un composant React pour permettre au joueur de s’enregistrer de manière sécurisée avant de commencer le quiz

Objectifs de la séance
- Comprendre ce qu’est un composant React
- Mettre en place une authentification sécurisée avec Supabase
- Utiliser un trigger pour lier un utilisateur authentifié à un joueur
- Créer des politiques RLS (Row Level Security) pour protéger les données
- Créer un formulaire d’enregistrement pour demander le pseudo du joueur
- Préparer le terrain pour enregistrer son score plus tard
Notions théoriques
Qu’est-ce qu’un composant React ?

Un composant est un bloc de code réutilisable.
Un composant peut contenir :
- du HTML (rendu à l’écran)
- du JavaScript (logique de l’interface)
- des états (
useState) pour gérer les données internes - des props (paramètres) pour personnaliser son comportement
Pourquoi créer des composants ?
- Pour organiser le code en petites parties lisibles
- Pour réutiliser des éléments (ex. : une carte, un bouton, un formulaire)
- Pour séparer les responsabilités : chaque composant a un rôle précis
Exemple simple de composant
// app/components/Bonjour.tsx
export default function Bonjour() {
return <p>Bonjour !</p>;
}
Et dans app/page.tsx :
import Bonjour from "./components/Bonjour";
export default function Home() {
return (
<div>
<Bonjour />
</div>
);
}
Composant avec état (useState)
"use client";
import { useState } from "react";
export default function Compteur() {
const [nombre, setNombre] = useState(0);
return (
<div>
<p>Vous avez cliqué {nombre} fois</p>
<button onClick={() => setNombre(nombre + 1)}>Cliquez ici</button>
</div>
);
}
Où créer les composants ?
Dans un projet Next.js, vous pouvez créer un dossier pour vos composants :
/app/components/
Chaque composant est un fichier .tsx (TypeScript + JSX).
- Le TypeScript est une extension de JavaScript qui ajoute la gestion des types.
- JSX est une syntaxe qui permet de mélanger du JavaScript et du HTML.
Étapes pour enregistrer un joueur de manière sécurisée
- Utiliser l'authentification anonyme de Supabase pour créer un utilisateur sécurisé.
- Créer un trigger qui se déclenche à la création de l'utilisateur pour l'insérer automatiquement dans la table
joueur. - Créer une politique RLS pour que chaque utilisateur ne puisse voir et modifier que ses propres données.
- Créer un composant
FormulaireJoueurqui appellesignInAnonymously. - Stocker l'ID de l'utilisateur Supabase dans le
localStorage. - Utiliser cet ID pour lier les futures actions (comme le score) au joueur.
Table joueur dans Supabase
La table joueur doit être modifiée pour être liée à la table auth.users de Supabase.
CREATE TABLE public.joueur (
id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
pseudo character varying NOT NULL,
email character varying,
date_inscription date NOT NULL,
user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE,
CONSTRAINT joueur_pkey PRIMARY KEY (id),
CONSTRAINT joueur_user_id_unique UNIQUE (user_id)
);
Nous utiliserons le champ pseudo pour stocker le nom du joueur et le champ user_id pour le lier de manière sécurisée à un compte Supabase.
Sécurité : Authentification et RLS
1. Activer l'authentification anonyme
Dans la console Supabase, allez dans Authentication > Settings et activez Enable anonymous sign-in.
2. Créer un trigger pour créer le joueur automatiquement
Pour garantir que chaque utilisateur authentifié a une entrée correspondante dans la table joueur, nous créons une fonction et un trigger.
-- La fonction qui sera exécutée par le trigger
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
-- On récupère le pseudo depuis les métadonnées envoyées lors de l'inscription
DECLARE new_pseudo TEXT;
new_pseudo := NEW.raw_user_meta_data->>'pseudo';
-- On lève une erreur explicite si le pseudo est manquant ou vide
IF new_pseudo IS NULL OR trim(new_pseudo) = '' THEN
RAISE EXCEPTION 'Le pseudo est obligatoire pour créer un joueur.';
END IF;
-- Si le pseudo est présent, on insère la nouvelle ligne dans la table joueur
INSERT INTO public.joueur (user_id, pseudo, date_inscription)
VALUES (
NEW.id,
new_pseudo,
CURRENT_DATE
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Le trigger qui déclenche la fonction après qu'un nouvel utilisateur soit créé dans auth.users
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
3. Créer une politique RLS sécurisée
Activez les RLS sur la table joueur et créez une politique qui n'autorise l'accès qu'au propriétaire de la ligne.
-- Activer RLS
ALTER TABLE public.joueur ENABLE ROW LEVEL SECURITY;
-- Créer la politique sécurisée
CREATE POLICY "A user can view and modify their own player data"
ON public.joueur
FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
Cette politique est beaucoup plus sûre car elle s'assure qu'un utilisateur ne peut voir ou modifier que la ligne de la table joueur qui lui est directement liée via son user_id.
Quelques méthodes à connaître
| Méthode / outil | Utilité |
|---|---|
useState | Gérer des champs de formulaire |
supabase.auth.signInAnonymously() | Créer un utilisateur anonyme sécurisé |
localStorage.setItem(...) | Mémoriser un ID dans le navigateur |
onSubmit={handleSubmit} | Gérer l’envoi d’un formulaire |
preventDefault() | Empêcher le rechargement de la page |
Test de mémorisation / compréhension
TP pour réfléchir et résoudre des problèmes
Objectif
Créer un formulaire de début de partie et enregistrer le joueur de manière sécurisée en le liant au système d'authentification de Supabase.

Lorsque le joueur a saisi son nom et clique sur le bouton Commencer le quiz :
- authentifier l'utilisateur anonymement
- insérer un joueur dans Supabase (via un trigger)
- stocker l’ID de l'utilisateur Supabase dans
localStorage
Étapes du TP
- Modifier la table
joueur - Créer un trigger
- Créer une RLS
- Ajouter les valeurs par défaut
- Créer un composant
FormulaireJoueur.tsxpour afficher un formulaire avec un champPseudoobligatoire - Modifier app/page.tsx pour afficher ce formulaire avant le quiz pour afficher ce composant dans
app/page.tsxavant le quiz et n'autoriser l’accès au quiz que si un joueur est enregistré
Étape 1 : Modifier la table joueur
Utiliser l'authentification de Supabase pour lier chaque joueur à un utilisateur sécurisé
Supabase offre une authentification anonyme qui crée un utilisateur temporaire dans la table auth.users.
Cette authentification fournie par Supabase vous donne un auth.uid() fiable et sécurisé à utiliser dans vos politiques RLS.
Nous allons lier chaque joueur à un utilisateur de la table auth.users.
Qu'est-ce que la table auth.users ?
La table auth.users est une table spéciale gérée par Supabase qui stocke les informations des utilisateurs authentifiés. Elle contient des champs comme id, email, created_at, etc. Le champ id est un identifiant unique pour chaque utilisateur, souvent de type uuid.

Pour gérer les utilisateurs, dans Supabase, il suffit d'aller dans l'onglet Authentication > Users.
La meilleure façon de lier les joueurs à la table auth.users, c'est :
-
d'ajouter une colonne
user_idde typeuuidet d'y mettre une clé étrangère-- Ajouter une colonne pour lier chaque joueur à un utilisateur Supabase
ALTER TABLE public.joueur
ADD COLUMN user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE;
-
de la rendre colonne
user_idunique pour garantir une relation 1-pour-1-- Rendre cette colonne unique pour garantir la relation 1-pour-1
ALTER TABLE public.joueur ADD CONSTRAINT joueur_user_id_unique UNIQUE (user_id);
Étape 2 : Créer un trigger
Créer un trigger pour créer le joueur automatiquement
Pour que le processus soit fluide et sécurisé, on va créer un trigger qui, dès qu'un utilisateur s'inscrit (anonymement), crée automatiquement une ligne correspondante dans la table joueur.
Nous apprendrons plus tard à créer des triggers
Voici le code SQL pour créer la fonction qui sera exécutée par le trigger :
-- Fonction qui sera exécutée par le trigger
-- On utilise CREATE OR REPLACE pour mettre à jour la fonction existante
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$ DECLARE
-- La déclaration des variables doit se faire AVANT le bloc BEGIN
new_pseudo TEXT;
BEGIN
-- On récupère le pseudo depuis les métadonnées envoyées lors de l'inscription
new_pseudo := NEW.raw_user_meta_data->>'pseudo';
-- On lève une erreur explicite si le pseudo est manquant ou vide
IF new_pseudo IS NULL OR trim(new_pseudo) = '' THEN
RAISE EXCEPTION 'Le pseudo est obligatoire pour créer un joueur.';
END IF;
-- Si le pseudo est présent, on insère la nouvelle ligne dans la table joueur
INSERT INTO public.joueur (user_id, pseudo, date_inscription)
VALUES (
NEW.id,
new_pseudo,
CURRENT_DATE
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

Voici le code SQL pour créer le trigger qui utilise cette fonction :
-- Trigger qui déclenche la fonction après qu'un nouvel utilisateur soit créé dans auth.users
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();

Étape 3. Créer les RLS
-
Vérifiez que les RLS sont activées sur la table
joueur:-- Activer RLS
ALTER TABLE joueur ENABLE ROW LEVEL SECURITY;
-
Créez une politique sécurisée qui n'autorise l'accès qu'au propriétaire des données. C'est beaucoup plus sûr que d'autoriser l'accès public.
Voici le code SQL pour la politique RLS sécurisée
-- Créer la politique sécurisée
CREATE POLICY "A user can view and modify their own player data"
ON public.joueur
FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
astuceCette politique garantit qu'un utilisateur ne peut modifier que la ligne de la table
joueurqui lui appartient (user_id).
Étape 4 : Ajouter les valeurs par défaut
Pour conserver la date de création et d’inscription du joueur, nous allons ajouter des valeurs par défaut dans la table joueur, pour les champs created_at et date_inscription.
-
Voici le code SQL à exécuter dans Supabase :
-- Ajouter une valeur par défaut pour le champ created_at
ALTER TABLE joueur
ALTER COLUMN created_at SET DEFAULT now();-- Ajouter une valeur par défaut pour le champ date_inscription
ALTER TABLE joueur
ALTER COLUMN date_inscription SET DEFAULT now();
Étape 5. Créer FormulaireJoueur.tsx
Dans le dossier app/components/, créez le fichier FormulaireJoueur.tsx.
Le composant FormulaireJoueur va appeler la fonction signInAnonymously (il ne va pas insérer lui même les données).
-
Voici le code complet du fichier
app/components/FormulaireJoueur.tsx"use client";
import { useState } from "react";
import { supabase } from "@/lib/supabaseClient";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
export default function FormulaireJoueur({ onJoueurCree }: { onJoueurCree: () => void }) {
const [nom, setNom] = useState("");
const [message, setMessage] = useState("");
const [isLoading, setIsLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setMessage("");
setIsLoading(true);
if (!nom.trim()) {
setMessage("Veuillez entrer un pseudo.");
setIsLoading(false);
return;
}
// On s'authentifie anonymement en passant le pseudo dans les métadonnées
const { data: authData, error: authError } = await supabase.auth.signInAnonymously({
options: {
data: {
pseudo: nom,
},
},
});
if (authError) {
setMessage(`Erreur: ${authError.message}`);
console.error(authError);
} else if (authData.user) {
// Si l'authentification réussit, le trigger a déjà créé le joueur.
// On stocke l'ID de l'utilisateur Supabase pour les futures requêtes.
localStorage.setItem("supabase_user_id", authData.user.id);
setMessage(`Bienvenue ${nom} !`);
onJoueurCree();
}
setIsLoading(false);
}
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto mt-10 space-y-4">
<label className="block text-lg font-semibold">Pseudo</label>
<Input
type="text"
value={nom}
onChange={(e) => setNom(e.target.value)}
placeholder="Choisissez un pseudo"
disabled={isLoading}
/>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Connexion..." : "Commencer le quiz"}
</Button>
{message && (
<Alert className="mt-4">
<AlertTitle>Info</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
</form>
);
}
Si erreur
Impossible de localiser le module '@/components/ui/input'Si l'erreur
Impossible de localiser le module '@/components/ui/input's'affiche, c'est que le composantInputn'existe pas dans votre projet.Il suffit d'installer le composant
Inputde Shadcn, tel qu'indiqué sur https://ui.shadcn.com/docs/components/input.Utilisez la commande suivante dans votre terminal, à la racine du projet :
npx shadcn@latest add input
Si une erreur s'affiche
Veuillez vérifier votre trigger
handle_new_user().
Décryptage du code du composant
Le composant FormulaireJoueur.tsx a pour objectif d’afficher un formulaire permettant à un joueur de saisir son pseudo, puis de l’authentifier de manière anonyme. Le trigger se charge ensuite de créer l'entrée dans la table joueur.
Une fois l'utilisateur authentifié, son identifiant Supabase est mémorisé dans le navigateur (dans le localStorage) et le quiz démarre.
Fonction principale du composant
export default function FormulaireJoueur({ onJoueurCree }: { onJoueurCree: () => void }) {
- Il s'agit d’un composant fonctionnel.
- Il reçoit une propriété (prop) appelée
onJoueurCree: c’est une fonction que le parent (dans notre cas,page.tsx) va lui transmettre. - Cette fonction sera appelée quand le joueur est bien enregistré, afin d’indiquer que le quiz peut commencer.
Déclaration des états
const [nom, setNom] = useState("");
const [message, setMessage] = useState("");
const [isLoading, setIsLoading] = useState(false);
nom: contient le texte saisi par l’utilisateur dans le champ de formulaire.message: contient un message d’erreur ou de confirmation à afficher.isLoading: un état pour gérer l'affichage d'un indicateur de chargement et désactiver le bouton pendant l'appel à l'API.
Fonction de traitement du formulaire
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
- Cette fonction est appelée lorsque l’utilisateur soumet le formulaire.
e.preventDefault()empêche le comportement par défaut du formulaire (qui est de recharger la page).
Vérification du champ
if (!nom.trim()) {
setMessage("Veuillez entrer un pseudo.");
return;
}
- Si le champ est vide ou contient uniquement des espaces, on affiche un message d’erreur.
Authentification et création automatique
const { data: authData, error: authError } = await supabase.auth.signInAnonymously({
options: {
data: {
pseudo: nom,
},
},
});
- On appelle la fonction
signInAnonymouslyde Supabase. - On passe le pseudo dans les
options.data. Ces données seront accessibles dans le trigger viaNEW.raw_user_meta_data. - La méthode retourne un objet avec
data(contenant l'utilisateur créé) eterror.
Traitement du résultat
if (authError) {
setMessage(`Erreur: ${authError.message}`);
console.error(authError);
} else if (authData.user) {
localStorage.setItem("supabase_user_id", authData.user.id);
setMessage(`Bienvenue ${nom} !`);
onJoueurCree();
}
- Si une erreur survient, on l'affiche.
- Sinon :
- On stocke l’
idde l'utilisateur Supabase (pas l'ID du joueur) dans lelocalStorage. - On affiche un message de bienvenue
- On appelle la fonction
onJoueurCree()pour signaler au parent que tout est prêt.
- On stocke l’
Affichage du formulaire
<form onSubmit={handleSubmit} className="max-w-md mx-auto mt-10 space-y-4">
- Le formulaire est centré (
mx-auto) et limité à une largeur (max-w-md). onSubmit={handleSubmit}: quand l’utilisateur valide, on appelle notre fonction.
Champ de saisie
<label className="block text-lg font-semibold">Pseudo</label>
<Input
type="text"
value={nom}
onChange={(e) => setNom(e.target.value)}
placeholder="Choisissez un pseudo"
disabled={isLoading}
/>
- Le champ affiche le contenu de
nom - À chaque frappe de l’utilisateur, on met à jour
nomavecsetNom - Il est désactivé pendant le chargement.
Bouton de validation
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Connexion..." : "Commencer le quiz"}
</Button>
- Le texte du bouton change pour indiquer l'état de chargement.
Affichage du message
{message && (
<Alert className="mt-4">
<AlertTitle>Info</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
- Si un message est défini (erreur ou confirmation), on l’affiche dans un composant
Alert.
Résumé du fonctionnement
| Étape | Description |
|---|---|
| 1 | L’utilisateur voit un champ pour entrer un pseudo |
| 2 | Il clique sur "Commencer le quiz" |
| 3 | Le formulaire appelle signInAnonymously sans recharger la page |
| 4 | Le trigger crée le joueur dans la base Supabase |
| 5 | L'ID de l'utilisateur Supabase est stocké dans le navigateur |
| 6 | Le quiz démarre |
Étape 6. Modifier app/page.tsx
Modifiez le fichier app/page.tsx pour afficher le formulaire avant le quiz, et n’autoriser l’accès au quiz que si un joueur est enregistré.
Votre fichier app/page.tsx doit :
-
chercher
supabase_user_iddans lelocalStorageau chargement -
utiliser
user_idpour les requêtes SQL dans Supabase -
si l’ID est trouvé, afficher le quiz
-
sinon, afficher le composant
FormulaireJoueur -
une fois le joueur créé, passer à l’affichage du quiz
-
stocker le nom du joueur pour l’afficher
-
Dans le fichier
app/page.tsx, on affiche ce composant seulement si aucun joueur n’est encore enregistré :const [joueurPret, setJoueurPret] = useState(false);
useEffect(() => {
// On ne lance la récupération que si joueurPret est passé à 'true'
if (joueurPret) {
const userId = localStorage.getItem("supabase_user_id");
if (userId) {
supabase
.from("joueur")
.select("pseudo")
.eq("user_id", userId)
.single()
.then(({ data, error }) => {
if (error) {
console.error("Erreur lors de la récupération du joueur :", error);
} else if (data) {
setJoueurNom(data.pseudo);
}
});
}
}
}, []);Et dans le
return:{!joueurPret ? (
<FormulaireJoueur onJoueurCree={() => setJoueurPret(true)} />
) : (
// Affichage du quiz
)}- Si
joueurPretestfalse, on affiche le formulaire. - Quand le joueur est créé, on appelle
setJoueurPret(true)pour afficher le quiz. - La logique pour récupérer le nom du joueur utilise maintenant
supabase_user_idet filtre sur la colonneuser_idde la tablejoueur.
Voici une solution possible du code complet de
app/page.tsxaprès modification"use client";
import { useEffect, useState } from "react";
import { supabase } from "../lib/supabaseClient";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import FormulaireJoueur from "@/components/FormulaireJoueur";
import Image from "next/image";
import Link from "next/link";
export default function Home() {
const [questions, setQuestions] = useState<any[]>([]);
const [questionIndex, setQuestionIndex] = useState(0);
const [explication, setExplication] = useState("");
const [afficherExplication, setAfficherExplication] = useState(false);
const [joueurNom, setJoueurNom] = useState("");
const question = questions[questionIndex];
const [joueurPret, setJoueurPret] = useState(false);
useEffect(() => {
// On ne lance la récupération que si joueurPret est passé à 'true'
if (joueurPret) {
const userId = localStorage.getItem("supabase_user_id");
if (userId) {
supabase
.from("joueur")
.select("pseudo")
.eq("user_id", userId)
.single()
.then(({ data, error }) => {
if (error) {
console.error("Erreur lors de la récupération du joueur :", error);
} else if (data) {
setJoueurNom(data.pseudo);
}
});
}
}
}, []);
useEffect(() => {
async function fetchQuestion() {
const { data, error } = await supabase
.from("question")
.select(`
id,
texte,
image_url,
image_credit_nom,
image_credit_url,
explication,
reponses:reponse (
id,
texte,
est_correcte
)
`)
.order("id", { ascending: true });
console.log("Données récupérées :", data);
if (error) {
console.error("Erreur Supabase :", error);
} else {
setQuestions(data || []); // On prend toutes les questions récupérées
}
}
fetchQuestion();
}, []);
function handleClick(reponse: any) {
if (!question || afficherExplication) return;
const estBonneReponse = reponse.est_correcte;
const message = estBonneReponse
? "✅ Bonne réponse !"
: "❌ Mauvaise réponse.";
const explicationTexte = message + " " + question.explication || message;
setExplication(explicationTexte);
setAfficherExplication(true);
setTimeout(() => {
setAfficherExplication(false);
setExplication("");
setQuestionIndex((prev) => prev + 1);
}, 5000);
}
if (!question) {
return (
<div className="text-center mt-10">
<h2 className="text-2xl font-bold">Quiz terminé !</h2>
<p className="mt-4 text-muted-foreground">Merci d’avoir participé.</p>
</div>
);
}
return (
<div>
{!joueurPret ? (
<FormulaireJoueur onJoueurCree={() => setJoueurPret(true)} />
) : (
<div>
<Alert className="bg-blue-50 border-blue-300 text-blue-800 max-w-xl mx-auto mt-6">
<AlertTitle className="text-xl font-semibold">Bienvenue sur CyberQuiz</AlertTitle>
<AlertDescription>
Un quiz pour tester vos connaissances en cybersécurité.
</AlertDescription>
</Alert>
{questions.length > 0 ? (
<Card className="max-w-4xl mx-auto mt-6">
<div className="flex flex-col md:flex-row">
{/* Colonne gauche : image + crédit */}
<div className="w-full md:w-1/2 p-4">
{question.image_url ? (
<Image
src={question.image_url}
alt="Illustration de la question"
width={400}
height={300}
className="rounded"
/>
) : (
<div className="w-full h-[300px] bg-gray-100 flex items-center justify-center text-sm text-gray-500 rounded">
Aucune image disponible
</div>
)}
{question.image_credit_nom && question.image_credit_url && (
<Alert className="mt-4 text-sm text-muted-foreground">
<AlertDescription>
<span className="inline">
Image :{" "}
<Link
href={question.image_credit_url}
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-2 hover:text-primary"
>
{question.image_credit_nom}
</Link>
</span>
</AlertDescription>
</Alert>
)}
</div>
{/* Colonne droite : question + réponses */}
<div className="w-full md:w-1/2 p-4">
<CardHeader className="p-0 mb-4">
<CardTitle>Question</CardTitle>
</CardHeader>
<CardContent className="p-0">
<p className="text-lg font-semibold mb-4">{question.texte}</p>
{question.reponses.map((reponse: any) => (
<Button
key={reponse.id}
onClick={() => handleClick(reponse)}
className="w-full justify-start mt-2 whitespace-normal text-left"
variant="outline"
>
{reponse.texte}
</Button>
))}
</CardContent>
{afficherExplication && (
<Alert className="mt-6 bg-yellow-50 border-yellow-300 text-yellow-800">
<AlertTitle>Explication</AlertTitle>
<AlertDescription>{explication}</AlertDescription>
</Alert>
)}
</div>
</div>
</Card>
) : (
<p className="text-center mt-6">Chargement de la question...</p>
)}
</div>
)}
</div>
);
}Résultat attendu
- Le joueur saisit un pseudo
- Il est authentifié anonymement et enregistré dans Supabase
- L'ID de l'utilisateur Supabase est stocké dans
localStorage - Le quiz démarre uniquement après l'enregistrement

Si vous avez une erreur
Anonymous sign-ins are disabledlors de l'insertion du joueurCela signifie que l'authentification anonyme n'est pas activée dans votre projet Supabase.
Pour l'activer, il vous suffit de vous rendre dans la console Supabase et d'activer cette option.
-
Ouvrez le dashboard de votre projet dans Supabase
-
Dans le menu de gauche, allez dans la section Authentication.
-
Cliquez sur Sign In / Providers.
-
Dans la section User Signups.
-
Trouvez le paramètre Allow anonymous sign-ins et activez-le en basculant l'interrupteur sur ON.

-
Cliquez sur Save changes.

Pourquoi cette étape est-elle nécessaire ?
Par défaut, Supabase ne permet pas les connexions anonymes pour des raisons de sécurité. C'est une fonctionnalité que vous devez explicitement activer. En l'activant, vous autorisez votre application à créer des utilisateurs temporaires via la fonction
supabase.auth.signInAnonymously(), ce qui est exactement ce que nous avons besoin pour notre application.Une fois cette option activée et sauvegardée, retournez dans votre application et réessayez de soumettre le formulaire. L'erreur devrait avoir disparu et le joueur sera correctement créé.
- Si
Bonus : Afficher le nom du joueur
Comment afficher le nom du joueur dans la page du quiz ?

Maintenant que le joueur est enregistré, que son ID d'utilisateur Supabase est stocké dans le
localStorage, et que nous récupérons son nom depuis Supabase nous pouvons afficher son nom dans l’interface du quiz, par exemple dans l’en-tête ou dans une alerte de bienvenue.
Dans le app/page.tsx, juste au-dessus du quiz, vous pouvez afficher une alerte personnalisée avec le nom du joueur :
<Alert className="bg-green-50 border-green-300 text-green-800 max-w-xl mx-auto mt-6">
<AlertTitle className="text-xl font-semibold">
Bienvenue {joueurNom} !
</AlertTitle>
<AlertDescription>
Préparez-vous à tester vos connaissances en cybersécurité.
</AlertDescription>
</Alert>
Le nom du joueur ne s’affiche pas
Le nom du joueur ne s’affiche pas lors de l’affichage de la 1ère question.
Il faut rafraichir le navigateur, ce n'est pas satisfaisant !
Lorsque le composant page.tsx est monté pour la première fois, il exécute l'effet useEffect qui vérifie si un supabase_user_id est stocké dans le localStorage.
Si c'est le cas, il définit joueurPret sur true et lance une requête pour récupérer le nom du joueur depuis la table joueur.
Cependant, cette requête est asynchrone, ce qui signifie qu'elle ne bloque pas le rendu initial du composant.
Ainsi, lors du premier rendu, le nom du joueur n'est pas encore disponible, car la requête pour le récupérer est toujours en cours.
La solution la plus simple à mettre en place, consiste à forcer la ré-exécution de l'effet useEffect lorsque joueurPret change.
Il suffit d'ajouter joueurPret comme dépendance au useEffect, afin de lui dire de se ré-exécuter chaque fois que l'état joueurPret change :
// Dans app/page.tsx
useEffect(() => {
// On ne lance la récupération que si joueurPret est passé à 'true'
if (joueurPret) {
const userId = localStorage.getItem("supabase_user_id");
if (userId) {
supabase
.from("joueur")
.select("pseudo")
.eq("user_id", userId)
.single()
.then(({ data, error }) => {
if (error) {
console.error("Erreur lors de la récupération du joueur :", error);
} else if (data) {
setJoueurNom(data.pseudo);
}
});
}
}
}, [joueurPret]); // <-- On ajoute joueurPret comme dépendance
Une proposition de solution du code complet de app/page.tsx
Vous devez être connecté pour voir le contenu.