Files
tribunal-app/src/app/char/gardien-bar.tsx
T
Valentin ROBIN f30f77a394
Build and deploy / deploy (push) Successful in 36s
Ajoute Le Jeu de l'Agora : Le Char et Le Gardien du Silence
Page /char (16:9, projetée) pour piloter les deux jeux physiques du
Tribunal : Le Char (2 colonnes de 3 emplacements côte à côte avec un
VS et une illustration de char vu du dessus, sélection en un clic,
duels, compteur et classement des passages) et Le Gardien du Silence
(tirage aléatoire parmi les Citoyens, minuteur avec reroll auto et
manuel). Banque de questions gérée séparément sur /char/questions
(pensée pour être pilotée depuis un téléphone pendant que /char est
projetée depuis un ordinateur).
2026-08-11 18:17:45 +02:00

83 lines
2.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { Avatar } from "@/components/avatar";
import { createClient } from "@/lib/supabase/client";
import type { ProfileRow } from "./page";
function computeSecondsLeft(expiresAt: string | null): number {
if (!expiresAt) return 0;
return Math.max(0, Math.round((new Date(expiresAt).getTime() - Date.now()) / 1000));
}
function formatCountdown(seconds: number): string {
const minutes = Math.floor(seconds / 60);
const remainder = seconds % 60;
return `${minutes}:${remainder.toString().padStart(2, "0")}`;
}
// Volontairement discret (une barre compacte, pas une grande carte) : ce
// n'est pas le sujet principal de la page projetée, juste un rappel présent.
export function GardienBar({
isJudge,
holder,
expiresAt,
}: {
isJudge: boolean;
holder: ProfileRow | null;
expiresAt: string | null;
}) {
const [secondsLeft, setSecondsLeft] = useState(() => computeSecondsLeft(expiresAt));
const [rerolling, setRerolling] = useState(false);
useEffect(() => {
const supabase = createClient();
let firedForThisExpiry = false;
function tick() {
const remaining = computeSecondsLeft(expiresAt);
setSecondsLeft(remaining);
if (remaining <= 0 && !firedForThisExpiry) {
firedForThisExpiry = true;
supabase.rpc("reroll_gardien", { p_force: false });
}
}
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [expiresAt]);
async function handleForceReroll() {
setRerolling(true);
const supabase = createClient();
await supabase.rpc("reroll_gardien", { p_force: true });
setRerolling(false);
}
return (
<div className="flex flex-wrap items-center justify-center gap-2 self-center rounded-full border border-gold/30 bg-ink-2/60 px-3 py-1.5 text-xs">
<span className="text-marble/60">Gardien du Silence :</span>
{holder ? (
<span key={holder.id} className="animate-podium-rise flex items-center gap-1.5">
<Avatar pseudo={holder.pseudo} avatarUrl={holder.avatar_url} size="xs" />
<span className="font-medium text-marble">{holder.pseudo}</span>
</span>
) : (
<span className="text-marble/50">personne</span>
)}
{expiresAt && <span className="font-heading text-gold-bright">{formatCountdown(secondsLeft)}</span>}
{isJudge && (
<button
type="button"
onClick={handleForceReroll}
disabled={rerolling}
className="rounded-full border border-gold/30 px-2 py-0.5 text-[0.65rem] text-marble/70 hover:bg-gold/10 disabled:opacity-50"
>
{rerolling ? "…" : "Changer"}
</button>
)}
</div>
);
}