import { useEffect, useState } from 'react'; import { Link } from 'react-router'; import { api, get } from '../lib/api'; import type { Stats } from '../lib/types'; import { absoluteTime, num, relativeTime } from '../lib/format'; import { PlayerAvatar } from '../components/PlayerAvatar'; import { useToast } from '../components/Toast'; import { Activity, Ban, Building2, Gift, Megaphone, RefreshCw, Users } from '../components/Icons'; type ActionState = { playerId: number; username: string; kind: 'kick' | 'ban' } | null; export function Dashboard() { const [stats, setStats] = useState(null); const [err, setErr] = useState(null); const [loading, setLoading] = useState(false); const [action, setAction] = useState(null); const toast = useToast(); const load = async () => { setLoading(true); setErr(null); try { setStats(await get('/stats')); } catch (e) { setErr((e as Error).message); } finally { setLoading(false); } }; useEffect(() => { load(); const id = setInterval(load, 10_000); return () => clearInterval(id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const runAction = async (reason: string) => { if (!action) return; try { if (action.kind === 'kick') { await api(`/players/${action.playerId}/kick`, { method: 'POST', body: { Reason: reason } }); toast.push(`Kicked @${action.username}`, 'success'); } else { await api(`/players/${action.playerId}/ban`, { method: 'POST', body: { DurationDays: 1, Reason: reason || 'Quick ban from live dashboard' } }); toast.push(`Banned @${action.username} for 1 day`, 'success'); } setAction(null); await load(); } catch (e) { toast.push((e as Error).message, 'error'); } }; const onlinePlayers = stats?.players.online ?? []; return (

Live Ops

Online players, active rooms, and server health.

Broadcast
{err &&
{err}
}
} label="Online" value={stats?.players.onlineNow} sub={`${stats?.players.total ?? 0} accounts`} tone="success" /> } label="In game" value={stats?.rooms.inGamePlayerCount} sub={`${stats?.rooms.activeSessionCount ?? 0} sessions`} tone="brand" /> } label="Rooms" value={stats?.rooms.total} sub={`${num(stats?.rooms.totalVisits ?? 0)} visits`} tone="neutral" /> } label="Inventions" value={stats?.inventions} sub={`${num(stats?.rooms.totalCheers ?? 0)} room cheers`} tone="neutral" /> } label="New today" value={stats?.players.newToday} sub={`${stats?.photos.today ?? 0} photos`} tone="brand" /> } label="Moderation" value={stats?.moderation.openReports} sub={`${stats?.players.bannedNow ?? 0} banned`} tone="warn" />

Online players

{onlinePlayers.length} connected right now

All players
{onlinePlayers.map(p => ( ))} {stats && onlinePlayers.length === 0 && ( )} {!stats && !err && ( )}
Player Room Instance Level Seen Actions
{p.displayName || p.username}
@{p.username} · #{p.id}
{p.isAdmin && Admin} {p.isJunior && Junior} {p.isVerified && Verified}
{p.currentRoom ? ( {p.currentRoom.name} ) : ( Unknown )} {p.currentRoom?.isPrivate && Private} {p.currentRoom ? ( #{p.currentRoom.roomInstanceId} · {p.currentRoom.photonRegionId} ) : '—'} {p.level} {relativeTime(p.lastSeenAt)}
Open Gift {!p.isAdmin && }
No online players.
Loading live players...
{stats?.rooms.activeSessions.map(s => (
{s.photonRoomName || `session-${s.id}`}
{s.playerCount}/{s.maxCapacity}
room {s.roomId || 'unknown'} · {s.region} · {relativeTime(s.createdAt)}
))} {stats && stats.rooms.activeSessions.length === 0 &&
No active sessions.
}
{stats?.rooms.topByVisits.slice(0, 6).map((r, idx) => (
{idx + 1} {r.name} {num(r.visitCount)}
))}

Server time: {stats ? new Date(stats.serverTime).toLocaleString() : '-'}

{action && ( setAction(null)} onConfirm={runAction} /> )}
); } function Stat({ label, value, sub, icon, tone }: { label: string; value: number | undefined; sub?: string; icon: React.ReactNode; tone: 'success' | 'warn' | 'brand' | 'neutral' }) { const toneClass = { success: 'text-success bg-success/10 border-success/25', warn: 'text-warn bg-warn/10 border-warn/25', brand: 'text-brand-200 bg-brand-500/10 border-brand-500/25', neutral: 'text-ink-200 bg-ink-800/60 border-ink-700', }[tone]; return (
{icon}
{label}
{value?.toLocaleString() ?? '-'}
{sub &&
{sub}
}
); } function Panel({ title, subtitle, children }: { title: string; subtitle: string; children: React.ReactNode }) { return (

{title}

{subtitle}

{children}
); } function QuickAction({ action, onCancel, onConfirm }: { action: Exclude; onCancel: () => void; onConfirm: (reason: string) => void }) { const [reason, setReason] = useState(action.kind === 'kick' ? 'Quick kick from live dashboard' : 'Quick ban from live dashboard'); return (

{action.kind === 'kick' ? 'Kick player' : 'Ban player for 1 day'}

@{action.username}