Ajoute les suggestions de questions des Citoyens et les emails sur Admin
Build and deploy / deploy (push) Successful in 35s

Chaque Citoyen peut proposer une question pour Le Char depuis /profile
(upsert via submit_chariot_question, figée dès la date du Tribunal
comme les scores d'Icare). Les Archontes modèrent ces suggestions
directement sur /char/questions (ajouter à la banque ou rejeter).

/admin affiche maintenant l'email de chaque membre via une nouvelle
RPC admin_list_members(), seule façon d'exposer auth.users.email sans
passer par la service_role key côté client.

Corrige au passage deux bugs découverts pendant les tests : la
policy interne de admin_list_members() référençait id/role sans les
qualifier, ambigus avec les colonnes du RETURNS TABLE (erreur
Postgres 42702) ; et l'auteur d'une suggestion s'affichait toujours
comme "un Citoyen" car le embed profiles(...) avait été mal retypé en
tableau alors qu'il est retourné en objet à l'exécution.
This commit is contained in:
Valentin ROBIN
2026-08-19 01:22:36 +02:00
parent 0c4814f4d6
commit 76fa8cd451
8 changed files with 411 additions and 56 deletions
+12 -8
View File
@@ -12,6 +12,7 @@ type Member = {
role: string;
pseudo_locked: boolean;
avatar_url: string | null;
email: string;
};
function MemberRow({ member, isSelf }: { member: Member; isSelf: boolean }) {
@@ -78,14 +79,17 @@ function MemberRow({ member, isSelf }: { member: Member; isSelf: boolean }) {
return (
<li className="marble-surface flex flex-col gap-2 rounded-lg border border-gold/25 px-4 py-3 shadow-sm sm:flex-row sm:items-center sm:gap-4">
<div className="flex items-center gap-2">
<Avatar pseudo={member.pseudo} avatarUrl={member.avatar_url} size="sm" />
<input
type="text"
value={pseudo}
onChange={(event) => setPseudo(event.target.value)}
className="w-full flex-1 rounded-md border border-gold/30 bg-white/50 px-2 py-1 text-sm text-text-marble outline-none focus:border-gold sm:w-auto"
/>
<div className="flex flex-1 flex-col gap-1">
<div className="flex items-center gap-2">
<Avatar pseudo={member.pseudo} avatarUrl={member.avatar_url} size="sm" />
<input
type="text"
value={pseudo}
onChange={(event) => setPseudo(event.target.value)}
className="w-full flex-1 rounded-md border border-gold/30 bg-white/50 px-2 py-1 text-sm text-text-marble outline-none focus:border-gold sm:w-auto"
/>
</div>
<p className="truncate pl-10 text-xs text-text-mut">{member.email}</p>
</div>
<div className="flex flex-wrap items-center gap-2">
+1 -4
View File
@@ -25,10 +25,7 @@ export default async function AdminPage() {
redirect("/leaderboard");
}
const { data: members } = await supabase
.from("profiles")
.select("id, pseudo, role, pseudo_locked, avatar_url")
.order("pseudo", { ascending: true });
const { data: members } = await supabase.rpc("admin_list_members");
return (
<div className="mx-auto w-full max-w-3xl px-4 py-8">