"use client"; import { useState } from "react"; import { Users, UserX, Wifi, Crown, Gavel, HeartPulse, MapPin, Gift } from "lucide-react"; import { api, useApi, useSSE, toast } from "@/lib/api"; import { Card, Badge, Button, EmptyState, Modal, Input, Select } from "@/components/ui"; import { timeAgo, fmtDateTime } from "@/lib/format"; type PlayerRow = { name: string; uuid: string; playTimeHours: number; deaths: number; mobKills: number; playerKills: number; distanceKm: number; jumps: number; damageDealt: number; blocksMined: number; isOnline: boolean; ping: number | null; authStatus: string | null; lastSeenAt: number | null; }; type PlayersData = { online: string[]; max: number; players: PlayerRow[] }; type ActivityRow = { id: number; ts: number; type: string; player: string | null; detail: string | null }; export default function PlayersPage() { const { data, refresh } = useApi("/api/players", 15000); const { data: joins, refresh: refreshJoins } = useApi("/api/activity?limit=30", 20000); const [detail, setDetail] = useState(null); const [kicking, setKicking] = useState(null); const [adminBusy, setAdminBusy] = useState(null); const [tpTarget, setTpTarget] = useState(""); const [giveItemId, setGiveItemId] = useState(""); const [giveCount, setGiveCount] = useState("1"); const adminAction = async (label: string, body: Record, message: string) => { setAdminBusy(label); try { await api("/api/admin", { body }); toast(message, "success"); } catch (err) { toast((err as Error).message, "error"); } finally { setAdminBusy(null); } }; const banPlayer = async (name: string) => { setAdminBusy("ban"); try { await api("/api/security", { body: { op: "ban-player", name, reason: "Banned by the admin" } }); toast(`${name} banned`, "warn"); setDetail(null); void refresh(); } catch (err) { toast((err as Error).message, "error"); } finally { setAdminBusy(null); } }; useSSE("/api/stream/events", { "mc-event": () => { void refresh(); void refreshJoins(); }, }); const kick = async (name: string) => { setKicking(name); try { await api("/api/console", { body: { command: `kick ${name} Kicked from the panel` } }); toast(`${name} kicked`, "warn"); setTimeout(() => void refresh(), 1000); } catch (err) { toast((err as Error).message, "error"); } finally { setKicking(null); } }; const players = data?.players ?? []; const online = players.filter((p) => p.isOnline); const onlineNames = data?.online ?? []; const joinEvents = (joins ?? []).filter((j) => ["join", "leave", "connect"].includes(j.type)); const pingBadge = (ping: number | null) => { if (ping === null) return null; const color = ping > 250 ? "crit" : ping > 120 ? "warn" : "good"; return ( {ping} ms ); }; return (

Players

{online.length} online{data?.max ? ` of ${data.max} max` : ""} · {players.length} have played

{players.length === 0 ? ( } title="No player data yet" hint="Stats appear after someone plays on the server." /> ) : ( {players.map((p) => ( setDetail(p)} > ))}
Player Playtime Deaths Last seen
{p.name} {p.isOnline && pingBadge(p.ping)}
{p.playTimeHours} h {p.deaths} {p.isOnline ? online now : timeAgo(p.lastSeenAt)} {p.isOnline && ( )}
)}
{/* join/leave feed */} {joinEvents.length === 0 ? ( ) : (
    {joinEvents.map((e) => (
  • {e.type === "connect" ? "connecting" : e.type} {e.player} {e.type === "connect" && e.detail && ( {e.detail} )} {timeAgo(e.ts)}
  • ))}
)}
{/* stats drawer */} setDetail(null)} title={detail?.name ?? ""} wide> {detail && (
{detail.isOnline ? online : offline} {detail.authStatus && {detail.authStatus}} {detail.isOnline && detail.ping !== null && pingBadge(detail.ping)}
{[ ["Playtime", `${detail.playTimeHours} h`], ["Deaths", detail.deaths], ["Mob kills", detail.mobKills], ["Player kills", detail.playerKills], ["Traveled", `${detail.distanceKm} km`], ["Blocks mined", detail.blocksMined.toLocaleString()], ["Jumps", detail.jumps.toLocaleString()], ["Damage dealt", detail.damageDealt.toLocaleString()], ].map(([label, value]) => (
{label}
{value}
))}
UUID: {detail.uuid}
Last seen: {detail.isOnline ? "online now" : fmtDateTime(detail.lastSeenAt)}
{/* admin actions */}
{detail.isOnline && ( <> {(["survival", "creative", "spectator"] as const).map((mode) => ( ))} )}
{/* creative admin favors (online players only) */} {detail.isOnline && (
{onlineNames.filter((n) => n !== detail.name).length > 0 && ( <> setGiveItemId(e.target.value)} placeholder="item id, e.g. diamond" className="w-44 font-mono text-xs" /> setGiveCount(e.target.value)} className="w-20 font-mono text-xs" />
)}
)}
); }