"use client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Separator } from "@/components/ui/separator"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { PlayerAvatar } from "@/components/PlayerAvatar"; type PlayerData = { ops: { name: string; uuid: string; level: number }[]; whitelist: { name: string; uuid: string }[]; banned: { name: string; uuid: string; reason: string }[]; }; type Tab = "ops" | "whitelist" | "banned"; export function PlayerManager() { const queryClient = useQueryClient(); const [tab, setTab] = useState("ops"); const [playerName, setPlayerName] = useState(""); const [banReason, setBanReason] = useState(""); const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null); const { data = { ops: [], whitelist: [], banned: [] } } = useQuery({ queryKey: ["players"], queryFn: async () => { const res = await fetch("/api/players"); if (!res.ok) throw new Error("Failed to fetch players"); return res.json(); }, staleTime: 10_000, refetchInterval: 15_000, }); const action = useMutation({ mutationFn: async (params: { action: string; player: string; reason?: string }) => { const res = await fetch("/api/players", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params), }); const data = await res.json(); if (!res.ok) throw new Error(data.error); return data; }, onSuccess: (data) => { setResult({ ok: true, message: data.response || "Done" }); setPlayerName(""); setBanReason(""); queryClient.invalidateQueries({ queryKey: ["players"] }); }, onError: (err) => { setResult({ ok: false, message: err.message }); }, }); const tabs: { key: Tab; label: string; count: number }[] = [ { key: "ops", label: "Operators", count: data.ops.length }, { key: "whitelist", label: "Whitelist", count: data.whitelist.length }, { key: "banned", label: "Banned", count: data.banned.length }, ]; const MC_NAME_RE = /^[A-Za-z0-9_]{3,16}$/; const trimmed = playerName.trim(); const nameValid = MC_NAME_RE.test(trimmed); const nameError = trimmed.length === 0 ? null : !nameValid ? "3–16 chars, letters/numbers/underscores only" : null; const handleAction = (act: string, player?: string) => { const name = player || trimmed; if (!name) return; if (!player && !nameValid) return; action.mutate({ action: act, player: name, reason: banReason || undefined }); }; return ( Player Management Manage operators, whitelist, and bans via RCON {/* Tabs */}
{tabs.map((t) => ( ))}
{/* Add player input */}
setPlayerName(e.target.value)} aria-invalid={!!nameError} maxLength={16} onKeyDown={(e) => { if (e.key === "Enter") { if (tab === "ops") handleAction("op"); else if (tab === "whitelist") handleAction("whitelist add"); else if (tab === "banned") handleAction("ban"); } }} className="w-full" /> {nameError && (

{nameError}

)}
{tab === "banned" && ( setBanReason(e.target.value)} className="flex-1" /> )}
{/* Feedback */} {result && ( {result.message} )} {/* Player lists */} {tab === "ops" && (
    {data.ops.length === 0 && (
  • No operators
  • )} {data.ops.map((p) => (
  • {p.name} Lv{p.level}
  • ))}
)} {tab === "whitelist" && (
    {data.whitelist.length === 0 && (
  • Whitelist is empty
  • )} {data.whitelist.map((p) => (
  • {p.name}
  • ))}
)} {tab === "banned" && (
    {data.banned.length === 0 && (
  • No banned players
  • )} {data.banned.map((p) => (
  • {p.name}
    {p.reason && (

    {p.reason}

    )}
  • ))}
)}
); }