import React, { useEffect, useMemo, useState } from "react"; import { createRoot } from "react-dom/client"; import "./admin.css"; type AdminSnapshot = { isPaused: boolean; isRunningRound: boolean; done: boolean; completedInMemory: number; persistedRounds: number; viewerCount: number; }; type AdminResponse = { ok: true } & AdminSnapshot; type Mode = "checking" | "locked" | "ready"; const RESET_TOKEN = "RESET"; async function readErrorMessage(res: Response): Promise { const text = await res.text(); if (text) return text; return `Request failed (${res.status})`; } async function requestAdminJson( path: string, init?: RequestInit, ): Promise { const headers = new Headers(init?.headers); if (!headers.has("Content-Type")) { headers.set("Content-Type", "application/json"); } const response = await fetch(path, { ...init, headers, cache: "no-store", }); if (!response.ok) { throw new Error(await readErrorMessage(response)); } return (await response.json()) as AdminResponse; } function StatusCard({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function App() { const [mode, setMode] = useState("checking"); const [snapshot, setSnapshot] = useState(null); const [passcode, setPasscode] = useState(""); const [error, setError] = useState(null); const [pending, setPending] = useState(null); const [isResetOpen, setIsResetOpen] = useState(false); const [resetText, setResetText] = useState(""); useEffect(() => { let mounted = true; requestAdminJson("/api/admin/status") .then((data) => { if (!mounted) return; setSnapshot(data); setMode("ready"); }) .catch(() => { if (!mounted) return; setSnapshot(null); setMode("locked"); }); return () => { mounted = false; }; }, []); const busy = useMemo(() => pending !== null, [pending]); async function onLogin(event: React.FormEvent) { event.preventDefault(); setError(null); setPending("login"); try { const data = await requestAdminJson("/api/admin/login", { method: "POST", body: JSON.stringify({ passcode }), }); setSnapshot(data); setPasscode(""); setMode("ready"); } catch (err) { setError(err instanceof Error ? err.message : "Failed to log in"); } finally { setPending(null); } } async function runControl(path: string, task: string) { setError(null); setPending(task); try { const data = await requestAdminJson(path, { method: "POST" }); setSnapshot(data); } catch (err) { const message = err instanceof Error ? err.message : "Admin action failed"; if (message.toLowerCase().includes("unauthorized")) { setMode("locked"); setSnapshot(null); } setError(message); } finally { setPending(null); } } async function onExport() { setError(null); setPending("export"); try { const response = await fetch("/api/admin/export", { cache: "no-store" }); if (!response.ok) { throw new Error(await readErrorMessage(response)); } const blob = await response.blob(); const disposition = response.headers.get("content-disposition") ?? ""; const fileNameMatch = disposition.match(/filename="([^"]+)"/i); const fileName = fileNameMatch?.[1] ?? `argumentes-export-${Date.now()}.json`; const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = fileName; document.body.append(anchor); anchor.click(); anchor.remove(); URL.revokeObjectURL(url); } catch (err) { const message = err instanceof Error ? err.message : "Export failed"; if (message.toLowerCase().includes("unauthorized")) { setMode("locked"); setSnapshot(null); } setError(message); } finally { setPending(null); } } async function onReset() { setError(null); setPending("reset"); try { const data = await requestAdminJson("/api/admin/reset", { method: "POST", body: JSON.stringify({ confirm: RESET_TOKEN }), }); setSnapshot(data); setResetText(""); setIsResetOpen(false); } catch (err) { setError(err instanceof Error ? err.message : "Reset failed"); } finally { setPending(null); } } async function onLogout() { setError(null); setPending("logout"); try { await fetch("/api/admin/logout", { method: "POST", cache: "no-store", }); setSnapshot(null); setPasscode(""); setMode("locked"); } finally { setPending(null); } } if (mode === "checking") { return (
Comprobando sesión de administrador...
); } if (mode === "locked") { return (
argument.es

Acceso de administrador

Introduce tu contraseña una vez. Una cookie segura mantendrá esta sesión activa en el navegador.

setPasscode(e.target.value)} className="text-input" autoFocus autoComplete="off" required data-1p-ignore data-lpignore="true" />
{error &&
{error}
}
); } return (
argument.es

Consola de administrador

Pausa/reanuda el bucle del juego, exporta todos los datos en JSON o borra todos los datos almacenados.

{error &&
{error}
}
{isResetOpen && (

¿Borrar todos los datos?

Esto elimina permanentemente todas las rondas guardadas y reinicia las puntuaciones. El juego también se pausará.

Escribe {RESET_TOKEN} para continuar.

setResetText(e.target.value)} className="text-input" placeholder={RESET_TOKEN} autoFocus />
)}
); } const root = createRoot(document.getElementById("root")!); root.render();