"use client" import { Suspense, useState, useEffect } from "react" import { useRouter, useSearchParams } from "next/navigation" import { motion, AnimatePresence } from "motion/react" import { ExternalLinkIcon, Gamepad2Icon, MessageSquareIcon, SettingsIcon, TrendingUpIcon, DatabaseIcon, SparklesIcon, } from "lucide-react" import { FaSteam } from "react-icons/fa" import Image from "next/image" import { WindowsIcon, MacIcon, LinuxIcon } from "@/app/components/PlatformIcons" interface UnifiedResult { kind: "local" | "steam" id?: string appId: number | null title: string image: string | null developer: string | null publisher: string | null description: string | null genres: string[] | null source: string counts: { benchmarks: number; presets: number; comments: number } | null platformSupport: { isSupported: boolean protonStatus: string antiCheatRelevant: boolean antiCheatName: string | null antiCheatStatus: string } | null metascore?: string | null price?: { currency: string; initial: number; final: number } | null platforms?: { windows: boolean; mac: boolean; linux: boolean } | null controllerSupport?: string | null } function SearchContent() { const searchParams = useSearchParams() const router = useRouter() const query = searchParams.get("q") || "" const [results, setResults] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const isValidQuery = query && query.length >= 2 // Handle direct navigation / browser back-forward useEffect(() => { if (!isValidQuery) return let cancelled = false async function fetchResults() { setLoading(true) setError(null) try { const res = await fetch( `/api/search/unified?q=${encodeURIComponent(query)}`, ) if (!res.ok) throw new Error(await res.text()) const data = await res.json() if (!cancelled) setResults(data.results || []) } catch (err) { if (!cancelled) { setError("Failed to fetch search results") console.error(err) } } finally { if (!cancelled) setLoading(false) } } fetchResults() return () => { cancelled = true } }, [isValidQuery, query]) function handleClick(result: UnifiedResult) { const path = result.appId ? `/game/${result.appId}` : `/game/${result.id}` router.push(path) } return (
{query ? `Results for "${query}"` : "Search using game name or AppID"} {query ? `${results.length} result${results.length !== 1 ? "s" : ""} found` : "Enter a game name or AppID to find benchmarks, settings, and reviews."} {!isValidQuery && (

Start typing to search for games

)} {isValidQuery && loading && (

Searching for "{query}"...

)} {isValidQuery && !loading && error && (

{error}

)} {isValidQuery && !loading && !error && results.length === 0 && (

No results found for "{query}"

)} {isValidQuery && !loading && !error && results.length > 0 && ( {results.map((result, idx) => ( ))} )}
) } function SearchResultCard({ result, onClick, index, }: { result: UnifiedResult onClick: (r: UnifiedResult) => void index: number }) { const isLocal = result.kind === "local" const counts = result.counts const hasData = isLocal && counts && (counts.benchmarks > 0 || counts.presets > 0 || counts.comments > 0) return ( {/* Full-card click target */}
onClick(result)} />
{/* Cover */} {/* Main Content */}
{/* Row 1: Title + metascore/price row */}

{result.title}

{(result.developer || result.publisher) && (

{result.developer} {result.developer && result.publisher ? " · " : ""} {result.publisher}

)}
{/* Metascore + Price row */}
{result.metascore ? ( {result.metascore} ) : null}
{/* Row 2: Description */} {result.description && (

{result.description}

)} {/* Row 3: Genre tags + Platform icons + Controller */}
{result.genres && result.genres.length > 0 && (
{result.genres.slice(0, 3).map((genre) => ( {genre} ))} {result.genres.length > 3 && ( +{result.genres.length - 3} )}
)} {/* Platform icons — always show all 3, color if present */}
{result.controllerSupport && ( Controller )}
{/* Row 4: Stats row */}
{hasData ? ( <> {counts!.benchmarks > 0 && ( )} {counts!.presets > 0 && ( )} {counts!.comments > 0 && ( )} ) : isLocal ? ( No data yet — be the first to contribute ) : ( )}
{/* Row 5: Anti-cheat info */} {result.platformSupport?.antiCheatRelevant && (
Anti-cheat: {result.platformSupport.antiCheatName || "Unknown"} {" "}— {result.platformSupport.antiCheatStatus}
)}
{/* Right Panel — Desktop Only */}
{/* Deck Status */} {/* Avg FPS */} {/* Version */}
) } function DataField({ label, value, bar, color, }: { label: string value: string bar?: boolean color?: string }) { return (
{label}
{bar && (
)} {value}
) } function protonLabel(status: string): string { const map: Record = { native: "Native", proton: "Proton", unsupported: "Unsupported", unknown: "Unknown", } return map[status] || status } function protonColor(status: string): string { const map: Record = { native: "text-green-400", proton: "text-blue-400", unsupported: "text-red-400", unknown: "text-text/25", } return map[status] || "text-text/25" } function GameCover({ image, title }: { image: string | null; title: string }) { const [error, setError] = useState(false) if (image && !error) { return ( {title} setError(true)} /> ) } return (
) } function PriceTag({ price, }: { price?: { currency: string; initial: number; final: number } | null }) { if (!price || price.final === 0) { return ( Free ) } const isDiscounted = price.final < price.initial const fmt = new Intl.NumberFormat("en-US", { style: "currency", currency: price.currency, }) return ( {isDiscounted && ( {fmt.format(price.initial / 100)} )} {fmt.format(price.final / 100)} ) } function StatBadge({ icon: Icon, count, label, }: { icon: React.ElementType count: number label: string }) { return ( {count} {label} ) } export default function SearchPage() { return ( ) }