From 32d52cf3bae27c0d44e48f183cd9ea0bf6fbeceb Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Thu, 30 Apr 2026 20:25:57 +0800 Subject: [PATCH] Task 17: Revamp Games List with New Filters & Badges - Add new filter state variables (min/max FPS, FSR, Proton/Native, anti-cheat, playability, Steam review score, free-to-play, multiplayer) - Expand sort options to include performance, popularity, release_date, steam_reviews - Add Performance, Compatibility, and Other filter UI sections with inputs - Integrate SavedFilters component for saving/loading filter presets - Add PlayabilityBadge and AntiCheatBadge to game cards - Update API listing to return antiCheatRelevant/antiCheatStatus per game - Update server-side games page to include new fields in initialGames - Update clear-filters logic across filter panel and empty state --- app/games/games-page-client.tsx | 274 ++++++++++++++++++++++++++++++-- app/games/page.tsx | 21 +++ lib/api/games-listing.ts | 15 ++ 3 files changed, 301 insertions(+), 9 deletions(-) diff --git a/app/games/games-page-client.tsx b/app/games/games-page-client.tsx index 3465de5..6ff1001 100644 --- a/app/games/games-page-client.tsx +++ b/app/games/games-page-client.tsx @@ -11,6 +11,9 @@ import { XIcon, Loader2Icon, } from "lucide-react" +import { AntiCheatBadge } from "@/components/anti-cheat-badge" +import { PlayabilityBadge } from "@/components/playability-badge" +import { SavedFilters } from "@/components/saved-filters" interface GamesListItem { id: string @@ -21,8 +24,13 @@ interface GamesListItem { headerImage: string | null genres: string[] | null source: string + steamReviewScore: number | null + playabilityStatus: "great" | "playable" | "needs_tweaks" | "unplayable" | "unknown" | null + onlineMultiplayerStatus: "none" | "supported" | "unknown" | null benchmarkCount: number deckStatus: string | null + antiCheatRelevant: boolean + antiCheatStatus: "none" | "supported" | "unsupported" | "unknown" | null } interface DeviceOption { @@ -30,7 +38,7 @@ interface DeviceOption { name: string } -type SortOption = "recent" | "name" | "benchmarks" +type SortOption = "recent" | "name" | "benchmarks" | "performance" | "popularity" | "release_date" | "steam_reviews" type SortDirection = "asc" | "desc" const DECK_STATUS_CONFIG: Record = { @@ -42,8 +50,12 @@ const DECK_STATUS_CONFIG: Record = const SORT_OPTIONS: { value: SortOption; label: string }[] = [ { value: "recent", label: "Recently Added" }, - { value: "name", label: "Name A\u2013Z" }, + { value: "name", label: "Name A–Z" }, { value: "benchmarks", label: "Most Benchmarks" }, + { value: "performance", label: "Best Performance" }, + { value: "popularity", label: "Most Popular" }, + { value: "release_date", label: "Release Date" }, + { value: "steam_reviews", label: "Steam Reviews" }, ] export function GamesPageClient({ @@ -67,6 +79,15 @@ export function GamesPageClient({ const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [showFilters, setShowFilters] = useState(false) + const [minFps, setMinFps] = useState("") + const [maxFps, setMaxFps] = useState("") + const [fsrSupport, setFsrSupport] = useState(false) + const [protonNative, setProtonNative] = useState("any") + const [antiCheatStatus, setAntiCheatStatus] = useState("any") + const [playabilityStatus, setPlayabilityStatus] = useState("") + const [steamReviewMin, setSteamReviewMin] = useState("") + const [isFree, setIsFree] = useState(false) + const [hasMultiplayer, setHasMultiplayer] = useState(false) const observerRef = useRef(null) const sentinelRef = useRef(null) @@ -82,9 +103,22 @@ export function GamesPageClient({ if (search) params.set("search", search) if (selectedDevice) params.set("device", selectedDevice) if (selectedGenres.length === 1) params.set("genre", selectedGenres[0]) + if (minFps) params.set("minFps", minFps) + if (maxFps) params.set("maxFps", maxFps) + if (fsrSupport) params.set("fsrSupport", "true") + if (protonNative !== "any") params.set("protonNative", protonNative) + if (antiCheatStatus !== "any") params.set("antiCheatStatus", antiCheatStatus) + if (playabilityStatus) params.set("playabilityStatus", playabilityStatus) + if (steamReviewMin) params.set("steamReviewScore", steamReviewMin) + if (isFree) params.set("isFree", "true") + if (hasMultiplayer) params.set("hasMultiplayer", "true") return `/api/games/listing?${params.toString()}` }, - [sort, sortDirection, search, selectedDevice, selectedGenres], + [ + sort, sortDirection, search, selectedDevice, selectedGenres, + minFps, maxFps, fsrSupport, protonNative, antiCheatStatus, + playabilityStatus, steamReviewMin, isFree, hasMultiplayer, + ], ) // Load more function for infinite scroll @@ -230,15 +264,46 @@ export function GamesPageClient({ @@ -308,12 +373,160 @@ export function GamesPageClient({ + {/* Performance Filters */} +
+

Performance

+
+ setMinFps(e.target.value)} + className="w-24 rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm" + /> + setMaxFps(e.target.value)} + className="w-24 rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm" + /> +
+ +
+ + {/* Compatibility Filters */} +
+

Compatibility

+ + + +
+ + {/* Other Filters */} +
+

Other

+ + + setSteamReviewMin(e.target.value)} + className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm" + /> +
+ + {/* Saved Filters */} + { + setMinFps(filters.minFps || "") + setMaxFps(filters.maxFps || "") + setFsrSupport(filters.fsrSupport || false) + setProtonNative(filters.protonNative || "any") + setAntiCheatStatus(filters.antiCheatStatus || "any") + setPlayabilityStatus(filters.playabilityStatus || "") + setSteamReviewMin(filters.steamReviewMin || "") + setIsFree(filters.isFree || false) + setHasMultiplayer(filters.hasMultiplayer || false) + if (filters.genre) setSelectedGenres([filters.genre]) + else setSelectedGenres([]) + if (filters.device) setSelectedDevice(filters.device) + else setSelectedDevice("") + if (filters.sortBy) setSort(filters.sortBy as SortOption) + }} + /> + {/* Clear filters */} - {(selectedDevice || selectedGenres.length > 0) && ( + {(selectedDevice || + selectedGenres.length > 0 || + minFps || + maxFps || + fsrSupport || + protonNative !== "any" || + antiCheatStatus !== "any" || + playabilityStatus || + steamReviewMin || + isFree || + hasMultiplayer) && (