"use client" import { useEffect, useMemo, useState } from "react" import Link from "next/link" import { cn } from "@/lib/utils" import { ConfirmDialog } from "@/components/ui/modal" import { Loader2, SearchIcon, Lightbulb, ExternalLinkIcon, CheckCircle2Icon, XCircleIcon, ChevronLeftIcon, ChevronRightIcon, } from "lucide-react" interface Suggestion { id: string gameId: string gameTitle: string fieldName: string currentValue: string | null proposedValue: string reason: string | null status: string createdAt: string userName: string | null } type ConfirmType = "approve" | "reject" interface SuggestionsApiResponse { data: Suggestion[] total: number limit: number offset: number } const LIMIT = 50 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" }) } export function SuggestionsClient() { const [suggestions, setSuggestions] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState("") const [offset, setOffset] = useState(0) const [total, setTotal] = useState(0) const [actionLoading, setActionLoading] = useState>({}) const [confirmAction, setConfirmAction] = useState<{ type: ConfirmType suggestion: Suggestion } | null>(null) const [activeTab, setActiveTab] = useState("all") const statusTabs = [ { label: "All", value: "all" }, { label: "Pending", value: "pending" }, { label: "Approved", value: "approved" }, { label: "Rejected", value: "rejected" }, ] useEffect(() => { let cancelled = false const run = async () => { try { const res = await fetch(`/api/community-suggestions/admin?limit=${LIMIT}`) if (res.ok && !cancelled) { const json = (await res.json()) as SuggestionsApiResponse setSuggestions(json.data) setTotal(json.total) } } catch { // ignore } finally { if (!cancelled) setLoading(false) } } run() return () => { cancelled = true } }, []) const filtered = useMemo(() => { let result = suggestions const term = search.trim().toLowerCase() if (term) { result = result.filter( (s) => s.gameTitle?.toLowerCase().includes(term) || s.fieldName?.toLowerCase().includes(term) || s.proposedValue?.toLowerCase().includes(term) || s.userName?.toLowerCase().includes(term) || s.reason?.toLowerCase().includes(term) ) } if (activeTab !== "all") { result = result.filter((s) => s.status === activeTab) } return result }, [suggestions, search, activeTab]) const paginated = filtered.slice(offset, offset + LIMIT) const hasMore = offset + LIMIT < total const handlePrev = () => setOffset((prev) => Math.max(0, prev - LIMIT)) const handleNext = () => setOffset((prev) => prev + LIMIT) const handleReview = async ( suggestion: Suggestion, status: "approved" | "rejected" ) => { setActionLoading((prev) => ({ ...prev, [suggestion.id]: true })) try { const res = await fetch( `/api/community-suggestions/${suggestion.id}/review`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status }), } ) if (res.ok) { setSuggestions((prev) => prev.filter((s) => s.id !== suggestion.id) ) setConfirmAction(null) } } finally { setActionLoading((prev) => ({ ...prev, [suggestion.id]: false })) } } return (
{/* Header */}

Suggestions

({filtered.length})
{/* Search */}
setSearch(e.target.value)} placeholder="Search suggestions..." className="w-full pl-9 pr-4 py-2.5 rounded-lg bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors" />
{/* Status Tabs */}
{statusTabs.map((tab) => ( ))}
{/* Suggestions List */} {loading ? (
) : paginated.length === 0 ? (

No suggestions found

) : (
{paginated.map((suggestion) => ( ))}
Game Field Current Proposed Reason By Date Status Actions

{suggestion.gameTitle}

{suggestion.fieldName}

{suggestion.currentValue || "—"}

{suggestion.proposedValue}

{suggestion.reason || "—"}

{suggestion.userName || "Unknown"}

{formatDate(suggestion.createdAt)} {suggestion.status}
View
)} {/* Pagination */} {total > 0 && (

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

)} {/* Confirm Dialog */} setConfirmAction(null)} onConfirm={() => { if (confirmAction) { handleReview( confirmAction.suggestion, confirmAction.type === "approve" ? "approved" : "rejected" ) } }} title={`Confirm ${confirmAction?.type === "approve" ? "Approval" : "Rejection"}`} message={ confirmAction ? `Are you sure you want to ${confirmAction.type === "approve" ? "approve" : "reject"} the "${confirmAction.suggestion.fieldName}" suggestion for "${confirmAction.suggestion.gameTitle}"?` : "" } confirmLabel={confirmAction?.type === "approve" ? "Approve" : "Reject"} cancelLabel="Cancel" variant={confirmAction?.type === "approve" ? "default" : "destructive"} loading={confirmAction ? !!actionLoading[confirmAction.suggestion.id] : false} />
) }