abbd079cd2
V2 — authentification et administration :
- Authentification par email + mot de passe (email confirmé, un compte
par email), abandon de l'email interne dérivé du pseudo.
- Rôles public/judge sur profiles, section Administration (juges
uniquement, vérifiée côté serveur) pour gérer les membres.
- Verrou de pseudo : modifiable une fois par son propriétaire puis figé,
contournable par un juge.
- RLS étendue par un trigger BEFORE UPDATE (enforce_profile_update) pour
verrouiller les colonnes sensibles (role, points, pseudo_locked, pseudo
figé) — la RLS seule ne peut pas exprimer une règle par colonne.
V3 — points, journal, podium, progression, photo :
- RPC award_points() (SECURITY DEFINER) : seul point d'écriture de la
colonne points, vérifie le rôle juge côté serveur, delta signé sans
plancher à 0, trace chaque opération dans points_log.
- Leaderboard temps réel (Supabase Realtime) avec podium top 3
(égalités gérées), flèches de progression (previous_rank), et
contrôles de points juges avec confirmation explicite (plus de
debounce auto) avant envoi.
- Page /journal ("le crieur") : fil live et public des attributions de
points.
- Recadrage photo carré + compression client (react-easy-crop + canvas)
au signup et sur le profil.
- Passe de polish visuel : cartes/boutons cohérents, lien actif dans la
nav, podium retravaillé.
schema.sql, README.md et CLAUDE.md mis à jour en conséquence (schéma
idempotent, instructions SMTP/rôles/migration, conventions RLS+trigger
documentées pour les futures colonnes sensibles).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
205 lines
10 KiB
Markdown
205 lines
10 KiB
Markdown
# Le Tribunal
|
||
|
||
Application web pour animer et scorer le jeu **« Le Tribunal »** entre amis pendant une semaine de vacances : comptes, rôles, leaderboard en direct, attribution de points par les juges et journal public des événements. Le socle est **ré-thématisable par édition** (un thème différent chaque année).
|
||
|
||
---
|
||
|
||
## Fonctionnalités
|
||
|
||
- 🔐 **Création de compte** par email + mot de passe (email confirmé, un compte par email)
|
||
- 👤 **Connexion / déconnexion**, pseudo affiché partout (jamais l'email)
|
||
- 🖼️ **Profil éditable** : pseudo modifiable **une seule fois** puis figé (sauf par un juge), photo recadrée en rond, redimensionnée et compressée côté client
|
||
- 🏆 **Leaderboard en direct** (Supabase Realtime) : podium top 3 (or/argent/bronze, gestion des égalités), flèches ▲▼ de progression depuis le dernier « round »
|
||
- ⚖️ **Rôles** : `public` / `judge`. Les juges attribuent des points (+/- avec montant personnalisé et motif, confirmation explicite avant envoi) et gèrent les membres depuis une section **Administration**
|
||
- 📣 **Le crieur** : journal public et en direct de toutes les attributions de points
|
||
- 📱 **Responsive**, pensé mobile-first et propre en vidéoprojection
|
||
|
||
---
|
||
|
||
## Stack technique
|
||
|
||
| Rôle | Techno |
|
||
|--------------------------|---------------------------------|
|
||
| Framework | Next.js 16 (App Router) + TypeScript |
|
||
| Style | Tailwind CSS v4 |
|
||
| Auth · Base · Stockage · Temps réel | Supabase (Auth, Postgres, Storage, Realtime) |
|
||
| Recadrage photo | react-easy-crop + canvas (resize/compression client) |
|
||
| Email transactionnel | SMTP custom (ex. Resend) branché sur Supabase Auth |
|
||
| Déploiement | Vercel + Supabase (managé) |
|
||
|
||
---
|
||
|
||
## Prérequis
|
||
|
||
- [Node.js](https://nodejs.org/) 20.19+ (Next.js 16 / React 19)
|
||
- Un compte [Supabase](https://supabase.com/) (offre gratuite suffisante)
|
||
- Un fournisseur SMTP gratuit (ex. [Resend](https://resend.com/), 100 emails/jour) — le service email intégré de Supabase a un rate limit trop bas pour un usage réel
|
||
- Un compte [Vercel](https://vercel.com/) pour le déploiement (optionnel en local)
|
||
|
||
---
|
||
|
||
## Installation
|
||
|
||
### 1. Cloner et installer
|
||
|
||
```bash
|
||
git clone ssh://git@git.alxczl.fr:7878/valentin/tribunal-app.git
|
||
cd tribunal-app
|
||
npm install
|
||
```
|
||
|
||
### 2. Créer le projet Supabase
|
||
|
||
1. Créez un nouveau projet sur [supabase.com](https://supabase.com/).
|
||
2. Dans **SQL Editor**, exécutez tout le contenu de [`supabase/schema.sql`](supabase/schema.sql) (tables `profiles`/`points_log`, policies RLS, triggers, RPC, bucket `avatars`, Realtime). Le script est idempotent, vous pouvez le ré-exécuter sans risque après une mise à jour.
|
||
3. Récupérez l'URL du projet et la clé `anon` dans **Project Settings → API**.
|
||
4. Dans **Authentication → Providers → Email**, **activez "Confirm email"** (un vrai email par personne, condition d'unicité des comptes).
|
||
5. Dans **Authentication → Settings → SMTP Settings**, branchez un fournisseur SMTP custom (ex. Resend) — le service email par défaut de Supabase est bridé à quelques emails/heure. Voir [Email transactionnel](#email-transactionnel) ci-dessous.
|
||
6. Une fois votre propre compte créé et confirmé via `/signup`, désignez-vous juge (voir [Rôles & administration](#rôles--administration)).
|
||
|
||
### 3. Variables d'environnement
|
||
|
||
```bash
|
||
cp .env.local.example .env.local
|
||
```
|
||
|
||
```env
|
||
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxx.supabase.co
|
||
NEXT_PUBLIC_SUPABASE_ANON_KEY=votre_cle_anon
|
||
```
|
||
|
||
> ⚠️ Ne mettez **jamais** la clé `service_role` dans le front ni dans le dépôt.
|
||
|
||
### 4. Lancer en local
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
L'application tourne sur [http://localhost:3000](http://localhost:3000).
|
||
|
||
---
|
||
|
||
## Scripts
|
||
|
||
| Commande | Description |
|
||
|------------------|------------------------------------|
|
||
| `npm run dev` | Serveur de développement |
|
||
| `npm run build` | Build de production |
|
||
| `npm run start` | Lance le build de production |
|
||
| `npm run lint` | Vérification du code (ESLint) |
|
||
|
||
---
|
||
|
||
## Déploiement (Vercel)
|
||
|
||
1. Importez le dépôt dans Vercel.
|
||
2. Renseignez les variables d'environnement (`NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`).
|
||
3. Déployez. Vercel rebuild automatiquement à chaque push sur la branche principale.
|
||
|
||
---
|
||
|
||
## Structure du projet
|
||
|
||
```
|
||
tribunal-app/
|
||
├─ src/
|
||
│ ├─ app/
|
||
│ │ ├─ login/page.tsx # Connexion (email + mot de passe)
|
||
│ │ ├─ signup/page.tsx # Création de compte + photo recadrée
|
||
│ │ ├─ leaderboard/
|
||
│ │ │ ├─ page.tsx # Fetch initial + rôle de l'utilisateur
|
||
│ │ │ └─ leaderboard-view.tsx # Realtime, podium, liste, contrôles juges
|
||
│ │ ├─ journal/ # « Le crieur » : fil d'événements en direct
|
||
│ │ ├─ admin/ # Gestion des membres + reset du repère (juges)
|
||
│ │ ├─ profile/ # Édition pseudo (verrou) / photo, déconnexion
|
||
│ │ ├─ layout.tsx # Layout + navigation
|
||
│ │ └─ page.tsx # Redirection selon l'état de connexion
|
||
│ ├─ components/ # Avatar, AvatarPicker (recadrage), NavBar, Podium,
|
||
│ │ │ # JudgePointControls
|
||
│ ├─ lib/
|
||
│ │ ├─ ranking.ts # Classement avec égalités + calcul des flèches
|
||
│ │ ├─ image.ts # Recadrage/compression canvas côté client
|
||
│ │ └─ supabase/ # Clients Supabase (browser / server / proxy)
|
||
│ └─ proxy.ts # Protection des routes + rafraîchissement session
|
||
├─ supabase/schema.sql # Schéma complet + policies RLS + triggers + RPC (versionné)
|
||
├─ .env.local.example
|
||
└─ README.md
|
||
```
|
||
|
||
---
|
||
|
||
## Authentification
|
||
|
||
La connexion se fait par **email + mot de passe** ; Supabase impose l'unicité de l'email, ce qui empêche la création de comptes multiples. Le pseudo est purement un nom d'affichage (leaderboard, profil, journal) : l'email n'est jamais montré aux autres membres. La confirmation d'email est obligatoire (voir [SMTP](#email-transactionnel)).
|
||
|
||
Le profil (`public.profiles`) est créé automatiquement à l'inscription via un trigger sur `auth.users` (fonctionne même si l'email n'est pas encore confirmé).
|
||
|
||
### Verrou de pseudo
|
||
|
||
Chaque membre peut changer son pseudo **une seule fois** depuis `/profile`. Dès l'enregistrement, `pseudo_locked` passe à `true` et le champ devient en lecture seule (« pseudo figé — contacte un juge pour le changer »). Un juge peut toujours modifier le pseudo de quiconque et, s'il le souhaite, redéverrouiller un compte depuis **Administration**.
|
||
|
||
### Rôles & administration
|
||
|
||
Deux rôles : `public` (défaut) et `judge`. Les 2 premiers juges sont désignés manuellement via le SQL Editor :
|
||
|
||
```sql
|
||
select id, pseudo from public.profiles;
|
||
update public.profiles set role = 'judge' where id = '<uuid-du-membre>';
|
||
```
|
||
|
||
Ensuite, un juge peut promouvoir/rétrograder d'autres membres et éditer leur pseudo depuis la page **Administration** (visible uniquement par les juges, et vérifié côté serveur — pas seulement caché en front).
|
||
|
||
### Email transactionnel
|
||
|
||
Le service email intégré de Supabase (sans SMTP custom) limite l'envoi à quelques emails par heure — largement insuffisant dès qu'on teste à plusieurs. Configurez un SMTP custom dans **Authentication → Settings → SMTP Settings**, par exemple avec [Resend](https://resend.com/) (gratuit, 100 emails/jour) :
|
||
|
||
| Champ | Valeur |
|
||
|---|---|
|
||
| Sender email | `onboarding@resend.dev` (domaine de test Resend) ou une adresse d'un domaine que vous avez vérifié |
|
||
| Host | `smtp.resend.com` |
|
||
| Port | `465` |
|
||
| Username | `resend` (en minuscules) |
|
||
| Password | votre clé API Resend |
|
||
|
||
> Avec le domaine de test `onboarding@resend.dev`, Resend ne livre qu'à l'adresse email du compte Resend lui-même. Pour envoyer à tous vos amis, vérifiez votre propre domaine dans Resend.
|
||
|
||
---
|
||
|
||
## Points, journal et classement
|
||
|
||
- **Attribution de points** : depuis le leaderboard, un juge clique `+`/`−` (montant personnalisable, motif optionnel) — le delta reste **en attente** (affiché en couleur) jusqu'à un clic sur **Confirmer** (envoie la RPC `award_points`) ou **Annuler**. Le client n'écrit jamais directement la colonne `points` : la RPC vérifie le rôle juge côté serveur, applique le delta (négatif autorisé, pas de plancher à 0) et journalise l'opération.
|
||
- **Le crieur** (`/journal`) : fil de tous les événements (« Untel a donné +5 à Untel — motif »), visible par tous les authentifiés, alimenté par Supabase Realtime.
|
||
- **Podium** : top 3 mis en avant (or/argent/bronze), gère proprement les égalités (rangs partagés) et les groupes de moins de 3 membres.
|
||
- **Flèches de progression** : ▲/▼/= comparent le rang actuel au dernier repère. Un juge fige un nouveau repère via **Nouveau round** dans **Administration**.
|
||
|
||
---
|
||
|
||
## Sécurité
|
||
|
||
- Mots de passe hachés et gérés par Supabase Auth (jamais stockés en clair).
|
||
- **Row Level Security** activée sur `profiles` et `points_log`. La RLS étant au niveau ligne, les colonnes sensibles (`role`, `points`, `pseudo_locked`, pseudo figé) sont verrouillées par un **trigger `BEFORE UPDATE`** qui ne fait jamais confiance à ce qu'envoie le client.
|
||
- Les points ne sont modifiables que via la RPC `award_points` (`SECURITY DEFINER`), jamais par une écriture directe — même un juge ne peut pas modifier la colonne `points` à la main.
|
||
- `points_log` est en lecture seule pour les clients ; seule la RPC peut y écrire.
|
||
- La page `/admin` vérifie le rôle côté serveur (Server Component) avant de rendre quoi que ce soit — pas de simple masquage front.
|
||
|
||
---
|
||
|
||
## Ré-thématisation
|
||
|
||
Les couleurs sont définies comme **design tokens** (variables CSS), afin de pouvoir changer l'habillage d'une édition à l'autre sans toucher aux composants. Palette par défaut : navy `#0F2748`, or `#C9A227`, ivoire `#F4ECD8`, encre `#2A2116`.
|
||
|
||
---
|
||
|
||
## Roadmap
|
||
|
||
- [ ] **Éditions & thèmes** — année, nom du thème, mood board, vocabulaire configurable
|
||
- [ ] **Le jeu du Tribunal** — le Char, batailles de cul sec, ostracisme, roulette, timer du Gardien
|
||
- [ ] **Mode grand écran** — vue leaderboard optimisée vidéoprojecteur
|
||
|
||
---
|
||
|
||
## Licence
|
||
|
||
Projet personnel et privé, destiné à un usage entre amis.
|