"use client" import { useState } from "react" import Image from "next/image" import { useRouter } from "next/navigation" import { AnimatePresence, motion } from "motion/react" import { useSession } from "@/lib/auth-client" import type { GameSettingCategory } from "@/lib/db/schema/performanceEntries" import { ThumbsUpIcon, ThumbsDownIcon, FlagIcon, TrashIcon, ChevronLeftIcon, ChevronRightIcon, ShieldCheckIcon, XIcon, UserIcon, ShareIcon, PencilIcon, } from "lucide-react" import { TiptapRenderer } from "@/components/tiptap-renderer" import { ScreenshotLightbox } from "@/components/ui/screenshot-lightbox" type TabKey = "details" | "media" | "settings" | "notes" interface Preset { id: string gameId?: string hardwareSlug: string hardwareName: string upvotes: number downvotes: number settingsJson: GameSettingCategory[] | null settingsCount: number fpsAvg: number | null fpsLow: number | null fpsHigh: number | null fpsOnePercentLow: number | null upscalerType: string | null upscalerVersion: string | null frameGenMethod: string | null protonVersion: string | null osVersion: string | null launchOptions: string | null loadTimeSsd: number | null loadTimeSd: number | null tdpWatts: number | null youtubeVideoId: string | null screenshots: Array<{ id: string url: string width: number height: number }> | null hardwareWattHours: number | null hardwareDeviceType: string | null customSystem: boolean userNotes: string | null userId: string userName: string | null userImage: string | null verifiedAt: string | null isPinned: boolean pinnedAt: string | null createdAt: string versionString: string | null buildId: string | null gameAntiCheatName: string | null gameAntiCheatStatus: "none" | "supported" | "unsupported" | "unknown" | null } interface PresetDetailModalProps { preset: Preset gameId: string gameSource: string gameSteamAppId: number | null gameSlug: string | null 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) } function MetaItem({ label, value, }: { label: string value: React.ReactNode | string | null }) { return (
{label} {value ?? "—"}
) } function hasPerformanceData(p: Preset): boolean { return ( p.fpsAvg !== null || p.fpsOnePercentLow !== null || p.loadTimeSsd !== null || p.loadTimeSd !== null ) } function hasHardwarePowerData(p: Preset): boolean { return p.tdpWatts !== null || p.hardwareWattHours !== null } function hasSoftwareData(p: Preset): boolean { return !!( p.protonVersion || p.osVersion || (p.upscalerType && p.upscalerType !== "none") || (p.frameGenMethod && p.frameGenMethod !== "none") || p.launchOptions ) } function hasGameInfoData(p: Preset): boolean { return !!(p.versionString || p.buildId || p.gameAntiCheatName) } export function PresetDetailModal({ preset, gameId, gameSource, gameSteamAppId, gameSlug, onClose, onDelete, onReport, hasReported, }: PresetDetailModalProps) { const router = useRouter() const { data: session } = useSession() const [activeCategoryIndex, setActiveCategoryIndex] = useState(0) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [showReportForm, setShowReportForm] = useState(false) const [reportReason, setReportReason] = useState< "inaccurate" | "spam" | "inappropriate" | "other" >("inaccurate") const [reportDetails, setReportDetails] = useState("") const [copied, setCopied] = useState(false) const [userVote, setUserVote] = useState<"up" | "down" | null>(null) const [localUpvotes, setLocalUpvotes] = useState(preset.upvotes) const [localDownvotes, setLocalDownvotes] = useState(preset.downvotes) const [activeTab, setActiveTab] = useState("media") const [lightboxOpen, setLightboxOpen] = useState(false) const [lightboxIndex, setLightboxIndex] = useState(0) const [deletingScreenshotId, setDeletingScreenshotId] = useState< string | null >(null) const isOwner = session?.user?.id === preset.userId const isAdmin = session?.user?.role === "admin" const isAuthenticated = !!session?.user const handleShare = () => { let identifier: string if (gameSource === "steam" && gameSteamAppId != null) { identifier = String(gameSteamAppId) } else if (gameSlug) { identifier = gameSlug } else { identifier = gameId // fallback for edge cases } const url = `${window.location.origin}/game/${identifier}?preset=${preset.id}` navigator.clipboard.writeText(url) setCopied(true) setTimeout(() => setCopied(false), 2000) } const categories = preset.settingsJson ?? [] const hasCategories = categories.length > 0 const currentCategory = hasCategories ? categories[activeCategoryIndex] : null const goPrevCategory = () => { setActiveCategoryIndex((prev) => prev > 0 ? prev - 1 : categories.length - 1, ) } const goNextCategory = () => { setActiveCategoryIndex((prev) => prev < categories.length - 1 ? prev + 1 : 0, ) } const handleUpvote = async () => { if (!isAuthenticated || userVote === "up") return try { const res = await fetch(`/api/performance/${preset.id}/upvote`, { method: "POST", }) if (res.ok) { if (userVote === "down") setLocalDownvotes((d) => d - 1) setLocalUpvotes((u) => u + 1) setUserVote("up") } } catch (err) { console.error("Failed to upvote:", err) } } const handleDownvote = async () => { if (!isAuthenticated || userVote === "down") return try { const res = await fetch(`/api/performance/${preset.id}/downvote`, { method: "POST", }) if (res.ok) { if (userVote === "up") setLocalUpvotes((u) => u - 1) setLocalDownvotes((d) => d + 1) setUserVote("down") } } catch (err) { console.error("Failed to downvote:", err) } } const handleReportSubmit = () => { onReport(preset.id, reportReason, reportDetails.trim() || undefined) setShowReportForm(false) setReportDetails("") } const handleTogglePin = async () => { const method = preset.isPinned ? "DELETE" : "POST" try { const res = await fetch(`/api/performance/${preset.id}/pin`, { method, }) if (res.ok) { router.refresh() } } catch (err) { console.error("Failed to toggle pin:", err) } } async function handleDeleteScreenshot(screenshotId: string) { setDeletingScreenshotId(screenshotId) try { const res = await fetch( `/api/performance/${preset.id}/screenshots/${screenshotId}`, { method: "DELETE" }, ) if (res.ok) { router.refresh() } } catch (err) { console.error("Failed to delete screenshot:", err) } finally { setDeletingScreenshotId(null) } } const visibleTabs: TabKey[] = ["details", "media", "settings"] if (preset.userNotes) visibleTabs.push("notes") return ( <> {/* Backdrop */} {/* Modal container */} {/* Modal card */} e.stopPropagation()} > {/* Header */}

{preset.hardwareName}

{/* Mobile tabs */}
{visibleTabs.map((tab) => ( ))}
{/* Two-column body */}
{/* Left panel — Details (always visible on desktop, tab on mobile) */}
{/* User info */}
{preset.userImage ? ( {preset.userName ) : ( )}
{preset.userName || "Anonymous"} {preset.verifiedAt && ( Verified on{" "} {new Date( preset.verifiedAt, ).toLocaleDateString( undefined, { year: "numeric", month: "short", day: "numeric", }, )} )}
{formatDate(preset.createdAt)}
{/* Actions */}
{isAdmin && ( )} {(isOwner || isAdmin) && ( <> {!showDeleteConfirm ? ( ) : (
Are you sure?
)} )} {(isOwner || isAdmin) && ( )} {session && !hasReported && ( <> {!showReportForm ? ( ) : (