import { useEffect, useState } from "react"; import { useAdmin } from "../lib/admin"; import { fetchLeaderboard, type Leaderboard as LeaderboardData } from "../lib/api"; function formatElapsed(ms: number | null) { if (ms === null) { return "—"; } const totalSeconds = Math.floor(ms / 1000); const minutes = Math.floor(totalSeconds / 60) .toString() .padStart(2, "0"); const seconds = (totalSeconds % 60).toString().padStart(2, "0"); const centiseconds = Math.floor((ms % 1000) / 10) .toString() .padStart(2, "0"); return `${minutes}:${seconds}.${centiseconds}`; } type Props = { puzzleId: string; }; export function Leaderboard({ puzzleId }: Props) { const { enabled, token } = useAdmin(); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [includeTest, setIncludeTest] = useState(false); useEffect(() => { if (!enabled || !puzzleId) { setData(null); return; } let cancelled = false; setLoading(true); setError(null); fetchLeaderboard(puzzleId, { includeTest }) .then((result) => { if (!cancelled) { setData(result); } }) .catch((caught) => { if (!cancelled) { setError(caught instanceof Error ? caught.message : "Could not load leaderboard."); } }) .finally(() => { if (!cancelled) { setLoading(false); } }); return () => { cancelled = true; }; }, [enabled, puzzleId, includeTest, token]); if (!enabled) { return null; } const entries = data?.entries ?? []; const solvedCount = entries.filter((entry) => entry.solved).length; return (
Leaderboard · admin
{loading ? "Loading…" : `${entries.length} session${entries.length === 1 ? "" : "s"} · ${solvedCount} solved`}
{error ?
{error}
: null} {!loading && entries.length === 0 ? (
No sessions for this puzzle yet.
) : null} {entries.length > 0 ? (
{entries.map((entry, index) => ( ))}
# Player Time Attempts Status
{entry.solved ? index + 1 : "—"} {entry.player_name} {entry.solved ? formatElapsed(entry.elapsed_ms) : "—"} {entry.submission_count} {entry.solved ? "solved" : "attempted"}
) : null}
); }