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>
37 lines
1.1 KiB
TypeScript
37 lines
1.1 KiB
TypeScript
import { redirect } from "next/navigation";
|
|
import { createClient } from "@/lib/supabase/server";
|
|
import { ProfileForm } from "./profile-form";
|
|
|
|
export default async function ProfilePage() {
|
|
const supabase = await createClient();
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser();
|
|
|
|
if (!user) {
|
|
redirect("/login");
|
|
}
|
|
|
|
const { data: profile } = await supabase
|
|
.from("profiles")
|
|
.select("pseudo, avatar_url, pseudo_locked, role")
|
|
.eq("id", user.id)
|
|
.single();
|
|
|
|
return (
|
|
<div className="mx-auto w-full max-w-sm px-4 py-8">
|
|
<h1 className="mb-6 text-2xl font-semibold text-navy">Mon profil</h1>
|
|
<div className="rounded-xl border border-navy/10 bg-white p-6 shadow-sm sm:p-8">
|
|
<p className="mb-4 text-sm text-ink/60">{user.email}</p>
|
|
<ProfileForm
|
|
userId={user.id}
|
|
isJudge={profile?.role === "judge"}
|
|
initialPseudo={profile?.pseudo ?? ""}
|
|
initialAvatarUrl={profile?.avatar_url ?? null}
|
|
initialPseudoLocked={profile?.pseudo_locked ?? false}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|