"use client" import { useCallback, useEffect, useRef, useState } from "react" import Image from "next/image" import Link from "next/link" import { Loader2, SearchIcon, ShieldCheckIcon, TrashIcon, RefreshCwIcon, ExternalLinkIcon, ChevronLeftIcon, ChevronRightIcon, } from "lucide-react" interface PerformanceEntry { id: string versionId: string hardwareSlug: string userId: string fpsAvg: number | null fpsLow: number | null fpsHigh: number | null protonVersion: string | null osVersion: string | null upscalerType: string | null upscalerVersion: string | null frameGenMethod: string | null loadTimeSsd: number | null loadTimeSd: number | null launchOptions: string | null settingsJson: string | null userNotes: string | null estimatedBatteryMin: number | null customSystem: string | null isRemoved: boolean removedReason: string | null upvotes: number downvotes: number verifiedAt: string | null verifiedBy: string | null createdAt: string updatedAt: string gameId: string gameTitle: string versionString: string hardwareName: string authorName: string | null authorImage: string | null } interface PerformanceApiResponse { data: PerformanceEntry[] total: number limit: number offset: number } type StatusFilter = "all" | "active" | "removed" | "unverified" const LIMIT = 20 function formatDate(value: string | Date | null | undefined) { if (!value) return "—" const d = typeof value === "string" ? new Date(value) : value return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }) } function getInitial(name: string | null | undefined) { return name?.charAt(0)?.toUpperCase() || "?" } function statusBadgeClasses(entry: PerformanceEntry) { if (entry.verifiedAt) return "bg-green-500/10 text-green-400" if (entry.isRemoved) return "bg-red-500/10 text-red-400" return "bg-text/5 text-text/50" } function statusLabel(entry: PerformanceEntry) { if (entry.verifiedAt) return "Verified" if (entry.isRemoved) return "Removed" return "Active" } function statusDotClass(entry: PerformanceEntry) { if (entry.verifiedAt) return "bg-green-400" if (entry.isRemoved) return "bg-red-400" return "bg-text/40" } function formatFps(entry: PerformanceEntry) { if (entry.fpsAvg == null) return "—" if (entry.fpsLow != null && entry.fpsHigh != null) { return `${entry.fpsAvg} (${entry.fpsLow}–${entry.fpsHigh})` } return `${entry.fpsAvg}` } export function BenchmarksClient() { const [entries, setEntries] = useState([]) const [total, setTotal] = useState(0) const [loading, setLoading] = useState(true) const [search, setSearch] = useState("") const [offset, setOffset] = useState(0) const [statusFilter, setStatusFilter] = useState("all") const [actionLoading, setActionLoading] = useState>({}) const [confirmAction, setConfirmAction] = useState< | { type: "verify" | "remove" | "restore"; entry: PerformanceEntry } | null >(null) const [removeReason, setRemoveReason] = useState("") const isSearchChangeRef = useRef(false) const handleSearchChange = (value: string) => { setSearch(value) setOffset(0) isSearchChangeRef.current = true } const handleStatusChange = (value: StatusFilter) => { setStatusFilter(value) setOffset(0) isSearchChangeRef.current = true } const handlePrev = () => { setOffset((prev) => Math.max(0, prev - LIMIT)) isSearchChangeRef.current = false } const handleNext = () => { setOffset((prev) => prev + LIMIT) isSearchChangeRef.current = false } const loadData = useCallback(async () => { setLoading(true) try { const params = new URLSearchParams({ limit: String(LIMIT), offset: String(offset), }) if (statusFilter === "active") { params.set("removed", "false") } else if (statusFilter === "removed") { params.set("removed", "true") } else if (statusFilter === "unverified") { params.set("verified", "false") } if (search.trim()) { params.set("search", search.trim()) } const res = await fetch(`/api/admin/performance?${params.toString()}`) if (res.ok) { const json = (await res.json()) as PerformanceApiResponse setEntries(json.data) setTotal(json.total) } } catch { // ignore } finally { setLoading(false) } }, [search, offset, statusFilter]) useEffect(() => { const delay = isSearchChangeRef.current ? 300 : 0 isSearchChangeRef.current = false let cancelled = false const timer = setTimeout(async () => { if (!cancelled) { await loadData() } }, delay) return () => { cancelled = true clearTimeout(timer) } }, [loadData]) const handleVerify = async (entry: PerformanceEntry) => { setActionLoading((prev) => ({ ...prev, [entry.id]: true })) try { const res = await fetch(`/api/admin/performance/${entry.id}/verify`, { method: "PATCH", }) if (res.ok) { await loadData() setConfirmAction(null) } } finally { setActionLoading((prev) => ({ ...prev, [entry.id]: false })) } } const handleRemove = async (entry: PerformanceEntry) => { setActionLoading((prev) => ({ ...prev, [entry.id]: true })) try { const body: { reason?: string } = {} if (removeReason.trim()) { body.reason = removeReason.trim() } const res = await fetch(`/api/admin/performance/${entry.id}/remove`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }) if (res.ok) { setRemoveReason("") await loadData() setConfirmAction(null) } } finally { setActionLoading((prev) => ({ ...prev, [entry.id]: false })) } } const handleRestore = async (entry: PerformanceEntry) => { setActionLoading((prev) => ({ ...prev, [entry.id]: true })) try { const res = await fetch(`/api/admin/performance/${entry.id}/restore`, { method: "PATCH", }) if (res.ok) { await loadData() setConfirmAction(null) } } finally { setActionLoading((prev) => ({ ...prev, [entry.id]: false })) } } const hasMore = offset + entries.length < total const tabs: { label: string; value: StatusFilter }[] = [ { label: "All", value: "all" }, { label: "Active", value: "active" }, { label: "Removed", value: "removed" }, { label: "Unverified", value: "unverified" }, ] return (
{/* Search */}
handleSearchChange(e.target.value)} placeholder="Search benchmarks..." className="w-full pl-9 pr-4 py-2 rounded-md bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors" />
{/* Filter tabs */}
{tabs.map((tab) => ( ))}
{/* Table */}
{loading ? ( ) : entries.length === 0 ? ( ) : ( entries.map((entry) => ( )) )}
Game Author Hardware FPS Upscaler Status Votes Date Actions
No benchmarks found.

{entry.gameTitle}

{entry.authorImage ? ( {entry.authorName ) : (
{getInitial(entry.authorName)}
)}

{entry.authorName || "Unknown"}

{entry.hardwareName}

{formatFps(entry)} {entry.upscalerType || "—"} {entry.verifiedAt ? ( ) : ( )} {statusLabel(entry)} ▲ {entry.upvotes} ▼ {entry.downvotes} {formatDate(entry.createdAt)}
View {!entry.verifiedAt && ( )} {!entry.isRemoved ? ( ) : ( )}
{/* Pagination */} {entries.length > 0 && (

Showing {offset + 1}–{Math.min(offset + entries.length, total)} of{" "} {total}

)} {/* Confirmation Dialogs */} {confirmAction && (
{confirmAction.type === "verify" && ( <>

Confirm Verify

Are you sure you want to verify this benchmark? It will be marked as verified.

)} {confirmAction.type === "remove" && ( <>

Confirm Remove

Are you sure you want to remove this benchmark?