App de Sondage Temps Réel (Clone Kahoot)
Application de sondage en temps réel
Résumé express : Créer une application complète type Kahoot permettant de créer et rejoindre des sondages avec votes en temps réel et affichage des résultats en barres animées.
1. Architecture du projet
- Créer deux dossiers :
- frontend : pour l'interface utilisateur
- backend : pour la base de données et les routes
Organisation du backend
- Créer un dossier tables pour les structures de données
- Créer un dossier services pour les routes API
2. Page de création de sondage
Créer la page CreatePoll
- Créer une nouvelle page CreatePoll
- Créer un conteneur principal :
- Width : 100%
- Height : 100vh
- Background : couleur bleue
Structure du contenu
- Ajouter un bloc de contenu avec :
- Un custom text pour le titre
- Un bloc bleu contenant :
- 1 text input pour la question
- 4 text input pour les choix de réponses
- 1 button pour valider
3. Base de données - Table Polls
Créer la table
- Dans le dossier tables, créer une table Polls
- Définir les colonnes :
- question (string)
- answer1 (string)
- answer2 (string)
- answer3 (string)
- answer4 (string)
- gameId (string)
gameId : identifiant à 5 caractères pour faciliter la saisie, comme dans Kahoot
4. Route de création de sondage
Créer la route createPolls
- Dans services, créer un dossier Poll
- Créer une route createPolls (méthode POST)
- Définir le body avec les mêmes champs que la table
Logique d'insertion
- Créer un node dbinsert
- Sélectionner la table Polls
- Connecter le body au champ data
5. Variables et liaison frontend
Créer les variables de page
- Dans CreatePoll, créer 5 variables :
- question (string)
- answer1 (string)
- answer2 (string)
- answer3 (string)
- answer4 (string)
Lier les inputs
- Pour chaque text input, définir le model correspondant :
- Premier input → question
- Inputs suivants → answer1, answer2, answer3, answer4
6. Logique de création avec UUID
Événement onClick du bouton
- Ajouter un événement onClick sur le bouton de validation
- Appeler la route createPolls
Générer le gameId
- Tirer un fil depuis le body pour créer un create object
- Connecter les variables :
- get question → question
- get answer1 → answer1
- get answer2 → answer2
- get answer3 → answer3
- get answer4 → answer4
- Pour le gameId :
- Aller dans Librairie → cocher Utils
- Utiliser UUID pour générer un identifiant unique
- Ajouter un slice (0, 5) pour ne garder que les 5 premiers caractères
- Connecter au champ gameId
Tester : remplir les champs → valider → vérifier dans la table Polls
7. Page d'accueil (Home)
Créer la page Home
- Créer une nouvelle page Home
- Design similaire à CreatePoll avec :
- Un input pour entrer le gameId
- Un bouton pour rejoindre un sondage
- Un bouton "Create Your Own Poll" pour accéder à CreatePoll
Définir comme page d'accueil
- Changer le path de CreatePoll pour que Home soit la page par défaut
Navigation vers CreatePoll
- Sur le bouton "Create Your Own Poll", ajouter un événement onClick
- Créer un get router
- Ajouter un get property → sélectionner push
- Créer une fonction lambda
- Définir le chemin :
/createpoll - Connecter le tout
Tester : cliquer sur le bouton → redirection vers CreatePoll
8. Récupération d'un sondage par ID
Créer la route getPollsBySlug
- Dans services/Poll, créer une route getPollsBySlug
- Méthode : GET
- Définir le body : gameId (string)
Logique de récupération
- Créer un dbfind sur la table Polls
- Ajouter un filter sur gameId
- Récupérer le gameId depuis le body (get property)
- Ajouter un first après le dbfind pour obtenir un objet au lieu d'un tableau
- Connecter à la réponse
Tester avec Execute : envoyer un gameId → récupérer l'objet du sondage
9. Rejoindre un sondage depuis Home
Variables et liaison
- Dans Home, créer une variable gameId
- Lier le text input au model gameId
Événement de validation
- Sur le bouton "Validate", ajouter un événement onClick
- Appeler getPollsBySlug
- Envoyer le gameId dans le body
Redirection conditionnelle
- Récupérer la réponse
- Caster la réponse en boolean (si réponse existe = true)
- Créer un get router → push
- Créer le chemin avec concatenation :
/poll/+ gameId
- Connecter conditionnellement
Tester : gameId invalide = rien / gameId valide = redirection vers
/poll/{gameId}
10. Page de sondage (Polls)
Créer la page
- Créer une nouvelle page Polls
- Définir le path :
/poll/:id - Design avec 3 zones :
- Zone supérieure : question du sondage
- Zone centrale : résultats (barres de votes)
- Zone inférieure : boutons de réponses
11. Store pour les données du sondage
Créer le store CurrentPoll
- Créer un nouveau store CurrentPoll
- Définir le schéma :
poll (object)
├─ question (string)
├─ answer1 (string)
├─ answer2 (string)
├─ answer3 (string)
├─ answer4 (string)
└─ gameId (string)
Remplir le store depuis Home
- Dans la logique de Home (après getPollsBySlug)
- Créer un get CurrentPoll
- Créer un set property → sélectionner poll
- Connecter la réponse de getPollsBySlug
Avantage : évite un nouvel appel au backend depuis la page Polls
12. Affichage de la question
Propriété dynamique
- Dans le custom text de la question sur la page Polls
- Écrire
{question}pour créer la propriété - Définir question =
CurrentPoll.poll.question
Tester : rejoindre un sondage → la question s'affiche
13. Composant ButtonVote
Créer le composant
- Créer un nouveau composant ButtonVote
- Définir les propriétés :
- value (string) : texte de la réponse
- index (number) : position de la réponse (0-3)
- Définir le contenu du bouton = value
Styliser le bouton
- Ajouter les styles appropriés (padding, background, border-radius, etc.)
14. Affichage des boutons de réponse
Template pour itérer sur les réponses
- Dans la zone des boutons, créer un template
- Logique : for
- Itérer sur :
CurrentPoll.pollen transformant en tableau- Créer un tableau avec
[answer1, answer2, answer3, answer4]
- Créer un tableau avec
Instancier le composant
- Ajouter le composant ButtonVote dans le template
- Définir les propriétés :
- value :
template.value - index :
template.index
- value :
Les 4 boutons s'affichent automatiquement
15. Base de données - Table Votes
Créer la table
- Dans tables, créer une table Votes
- Définir les colonnes :
- gameId (string)
- voteIndex (number) : index de la réponse votée (0-3)
16. Route de création de vote
Créer la route createVote
- Dans services, créer un dossier Vote
- Créer une route createVote (méthode POST)
- Définir le body :
- voteIndex (number)
- gameId (string)
Logique d'insertion et récupération
- Créer un dbinsert sur Votes
- Connecter le body à data
- Après insertion, créer un dbfind sur Votes
- Ajouter un filter sur gameId (récupéré du body avec get property)
- Connecter à la réponse
La réponse renvoie tous les votes du sondage
17. Logique de vote dans ButtonVote
Événement onClick
- Dans le composant ButtonVote, ajouter un événement onClick
- Appeler la route createVote
- Créer le body :
- voteIndex : get index (propriété du composant)
- gameId :
CurrentPoll.poll.gameId
Tester : cliquer sur un bouton → nouveau vote ajouté dans la table Votes
18. Route de calcul des résultats
Créer la route getVotesByGameId
- Dans services/Vote, créer getVotesByGameId (GET)
- Body : gameId (string)
- Réponse attendue :
- votes : tableau de 4 nombres (nombre de votes par réponse)
- totalVotes : nombre total de votes
- percentages : tableau de 4 pourcentages
Récupérer les votes
- Créer un dbfind sur Votes
- Ajouter un filter sur gameId
Installer le package ES-Toolkit
- Aller dans Librairie
- Installer le package ES-Toolkit
Regrouper les votes avec countBy
- Utiliser countBy de ES-Toolkit
- Paramètres :
- Liste : résultat du dbfind
- Fonction de regroupement :
item.voteIndex
- Caster l'item pour indiquer qu'il a un champ voteIndex
- Faire un get property pour extraire l'objet
Résultat : objet de type
{0: 2, 1: 5, 2: 1, 3: 3}(nombre de votes par index)
Transformer en tableau
- Créer un new array de 4 nombres
- Nommer les indices : 0, 1, 2, 3
- Utiliser spread sur l'objet countBy
- Connecter chaque valeur à l'index correspondant
Calculer le total
- Utiliser get length sur le résultat du dbfind
Calculer les pourcentages
- Créer un map sur le tableau de votes
- Pour chaque élément :
- Diviser par totalVotes
- Multiplier par 100
- Connecter à la réponse
Structurer la réponse
- Créer un create object avec :
- votes : tableau de votes
- totalVotes : nombre total
- percentages : tableau de pourcentages
19. Store pour les résultats
Ajouter au store CurrentPoll
votes (array of objects) - 4 éléments
└─ percentage (string)
totalVotes (number)
Initialiser le store
- Cliquer 4 fois sur le "+" pour créer 4 objets vides dans votes
- Reset le store
20. Fonction SetVotes dans le store
Créer la fonction
- Dans le store CurrentPoll, créer une fonction SetVotes
- Cette fonction met à jour les résultats après chaque vote
Logique de la fonction
- Appeler getVotesByGameId
- Créer le body avec le gameId du store (
CurrentPoll.poll.gameId)
Mettre à jour les pourcentages
- Créer un map sur
CurrentPoll.votes - Pour chaque élément :
- Créer un set property pour percentage
- Récupérer la valeur avec spread de la réponse
- Utiliser at avec l'index (cocher "index" dans le map)
- Caster l'index en string
- Concatener avec le symbole
%
Mettre à jour les votes
- Créer un autre set property pour votes
- Même logique : spread → at → index
Mettre à jour totalVotes
- Créer un set property pour totalVotes
- Connecter depuis
response.totalVotes
Finaliser
- Connecter le map à l'output de la fonction
- Cliquer sur Export
Possibilité d'optimisation : décomposer en plusieurs fonctions pour plus de lisibilité
21. Appeler SetVotes après le vote
Modifier ButtonVote
- Dans la logique onClick de ButtonVote
- Après l'appel à createVote
- Appeler la fonction SetVotes du store
Maintenant, chaque vote met à jour automatiquement les résultats
22. Affichage des barres de résultats
Créer le template des barres
- Dans la zone des résultats, créer un template
- Logique : for
- Itérer sur :
CurrentPoll.votes
Styliser le conteneur
- Agrandir le conteneur pour voir les barres
- Définir les styles :
- Display : flex
- Align-items : flex-end (alignement en bas)
- Justify-content : space-around
Créer les barres
- Dans le template, créer un bloc (la barre)
- Définir la hauteur dynamiquement :
- Height :
template.value.percentage(ex: "25%")
- Height :
- Ajouter un custom text centré pour afficher le nombre de votes
- Ajouter une transition pour l'animation
Tester : voter → les barres s'animent en temps réel !
Récapitulatif des concepts clés
| Concept | Utilisation |
|---|---|
| UUID + slice | Générer des identifiants courts (5 caractères) |
| Router push | Navigation programmatique entre pages |
| Path parameters | URL dynamique avec :id |
| Store functions | Encapsuler la logique de mise à jour |
| countBy (ES-Toolkit) | Regrouper et compter des éléments |
| Map | Transformer des tableaux |
| Template (for) | Itérer pour afficher des listes |
| Concatenation | Construire des URLs et strings dynamiques |
| Cast to boolean | Vérifier l'existence d'une réponse |
| Transitions CSS | Animations fluides |
Architecture complète
Frontend
├─ Home (page d'accueil)
│ ├─ Input gameId
│ ├─ Bouton "Join Poll"
│ └─ Bouton "Create Your Own Poll"
├─ CreatePoll (création)
│ ├─ Input question
│ ├─ 4 inputs réponses
│ └─ Bouton validation
└─ Polls (sondage actif)
├─ Question
├─ Barres de résultats
└─ Boutons de vote (composant ButtonVote)
Backend
├─ Tables
│ ├─ Polls (question, answer1-4, gameId)
│ └─ Votes (gameId, voteIndex)
└─ Services
├─ Poll
│ ├─ createPolls
│ └─ getPollsBySlug
└─ Vote
├─ createVote
└─ getVotesByGameId
Store
└─ CurrentPoll
├─ poll (object)
├─ votes (array)
├─ totalVotes (number)
└─ SetVotes() (function)
Conseils pratiques
- gameId à 5 caractères : slice(0, 5) sur UUID pour simplicité
- Store functions : centraliser la logique complexe
- Utiliser ES-Toolkit pour des opérations avancées sur les tableaux
- Path parameters :
:iddans l'URL pour les pages dynamiques - Animations CSS : ajouter des transitions pour un effet visuel professionnel
- Cast to boolean : transformer une réponse en condition (existe = true)
- Map avec index : cocher "index" pour accéder à la position
- Toujours reset le store après modification du schéma
- Concatenation : pour construire des URLs dynamiques
- Penser à exporter les fonctions du store