From 830c14fd3e9e7aaf40803abd0ebdc20d64b06705 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 27 Apr 2026 15:49:25 +0800 Subject: [PATCH] feat: add PresetDetailModal component with settings view, delete, and report --- app/game/[id]/preset-detail-modal.tsx | 524 ++++++++++++++++++++++++++ 1 file changed, 524 insertions(+) create mode 100644 app/game/[id]/preset-detail-modal.tsx diff --git a/app/game/[id]/preset-detail-modal.tsx b/app/game/[id]/preset-detail-modal.tsx new file mode 100644 index 0000000..8c2a81f --- /dev/null +++ b/app/game/[id]/preset-detail-modal.tsx @@ -0,0 +1,524 @@ +"use client" + +import { useState } from "react" +import Image from "next/image" +import { AnimatePresence, motion } from "motion/react" +import { useSession } from "@/lib/auth-client" +import type { GameSettingCategory } from "@/lib/db/schema/performanceEntries" +import { + TrendingUpIcon, + TrendingDownIcon, + FlagIcon, + TrashIcon, + ChevronDownIcon, + ShieldCheckIcon, + XIcon, + UserIcon, +} from "lucide-react" + +interface Preset { + id: string + hardwareSlug: string + hardwareName: string + upvotes: number + downvotes: number + settingsJson: GameSettingCategory[] | null + settingsCount: number + fpsAvg: number | null + fpsLow: number | null + fpsHigh: number | null + upscalerType: string | null + upscalerVersion: string | null + frameGenMethod: string | null + protonVersion: string | null + osVersion: string | null + launchOptions: string | null + userNotes: string | null + userId: string + userName: string | null + userImage: string | null + verifiedAt: string | null + createdAt: string +} + +interface PresetDetailModalProps { + preset: Preset + onClose: () => void + onDelete: (presetId: string) => void + onReport: ( + presetId: string, + reason: "inaccurate" | "spam" | "inappropriate" | "other", + details?: string, + ) => void + hasReported: boolean +} + +function formatDate(value: string | null): string { + if (!value) return "—" + return new Date(value).toLocaleDateString() +} + +function formatValue(value: string | number | boolean): string { + if (typeof value === "boolean") return value ? "On" : "Off" + return String(value) +} + +export function PresetDetailModal({ + preset, + onClose, + onDelete, + onReport, + hasReported, +}: PresetDetailModalProps) { + const { data: session } = useSession() + + const [openCategories, setOpenCategories] = useState>( + new Set(), + ) + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) + const [showReportForm, setShowReportForm] = useState(false) + const [reportReason, setReportReason] = useState< + "inaccurate" | "spam" | "inappropriate" | "other" + >("inaccurate") + const [reportDetails, setReportDetails] = useState("") + + const isOwner = session?.user?.id === preset.userId + const isAdmin = session?.user?.role === "admin" + + const toggleCategory = (index: number) => { + setOpenCategories((prev) => { + const next = new Set(prev) + if (next.has(index)) { + next.delete(index) + } else { + next.add(index) + } + return next + }) + } + + const handleReportSubmit = () => { + onReport( + preset.id, + reportReason, + reportDetails.trim() || undefined, + ) + setShowReportForm(false) + setReportDetails("") + } + + return ( + + <> + {/* Backdrop */} + + + {/* Modal container */} + + {/* Modal card */} + e.stopPropagation()} + > + {/* Header */} +
+
+
+ {preset.userImage ? ( + { + ) : ( + + )} +
+
+
+ + {preset.userName || "Anonymous"} + + {preset.verifiedAt && ( + + + + )} +
+ + {formatDate(preset.createdAt)} + +
+
+ +
+ + {/* Stats Row */} +
+
+ + Upvotes + +
+ + + {preset.upvotes} + +
+
+
+ + Downvotes + +
+ + + {preset.downvotes} + +
+
+
+ + Avg FPS + +
+ + {preset.fpsAvg ?? "—"} + + {preset.fpsAvg !== null && + preset.fpsLow !== null && + preset.fpsHigh !== null && ( + + ({Math.round(preset.fpsLow)} + – + {Math.round(preset.fpsHigh)}) + + )} +
+
+
+ + {/* Metadata Grid */} +
+ + + + + +
+ + {/* Settings Table */} + {preset.settingsJson && + preset.settingsJson.length > 0 && ( +
+

+ Settings ({preset.settingsCount}) +

+
+ {preset.settingsJson.map( + (category, idx) => ( +
+ + + {openCategories.has( + idx, + ) && ( + +
+ {category.settings.map( + ( + setting, + sIdx, + ) => ( +
+ + { + setting.title + } + + + {formatValue( + setting.value, + )} + +
+ ), + )} +
+
+ )} +
+
+ ), + )} +
+
+ )} + + {/* User Notes */} + {preset.userNotes && ( +
+

+ Notes +

+

+ {preset.userNotes} +

+
+ )} + + {/* Actions */} +
+ {(isOwner || isAdmin) && ( + <> + {!showDeleteConfirm ? ( + + ) : ( +
+ + Are you sure? + + + +
+ )} + + )} + + {session && !hasReported && ( + <> + {!showReportForm ? ( + + ) : ( +
+ +