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
This commit is contained in:
@@ -11,6 +11,9 @@ import {
|
|||||||
XIcon,
|
XIcon,
|
||||||
Loader2Icon,
|
Loader2Icon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import { AntiCheatBadge } from "@/components/anti-cheat-badge"
|
||||||
|
import { PlayabilityBadge } from "@/components/playability-badge"
|
||||||
|
import { SavedFilters } from "@/components/saved-filters"
|
||||||
|
|
||||||
interface GamesListItem {
|
interface GamesListItem {
|
||||||
id: string
|
id: string
|
||||||
@@ -21,8 +24,13 @@ interface GamesListItem {
|
|||||||
headerImage: string | null
|
headerImage: string | null
|
||||||
genres: string[] | null
|
genres: string[] | null
|
||||||
source: string
|
source: string
|
||||||
|
steamReviewScore: number | null
|
||||||
|
playabilityStatus: "great" | "playable" | "needs_tweaks" | "unplayable" | "unknown" | null
|
||||||
|
onlineMultiplayerStatus: "none" | "supported" | "unknown" | null
|
||||||
benchmarkCount: number
|
benchmarkCount: number
|
||||||
deckStatus: string | null
|
deckStatus: string | null
|
||||||
|
antiCheatRelevant: boolean
|
||||||
|
antiCheatStatus: "none" | "supported" | "unsupported" | "unknown" | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeviceOption {
|
interface DeviceOption {
|
||||||
@@ -30,7 +38,7 @@ interface DeviceOption {
|
|||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortOption = "recent" | "name" | "benchmarks"
|
type SortOption = "recent" | "name" | "benchmarks" | "performance" | "popularity" | "release_date" | "steam_reviews"
|
||||||
type SortDirection = "asc" | "desc"
|
type SortDirection = "asc" | "desc"
|
||||||
|
|
||||||
const DECK_STATUS_CONFIG: Record<string, { label: string; className: string }> = {
|
const DECK_STATUS_CONFIG: Record<string, { label: string; className: string }> = {
|
||||||
@@ -42,8 +50,12 @@ const DECK_STATUS_CONFIG: Record<string, { label: string; className: string }> =
|
|||||||
|
|
||||||
const SORT_OPTIONS: { value: SortOption; label: string }[] = [
|
const SORT_OPTIONS: { value: SortOption; label: string }[] = [
|
||||||
{ value: "recent", label: "Recently Added" },
|
{ value: "recent", label: "Recently Added" },
|
||||||
{ value: "name", label: "Name A\u2013Z" },
|
{ value: "name", label: "Name A–Z" },
|
||||||
{ value: "benchmarks", label: "Most Benchmarks" },
|
{ 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({
|
export function GamesPageClient({
|
||||||
@@ -67,6 +79,15 @@ export function GamesPageClient({
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [showFilters, setShowFilters] = useState(false)
|
const [showFilters, setShowFilters] = useState(false)
|
||||||
|
const [minFps, setMinFps] = useState<string>("")
|
||||||
|
const [maxFps, setMaxFps] = useState<string>("")
|
||||||
|
const [fsrSupport, setFsrSupport] = useState<boolean>(false)
|
||||||
|
const [protonNative, setProtonNative] = useState<string>("any")
|
||||||
|
const [antiCheatStatus, setAntiCheatStatus] = useState<string>("any")
|
||||||
|
const [playabilityStatus, setPlayabilityStatus] = useState<string>("")
|
||||||
|
const [steamReviewMin, setSteamReviewMin] = useState<string>("")
|
||||||
|
const [isFree, setIsFree] = useState<boolean>(false)
|
||||||
|
const [hasMultiplayer, setHasMultiplayer] = useState<boolean>(false)
|
||||||
const observerRef = useRef<IntersectionObserver | null>(null)
|
const observerRef = useRef<IntersectionObserver | null>(null)
|
||||||
const sentinelRef = useRef<HTMLDivElement>(null)
|
const sentinelRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
@@ -82,9 +103,22 @@ export function GamesPageClient({
|
|||||||
if (search) params.set("search", search)
|
if (search) params.set("search", search)
|
||||||
if (selectedDevice) params.set("device", selectedDevice)
|
if (selectedDevice) params.set("device", selectedDevice)
|
||||||
if (selectedGenres.length === 1) params.set("genre", selectedGenres[0])
|
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()}`
|
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
|
// Load more function for infinite scroll
|
||||||
@@ -230,15 +264,46 @@ export function GamesPageClient({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowFilters(!showFilters)}
|
onClick={() => setShowFilters(!showFilters)}
|
||||||
className={`px-3 py-2 rounded-md text-sm transition-colors cursor-pointer border ${
|
className={`px-3 py-2 rounded-md text-sm transition-colors cursor-pointer border ${
|
||||||
showFilters || selectedDevice || selectedGenres.length > 0
|
showFilters ||
|
||||||
|
selectedDevice ||
|
||||||
|
selectedGenres.length > 0 ||
|
||||||
|
minFps ||
|
||||||
|
maxFps ||
|
||||||
|
fsrSupport ||
|
||||||
|
protonNative !== "any" ||
|
||||||
|
antiCheatStatus !== "any" ||
|
||||||
|
playabilityStatus ||
|
||||||
|
steamReviewMin ||
|
||||||
|
isFree ||
|
||||||
|
hasMultiplayer
|
||||||
? "bg-primary/10 text-primary border-primary/30"
|
? "bg-primary/10 text-primary border-primary/30"
|
||||||
: "bg-text/5 text-text/60 hover:text-text/80 border-border hover:border-border-active"
|
: "bg-text/5 text-text/60 hover:text-text/80 border-border hover:border-border-active"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Filters
|
Filters
|
||||||
{(selectedDevice || selectedGenres.length > 0) && (
|
{(selectedDevice ||
|
||||||
|
selectedGenres.length > 0 ||
|
||||||
|
minFps ||
|
||||||
|
maxFps ||
|
||||||
|
fsrSupport ||
|
||||||
|
protonNative !== "any" ||
|
||||||
|
antiCheatStatus !== "any" ||
|
||||||
|
playabilityStatus ||
|
||||||
|
steamReviewMin ||
|
||||||
|
isFree ||
|
||||||
|
hasMultiplayer) && (
|
||||||
<span className="ml-1.5 inline-flex items-center justify-center w-4 h-4 rounded-full bg-primary text-background text-[10px] font-bold">
|
<span className="ml-1.5 inline-flex items-center justify-center w-4 h-4 rounded-full bg-primary text-background text-[10px] font-bold">
|
||||||
{selectedGenres.length + (selectedDevice ? 1 : 0)}
|
{selectedGenres.length +
|
||||||
|
(selectedDevice ? 1 : 0) +
|
||||||
|
(minFps ? 1 : 0) +
|
||||||
|
(maxFps ? 1 : 0) +
|
||||||
|
(fsrSupport ? 1 : 0) +
|
||||||
|
(protonNative !== "any" ? 1 : 0) +
|
||||||
|
(antiCheatStatus !== "any" ? 1 : 0) +
|
||||||
|
(playabilityStatus ? 1 : 0) +
|
||||||
|
(steamReviewMin ? 1 : 0) +
|
||||||
|
(isFree ? 1 : 0) +
|
||||||
|
(hasMultiplayer ? 1 : 0)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
@@ -308,12 +373,160 @@ export function GamesPageClient({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Performance Filters */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="text-sm font-medium text-zinc-300">Performance</h4>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
placeholder="Min FPS"
|
||||||
|
value={minFps}
|
||||||
|
onChange={(e) => setMinFps(e.target.value)}
|
||||||
|
className="w-24 rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
placeholder="Max FPS"
|
||||||
|
value={maxFps}
|
||||||
|
onChange={(e) => setMaxFps(e.target.value)}
|
||||||
|
className="w-24 rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={playabilityStatus}
|
||||||
|
onChange={(e) => setPlayabilityStatus(e.target.value)}
|
||||||
|
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm"
|
||||||
|
>
|
||||||
|
<option value="">Any Playability</option>
|
||||||
|
<option value="great">Plays Great</option>
|
||||||
|
<option value="playable">Playable</option>
|
||||||
|
<option value="needs_tweaks">Needs Tweaks</option>
|
||||||
|
<option value="unplayable">Unplayable</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Compatibility Filters */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="text-sm font-medium text-zinc-300">Compatibility</h4>
|
||||||
|
<select
|
||||||
|
value={protonNative}
|
||||||
|
onChange={(e) => setProtonNative(e.target.value)}
|
||||||
|
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm"
|
||||||
|
>
|
||||||
|
<option value="any">Any Runtime</option>
|
||||||
|
<option value="native">Native</option>
|
||||||
|
<option value="proton">Proton</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
value={antiCheatStatus}
|
||||||
|
onChange={(e) => setAntiCheatStatus(e.target.value)}
|
||||||
|
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm"
|
||||||
|
>
|
||||||
|
<option value="any">Any Anti-Cheat</option>
|
||||||
|
<option value="supported">AC Supported</option>
|
||||||
|
<option value="unsupported">AC Unsupported</option>
|
||||||
|
<option value="unknown">AC Unknown</option>
|
||||||
|
</select>
|
||||||
|
<label className="flex items-center gap-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={fsrSupport}
|
||||||
|
onChange={(e) => setFsrSupport(e.target.checked)}
|
||||||
|
className="rounded border-zinc-600"
|
||||||
|
/>
|
||||||
|
FSR Support
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Other Filters */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="text-sm font-medium text-zinc-300">Other</h4>
|
||||||
|
<label className="flex items-center gap-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={isFree}
|
||||||
|
onChange={(e) => setIsFree(e.target.checked)}
|
||||||
|
className="rounded border-zinc-600"
|
||||||
|
/>
|
||||||
|
Free to Play
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={hasMultiplayer}
|
||||||
|
onChange={(e) => setHasMultiplayer(e.target.checked)}
|
||||||
|
className="rounded border-zinc-600"
|
||||||
|
/>
|
||||||
|
Has Multiplayer
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
placeholder="Min Steam Review %"
|
||||||
|
value={steamReviewMin}
|
||||||
|
onChange={(e) => setSteamReviewMin(e.target.value)}
|
||||||
|
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Saved Filters */}
|
||||||
|
<SavedFilters
|
||||||
|
currentFilters={{
|
||||||
|
minFps,
|
||||||
|
maxFps,
|
||||||
|
fsrSupport,
|
||||||
|
protonNative,
|
||||||
|
antiCheatStatus,
|
||||||
|
playabilityStatus,
|
||||||
|
steamReviewMin,
|
||||||
|
isFree,
|
||||||
|
hasMultiplayer,
|
||||||
|
genre: selectedGenres[0] || "",
|
||||||
|
device: selectedDevice,
|
||||||
|
sortBy: sort,
|
||||||
|
}}
|
||||||
|
onLoad={(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 */}
|
{/* Clear filters */}
|
||||||
{(selectedDevice || selectedGenres.length > 0) && (
|
{(selectedDevice ||
|
||||||
|
selectedGenres.length > 0 ||
|
||||||
|
minFps ||
|
||||||
|
maxFps ||
|
||||||
|
fsrSupport ||
|
||||||
|
protonNative !== "any" ||
|
||||||
|
antiCheatStatus !== "any" ||
|
||||||
|
playabilityStatus ||
|
||||||
|
steamReviewMin ||
|
||||||
|
isFree ||
|
||||||
|
hasMultiplayer) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedDevice("")
|
setSelectedDevice("")
|
||||||
setSelectedGenres([])
|
setSelectedGenres([])
|
||||||
|
setMinFps("")
|
||||||
|
setMaxFps("")
|
||||||
|
setFsrSupport(false)
|
||||||
|
setProtonNative("any")
|
||||||
|
setAntiCheatStatus("any")
|
||||||
|
setPlayabilityStatus("")
|
||||||
|
setSteamReviewMin("")
|
||||||
|
setIsFree(false)
|
||||||
|
setHasMultiplayer(false)
|
||||||
}}
|
}}
|
||||||
className="text-xs text-text/50 hover:text-primary transition-colors cursor-pointer self-start"
|
className="text-xs text-text/50 hover:text-primary transition-colors cursor-pointer self-start"
|
||||||
>
|
>
|
||||||
@@ -356,16 +569,47 @@ export function GamesPageClient({
|
|||||||
<div className="flex flex-col items-center justify-center py-20 gap-4">
|
<div className="flex flex-col items-center justify-center py-20 gap-4">
|
||||||
<Gamepad2Icon className="h-12 w-12 text-text/20" />
|
<Gamepad2Icon className="h-12 w-12 text-text/20" />
|
||||||
<p className="text-text/40 text-sm">
|
<p className="text-text/40 text-sm">
|
||||||
{search || selectedDevice || selectedGenres.length > 0
|
{search ||
|
||||||
|
selectedDevice ||
|
||||||
|
selectedGenres.length > 0 ||
|
||||||
|
minFps ||
|
||||||
|
maxFps ||
|
||||||
|
fsrSupport ||
|
||||||
|
protonNative !== "any" ||
|
||||||
|
antiCheatStatus !== "any" ||
|
||||||
|
playabilityStatus ||
|
||||||
|
steamReviewMin ||
|
||||||
|
isFree ||
|
||||||
|
hasMultiplayer
|
||||||
? "No games match your filters"
|
? "No games match your filters"
|
||||||
: "No games found"}
|
: "No games found"}
|
||||||
</p>
|
</p>
|
||||||
{(search || selectedDevice || selectedGenres.length > 0) && (
|
{(search ||
|
||||||
|
selectedDevice ||
|
||||||
|
selectedGenres.length > 0 ||
|
||||||
|
minFps ||
|
||||||
|
maxFps ||
|
||||||
|
fsrSupport ||
|
||||||
|
protonNative !== "any" ||
|
||||||
|
antiCheatStatus !== "any" ||
|
||||||
|
playabilityStatus ||
|
||||||
|
steamReviewMin ||
|
||||||
|
isFree ||
|
||||||
|
hasMultiplayer) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSearch("")
|
setSearch("")
|
||||||
setSelectedDevice("")
|
setSelectedDevice("")
|
||||||
setSelectedGenres([])
|
setSelectedGenres([])
|
||||||
|
setMinFps("")
|
||||||
|
setMaxFps("")
|
||||||
|
setFsrSupport(false)
|
||||||
|
setProtonNative("any")
|
||||||
|
setAntiCheatStatus("any")
|
||||||
|
setPlayabilityStatus("")
|
||||||
|
setSteamReviewMin("")
|
||||||
|
setIsFree(false)
|
||||||
|
setHasMultiplayer(false)
|
||||||
}}
|
}}
|
||||||
className="text-xs text-primary hover:text-primary/80 transition-colors cursor-pointer"
|
className="text-xs text-primary hover:text-primary/80 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
@@ -431,6 +675,18 @@ function GameCard({ game }: { game: GamesListItem }) {
|
|||||||
<h3 className="text-xs sm:text-sm font-semibold text-text group-hover:text-primary transition-colors line-clamp-2 leading-tight">
|
<h3 className="text-xs sm:text-sm font-semibold text-text group-hover:text-primary transition-colors line-clamp-2 leading-tight">
|
||||||
{game.title}
|
{game.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
<div className="flex flex-wrap gap-1 mt-1">
|
||||||
|
{game.playabilityStatus && (
|
||||||
|
<PlayabilityBadge status={game.playabilityStatus} compact showLabel={false} />
|
||||||
|
)}
|
||||||
|
{game.antiCheatRelevant && game.antiCheatStatus === "unsupported" && (
|
||||||
|
<AntiCheatBadge
|
||||||
|
antiCheatRelevant={true}
|
||||||
|
antiCheatStatus={game.antiCheatStatus}
|
||||||
|
compact
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="mt-1.5 flex items-center gap-2 flex-wrap">
|
<div className="mt-1.5 flex items-center gap-2 flex-wrap">
|
||||||
{game.benchmarkCount > 0 ? (
|
{game.benchmarkCount > 0 ? (
|
||||||
<span className="inline-flex items-center gap-1 text-[10px] text-text/50">
|
<span className="inline-flex items-center gap-1 text-[10px] text-text/50">
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ export default async function GamesPage() {
|
|||||||
genres: games.genres,
|
genres: games.genres,
|
||||||
source: games.source,
|
source: games.source,
|
||||||
createdAt: games.createdAt,
|
createdAt: games.createdAt,
|
||||||
|
steamReviewScore: games.steamReviewScore,
|
||||||
|
playabilityStatus: games.playabilityStatus,
|
||||||
|
onlineMultiplayerStatus: games.onlineMultiplayerStatus,
|
||||||
})
|
})
|
||||||
.from(games)
|
.from(games)
|
||||||
.orderBy(desc(games.createdAt))
|
.orderBy(desc(games.createdAt))
|
||||||
@@ -90,18 +93,31 @@ export default async function GamesPage() {
|
|||||||
gameId: gamePlatformSupport.gameId,
|
gameId: gamePlatformSupport.gameId,
|
||||||
hardwareSlug: gamePlatformSupport.hardwareSlug,
|
hardwareSlug: gamePlatformSupport.hardwareSlug,
|
||||||
protonStatus: gamePlatformSupport.protonStatus,
|
protonStatus: gamePlatformSupport.protonStatus,
|
||||||
|
antiCheatRelevant: gamePlatformSupport.antiCheatRelevant,
|
||||||
|
antiCheatStatus: gamePlatformSupport.antiCheatStatus,
|
||||||
})
|
})
|
||||||
.from(gamePlatformSupport)
|
.from(gamePlatformSupport)
|
||||||
.where(inArray(gamePlatformSupport.gameId, gameIds))
|
.where(inArray(gamePlatformSupport.gameId, gameIds))
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const platformMap = new Map<string, string>()
|
const platformMap = new Map<string, string>()
|
||||||
|
const antiCheatMap = new Map<string, { antiCheatRelevant: boolean; antiCheatStatus: "none" | "supported" | "unsupported" | "unknown" | null }>()
|
||||||
for (const row of platformRows) {
|
for (const row of platformRows) {
|
||||||
const isSteamDeck = row.hardwareSlug.startsWith("steamdeck")
|
const isSteamDeck = row.hardwareSlug.startsWith("steamdeck")
|
||||||
const existing = platformMap.get(row.gameId)
|
const existing = platformMap.get(row.gameId)
|
||||||
if (!existing || (!existing.startsWith("steamdeck") && isSteamDeck)) {
|
if (!existing || (!existing.startsWith("steamdeck") && isSteamDeck)) {
|
||||||
platformMap.set(row.gameId, row.protonStatus)
|
platformMap.set(row.gameId, row.protonStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const existingAc = antiCheatMap.get(row.gameId)
|
||||||
|
if (row.antiCheatRelevant) {
|
||||||
|
if (!existingAc || (!existingAc.antiCheatRelevant && isSteamDeck) || (!existingAc.antiCheatRelevant)) {
|
||||||
|
antiCheatMap.set(row.gameId, {
|
||||||
|
antiCheatRelevant: row.antiCheatRelevant,
|
||||||
|
antiCheatStatus: row.antiCheatStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all genres
|
// Fetch all genres
|
||||||
@@ -135,8 +151,13 @@ export default async function GamesPage() {
|
|||||||
headerImage: g.headerImage,
|
headerImage: g.headerImage,
|
||||||
genres: g.genres,
|
genres: g.genres,
|
||||||
source: g.source,
|
source: g.source,
|
||||||
|
steamReviewScore: g.steamReviewScore,
|
||||||
|
playabilityStatus: g.playabilityStatus,
|
||||||
|
onlineMultiplayerStatus: g.onlineMultiplayerStatus,
|
||||||
benchmarkCount: benchmarkMap.get(g.id) ?? 0,
|
benchmarkCount: benchmarkMap.get(g.id) ?? 0,
|
||||||
deckStatus: platformMap.get(g.id) ?? null,
|
deckStatus: platformMap.get(g.id) ?? null,
|
||||||
|
antiCheatRelevant: antiCheatMap.get(g.id)?.antiCheatRelevant ?? false,
|
||||||
|
antiCheatStatus: antiCheatMap.get(g.id)?.antiCheatStatus ?? null,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const allGenres = Array.from(genreSet).sort()
|
const allGenres = Array.from(genreSet).sort()
|
||||||
|
|||||||
@@ -313,12 +313,15 @@ export const gamesListingRoutes = new Elysia({ prefix: "/games/listing" }).get(
|
|||||||
|
|
||||||
// Fetch platform support for the returned games (prioritise Steam Deck)
|
// Fetch platform support for the returned games (prioritise Steam Deck)
|
||||||
const platformMap = new Map<string, string>()
|
const platformMap = new Map<string, string>()
|
||||||
|
const antiCheatMap = new Map<string, { antiCheatRelevant: boolean; antiCheatStatus: string | null }>()
|
||||||
if (gameIds.length > 0) {
|
if (gameIds.length > 0) {
|
||||||
const platformRows = await db
|
const platformRows = await db
|
||||||
.select({
|
.select({
|
||||||
gameId: gamePlatformSupport.gameId,
|
gameId: gamePlatformSupport.gameId,
|
||||||
hardwareSlug: gamePlatformSupport.hardwareSlug,
|
hardwareSlug: gamePlatformSupport.hardwareSlug,
|
||||||
protonStatus: gamePlatformSupport.protonStatus,
|
protonStatus: gamePlatformSupport.protonStatus,
|
||||||
|
antiCheatRelevant: gamePlatformSupport.antiCheatRelevant,
|
||||||
|
antiCheatStatus: gamePlatformSupport.antiCheatStatus,
|
||||||
})
|
})
|
||||||
.from(gamePlatformSupport)
|
.from(gamePlatformSupport)
|
||||||
.where(inArray(gamePlatformSupport.gameId, gameIds))
|
.where(inArray(gamePlatformSupport.gameId, gameIds))
|
||||||
@@ -329,6 +332,16 @@ export const gamesListingRoutes = new Elysia({ prefix: "/games/listing" }).get(
|
|||||||
if (!existing || (!existing.startsWith("steamdeck") && isSteamDeck)) {
|
if (!existing || (!existing.startsWith("steamdeck") && isSteamDeck)) {
|
||||||
platformMap.set(row.gameId, row.protonStatus)
|
platformMap.set(row.gameId, row.protonStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const existingAc = antiCheatMap.get(row.gameId)
|
||||||
|
if (row.antiCheatRelevant) {
|
||||||
|
if (!existingAc || (!existingAc.antiCheatRelevant && isSteamDeck) || (!existingAc.antiCheatRelevant)) {
|
||||||
|
antiCheatMap.set(row.gameId, {
|
||||||
|
antiCheatRelevant: row.antiCheatRelevant,
|
||||||
|
antiCheatStatus: row.antiCheatStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +362,8 @@ export const gamesListingRoutes = new Elysia({ prefix: "/games/listing" }).get(
|
|||||||
onlineMultiplayerStatus: g.onlineMultiplayerStatus,
|
onlineMultiplayerStatus: g.onlineMultiplayerStatus,
|
||||||
benchmarkCount: benchmarkMap.get(g.id) ?? 0,
|
benchmarkCount: benchmarkMap.get(g.id) ?? 0,
|
||||||
deckStatus: platformMap.get(g.id) ?? null,
|
deckStatus: platformMap.get(g.id) ?? null,
|
||||||
|
antiCheatRelevant: antiCheatMap.get(g.id)?.antiCheatRelevant ?? false,
|
||||||
|
antiCheatStatus: antiCheatMap.get(g.id)?.antiCheatStatus ?? null,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// If sorting by benchmarks, re-sort the enriched data
|
// If sorting by benchmarks, re-sort the enriched data
|
||||||
|
|||||||
Reference in New Issue
Block a user