"use client"; import * as React from "react"; import { AlertTriangle, Save, Wrench } from "lucide-react"; import { Button } from "@/components/ui"; import { api } from "@/lib/api"; import { toast } from "@/lib/store"; import { cn } from "@/lib/utils"; import { useAdmin } from "@/components/admin/store"; import { useAdminQuery } from "@/components/admin/use-query"; import type { MaintenanceSetting, ProfanitySetting, SettingsResponse } from "@/components/admin/types"; import { PageHeader, RefreshButton, Panel, ErrorState, TableSkeleton, Toggle, DenseTextarea, FieldLabel, InlineError, ConfirmDialog, Pill } from "@/components/admin/primitives"; import { describeError, int } from "@/components/admin/format"; const FLAG_LABELS: Record = { "registration.enabled": "New account creation", "dailyRewards.enabled": "Daily reward claims", "leaderboards.enabled": "Public leaderboards", "missions.enabled": "Daily & weekly missions", "achievements.enabled": "Achievements", "rescue.enabled": "Rescue credits at zero balance", }; export default function AdminSettingsPage() { const q = useAdminQuery("/api/admin/settings"); const d = q.data; const [busyFlag, setBusyFlag] = React.useState(null); async function setFlag(key: string, enabled: boolean) { setBusyFlag(key); q.mutate((prev) => ({ ...prev, flags: { ...prev.flags, [key]: enabled } })); try { await api(`/api/admin/flags/${encodeURIComponent(key)}`, { json: { enabled } }); toast({ title: `${FLAG_LABELS[key] ?? key} ${enabled ? "enabled" : "disabled"}`, tone: enabled ? "success" : "default" }); } catch (e) { q.mutate((prev) => ({ ...prev, flags: { ...prev.flags, [key]: !enabled } })); toast({ title: "Flag update failed", description: describeError(e), tone: "danger" }); } finally { setBusyFlag(null); } } const platformFlags = Object.entries(d?.flags ?? {}).filter(([k]) => !k.startsWith("game.")).sort(([a], [b]) => a.localeCompare(b)); const gameFlags = Object.entries(d?.flags ?? {}).filter(([k]) => k.startsWith("game.")).sort(([a], [b]) => a.localeCompare(b)); const gamesOff = gameFlags.filter(([, v]) => !v).length; return ( <> void q.refresh()} loading={q.refreshing} />} /> {q.error && !d ? ( void q.refresh()} /> ) : !d ? (
) : (
    {platformFlags.map(([key, enabled]) => (
  • {FLAG_LABELS[key] ?? key}
    {key}
    void setFlag(key, v)} label={FLAG_LABELS[key] ?? key} />
  • ))} {platformFlags.length === 0 ?
  • No flags defined. Run the database seed.
  • : null}
    {gameFlags.map(([key, enabled]) => (
  • {key.replace(/^game\./, "").replace(/\.enabled$/, "")} void setFlag(key, v)} label={key} />
  • ))}
void q.refresh()} /> typeof w === "string")} onSaved={() => void q.refresh()} />
)} ); } function MaintenanceCard({ initial, onSaved }: { initial: MaintenanceSetting; onSaved: () => void }) { const setMaintenance = useAdmin((s) => s.setMaintenance); const [enabled, setEnabled] = React.useState(!!initial.enabled); const [message, setMessage] = React.useState(initial.message ?? ""); const [confirm, setConfirm] = React.useState(false); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); const dirty = enabled !== !!initial.enabled || message !== (initial.message ?? ""); const valid = message.trim().length >= 3; async function save() { setBusy(true); setError(null); try { const value = { enabled, message: message.trim() }; await api("/api/admin/settings/maintenance", { json: { value } }); setMaintenance(value); toast({ title: enabled ? "Maintenance mode ON" : "Maintenance mode OFF", description: enabled ? "Players now see the maintenance message." : "The platform is live again.", tone: enabled ? "danger" : "success" }); setConfirm(false); onSaved(); } catch (e) { setError(describeError(e)); setConfirm(false); } finally { setBusy(false); } } return ( Maintenance mode {initial.enabled ? "active" : "off"} } description="When enabled every player request returns 503 with this message. Admin routes stay reachable." tone={initial.enabled ? "danger" : undefined} >
Maintenance enabled
Blocks gameplay, sign-in and registration.
Player-facing message setMessage(e.target.value)} />
{error ?
{error}
: null}
setConfirm(false)} onConfirm={() => void save()} title={enabled ? "Enable maintenance mode?" : "Update maintenance settings?"} confirmLabel={enabled ? "Yes, take Spinza offline" : "Save"} danger={enabled} loading={busy} description={ enabled ? ( All player traffic will receive a 503 immediately. Active spins finish, no credits are lost. This is logged as a high-severity event. ) : ( "Players will see the platform again as soon as the API cache refreshes (≤ 10 s)." ) } >
“{message.trim()}”
); } function ProfanityCard({ initial, onSaved }: { initial: string[]; onSaved: () => void }) { const [text, setText] = React.useState(initial.join("\n")); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); const words = React.useMemo(() => Array.from(new Set(text.split(/[\n,]+/).map((w) => w.trim().toLowerCase()).filter(Boolean))), [text]); const dirty = JSON.stringify(words) !== JSON.stringify(initial.map((w) => w.trim().toLowerCase()).filter(Boolean)); async function save() { setBusy(true); setError(null); try { await api("/api/admin/settings/profanity", { json: { value: { words } } }); toast({ title: "Profanity list saved", description: `${int(words.length)} custom words`, tone: "success" }); onSaved(); } catch (e) { setError(describeError(e)); } finally { setBusy(false); } } return ( {int(words.length)} words}> setText(e.target.value)} placeholder={"one word per line"} className="font-mono" spellCheck={false} /> {error ?
{error}
: null}
); }