"use client"; import { useState, useEffect, useCallback } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; import { Skeleton } from "@/components/ui/skeleton"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Search, MoreHorizontal, Ban, UserX, Shield, Clock, Users, WifiOff, RefreshCw, } from "lucide-react"; import { toast } from "sonner"; import { formatDistanceToNow } from "date-fns"; interface Player { id: string; uuid: string | null; username: string; firstSeen: number; lastSeen: number; isOnline: boolean; playTime: number; role: string | null; isBanned: boolean; notes: string | null; } function PlayerAvatar({ username }: { username: string }) { return ( {username.slice(0, 2).toUpperCase()} ); } function formatPlayTime(minutes: number): string { const h = Math.floor(minutes / 60); const m = minutes % 60; if (h === 0) return `${m}m`; return `${h}h ${m}m`; } export default function PlayersPage() { const [players, setPlayers] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [filter, setFilter] = useState<"all" | "online" | "banned">("all"); const [selectedPlayer, setSelectedPlayer] = useState(null); const [banDialogOpen, setBanDialogOpen] = useState(false); const [banReason, setBanReason] = useState(""); const [actionLoading, setActionLoading] = useState(false); const fetchPlayers = useCallback(async () => { try { const params = new URLSearchParams({ q: search, limit: "100" }); if (filter === "online") params.set("online", "true"); if (filter === "banned") params.set("banned", "true"); const res = await fetch(`/api/players?${params}`); if (res.ok) { const data = await res.json(); setPlayers(data.players); } } finally { setLoading(false); } }, [search, filter]); useEffect(() => { setLoading(true); fetchPlayers(); }, [fetchPlayers]); const handleBan = async () => { if (!selectedPlayer || !banReason.trim()) return; setActionLoading(true); try { const res = await fetch(`/api/players/${selectedPlayer.id}?action=ban`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ reason: banReason }), }); if (!res.ok) throw new Error((await res.json()).error); toast.success(`${selectedPlayer.username} has been banned`); setBanDialogOpen(false); setBanReason(""); fetchPlayers(); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to ban player"); } finally { setActionLoading(false); } }; const handleUnban = async (player: Player) => { try { const res = await fetch(`/api/players/${player.id}?action=unban`, { method: "POST", }); if (!res.ok) throw new Error((await res.json()).error); toast.success(`${player.username} has been unbanned`); fetchPlayers(); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to unban player"); } }; const handleKick = async (player: Player) => { try { const res = await fetch(`/api/players/${player.id}?action=kick`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ reason: "Kicked by admin" }), }); if (!res.ok) throw new Error((await res.json()).error); toast.success(`${player.username} has been kicked`); fetchPlayers(); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to kick player"); } }; const onlinePlayers = players.filter((p) => p.isOnline).length; return (

Players

Manage players, bans, and permissions

{onlinePlayers} online
{/* Filters */}
setSearch(e.target.value)} className="pl-9 bg-zinc-900 border-zinc-700 text-white placeholder:text-zinc-500 focus:border-emerald-500/50" />
{(["all", "online", "banned"] as const).map((f) => ( ))}
{/* Players table */} {loading ? (
{Array.from({ length: 8 }).map((_, i) => ( ))}
) : players.length === 0 ? (

No players found

) : (
{/* Header */}
Player Status Play Time Last Seen
{players.map((player) => (
{player.username} {player.role && ( {player.role} )} {player.isBanned && ( Banned )}
{player.uuid ?? "UUID unknown"}
{player.isOnline ? "Online" : "Offline"}
{formatPlayTime(player.playTime)} {formatDistanceToNow(new Date(player.lastSeen), { addSuffix: true, })} (window.location.href = `/players/${player.id}`) } > View Profile {player.isOnline && ( handleKick(player)} > Kick )} {player.isBanned ? ( handleUnban(player)} > Unban ) : ( { setSelectedPlayer(player); setBanDialogOpen(true); }} > Ban )}
))}
)}
{/* Ban Dialog */} Ban {selectedPlayer?.username} This will ban the player from the server and record the ban in the history.