From 222fb0bdc32450e24b52c9054d744f89d34b6e6c Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Tue, 31 Mar 2026 16:16:10 -0700 Subject: [PATCH] feat: add delete player functionality to admin panel --- src/app/admin/players/page.tsx | 56 ++++++++++++++--- src/app/api/admin/players/[id]/route.ts | 83 +++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 9 deletions(-) diff --git a/src/app/admin/players/page.tsx b/src/app/admin/players/page.tsx index f9e4d1e..17b9880 100644 --- a/src/app/admin/players/page.tsx +++ b/src/app/admin/players/page.tsx @@ -27,6 +27,7 @@ export default function AdminPlayersPage() { const [mergeMode, setMergeMode] = useState(false) const [selectedPlayers, setSelectedPlayers] = useState([]) const [isMerging, setIsMerging] = useState(false) + const [deletingId, setDeletingId] = useState(null) useEffect(() => { fetchPlayers() @@ -122,6 +123,31 @@ export default function AdminPlayersPage() { } } + const handleDelete = async (playerId: number) => { + if (!confirm("Are you sure you want to delete this player? This action cannot be undone and the player must not have any match history.")) { + return + } + + setDeletingId(playerId) + try { + const response = await fetch(`/api/admin/players/${playerId}`, { + method: "DELETE", + }) + + const data = await response.json() + + if (data.success) { + setPlayers(players.filter(p => p.id !== playerId)) + } else { + alert(`Error: ${data.error}`) + } + } catch (err: unknown) { + alert(`Error: ${err instanceof Error ? err.message : "Unknown error occurred"}`) + } finally { + setDeletingId(null) + } + } + const playerCount = players.length return ( @@ -268,15 +294,27 @@ export default function AdminPlayersPage() { View {!mergeMode && ( - + <> + + + )} diff --git a/src/app/api/admin/players/[id]/route.ts b/src/app/api/admin/players/[id]/route.ts index 7c0969d..ba7a071 100644 --- a/src/app/api/admin/players/[id]/route.ts +++ b/src/app/api/admin/players/[id]/route.ts @@ -2,6 +2,89 @@ import { NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import { hasRole } from "@/lib/permissions"; +/** + * DELETE /api/admin/players/[id] + * + * Delete a player + * Requires: club_admin or site_admin role + * Note: Player can only be deleted if they have no matches + */ +export async function DELETE( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params; + const playerId = parseInt(id); + + // Validate player ID + if (isNaN(playerId)) { + return NextResponse.json( + { error: "Invalid player ID" }, + { status: 400 } + ); + } + + // Check permissions - only club_admin and site_admin can delete players + const permission = await hasRole('club_admin'); + if (!permission.allowed) { + return NextResponse.json( + { error: permission.reason || "Not authorized to delete players" }, + { status: 403 } + ); + } + + // Verify player exists + const existingPlayer = await prisma.player.findUnique({ + where: { id: playerId }, + include: { + matchesAsP1: true, + matchesAsP2: true, + matchesAsP3: true, + matchesAsP4: true, + }, + }); + + if (!existingPlayer) { + return NextResponse.json( + { error: "Player not found" }, + { status: 404 } + ); + } + + // Check if player has any matches + const hasMatches = + existingPlayer.matchesAsP1.length > 0 || + existingPlayer.matchesAsP2.length > 0 || + existingPlayer.matchesAsP3.length > 0 || + existingPlayer.matchesAsP4.length > 0; + + if (hasMatches) { + return NextResponse.json( + { error: "Cannot delete player with match history" }, + { status: 400 } + ); + } + + // Delete the player (this will also delete associated ratings via cascade) + await prisma.player.delete({ + where: { id: playerId }, + }); + + return NextResponse.json({ + success: true, + message: "Player deleted successfully", + }); + } catch (error: unknown) { + console.error("Error deleting player:", error); + const message = error instanceof Error ? error.message : "Failed to delete player"; + return NextResponse.json( + { error: message }, + { status: 500 } + ); + } +} + /** * PATCH /api/admin/players/[id] *