"use client"
import { AnimatePresence, motion } from "motion/react"
import { useState, useEffect } from "react"
import Link from "next/link"
import Image from "next/image"
import { Gamepad2Icon, SearchIcon, TrendingUpIcon, SparklesIcon, GaugeIcon, FlagIcon } from "lucide-react"
import { useRouter } from "next/navigation"
interface GameCard {
id: string
title: string
capsule_image: string | null
header_image: string | null
playability_status?: string | null
activity_score?: number
benchmark_count?: number
comment_count?: number
upvote_count?: number
avg_fps?: number
report_count?: number
release_date?: string | null
created_at?: string | null
}
interface SectionData {
trending: GameCard[]
bestNewReleases: GameCard[]
mostTested: GameCard[]
mostReported: GameCard[]
}
function SkeletonSections() {
return (
<>
{[1, 2, 3].map((i) => (
{[1, 2, 3, 4].map((j) => (
))}
))}
>
)
}
function PlayabilityDot({ status }: { status: string }) {
const colors: Record = {
great: "bg-green-500",
playable: "bg-blue-500",
needs_tweaks: "bg-yellow-500",
unplayable: "bg-red-500",
}
const labels: Record = {
great: "Plays Great",
playable: "Playable",
needs_tweaks: "Needs Tweaks",
unplayable: "Unplayable",
}
return (
)
}
function GameSection({
title,
icon: Icon,
games,
statKey,
statLabel,
statFormatter,
accentColor = "text-text/50",
muted = false,
}: {
title: string
icon: React.ElementType
games: GameCard[]
statKey: string
statLabel: string
statFormatter?: (v: unknown) => string
accentColor?: string
muted?: boolean
}) {
const router = useRouter()
const formatStat = (v: unknown): string => {
if (statFormatter) return statFormatter(v)
if (typeof v === "number") return `${Math.round(v)} ${statLabel}`
return `${v} ${statLabel}`
}
return (
{games.map((game, idx) => (
router.push(`/game/${game.id}?sync=1`)}
>
{game.capsule_image ? (
) : (
)}
{game.playability_status && game.playability_status !== "unknown" && (
)}
{game.title}
{formatStat((game as unknown as Record)[statKey])}
))}
)
}
export default function Landing() {
const router = useRouter()
const words = ["benchmarks", "settings", "reviews"]
const [currentWord, setCurrentWord] = useState(0)
const [searchQuery, setSearchQuery] = useState("")
// Landing section state
const [sections, setSections] = useState({
trending: [],
bestNewReleases: [],
mostTested: [],
mostReported: [],
})
const [sectionsLoading, setSectionsLoading] = useState(true)
// Animated words cycle — use useEffect with proper cleanup
useEffect(() => {
const interval = setInterval(() => {
setCurrentWord((prev) => (prev + 1) % words.length)
}, 2000)
return () => clearInterval(interval)
}, [words.length])
// Fetch all 4 sections in parallel on mount
useEffect(() => {
let cancelled = false
async function fetchSections() {
try {
const [trending, bestNew, mostTested, mostReported] = await Promise.all([
fetch("/api/dashboard/trending").then(r => r.ok ? r.json() : []),
fetch("/api/dashboard/best-new-releases").then(r => r.ok ? r.json() : []),
fetch("/api/dashboard/most-tested").then(r => r.ok ? r.json() : []),
fetch("/api/dashboard/most-reported").then(r => r.ok ? r.json() : []),
])
if (!cancelled) {
setSections({
trending: Array.isArray(trending) ? trending : [],
bestNewReleases: Array.isArray(bestNew) ? bestNew : [],
mostTested: Array.isArray(mostTested) ? mostTested : [],
mostReported: Array.isArray(mostReported) ? mostReported : [],
})
}
} catch {
// Silently fail — sections are best-effort
} finally {
if (!cancelled) setSectionsLoading(false)
}
}
fetchSections()
return () => { cancelled = true }
}, [])
const handleSearchSubmit = () => {
if (searchQuery.trim()) {
sessionStorage.setItem("focusSearch", "true")
router.push(`/search?q=${encodeURIComponent(searchQuery.trim())}`)
}
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
handleSearchSubmit()
}
}
return (
<>
{/* ── Hero Section ── */}
{/* ── Landing Sections ── */}
{sectionsLoading ? (
) : (
<>
{sections.trending.length > 0 && (
)}
{sections.bestNewReleases.length > 0 && (
`${Math.round(Number(v))} FPS`}
accentColor="text-green-400"
/>
)}
{sections.mostTested.length > 0 && (
)}
{sections.mostReported.length > 0 && (
)}
>
)}
>
)
}