feat: rearrange game benchmark wizard and improve overall
This commit is contained in:
@@ -326,7 +326,7 @@ export function GamesClient() {
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/games?limit=${LIMIT}&offset=${offset}&search=${encodeURIComponent(search)}`
|
||||
`/api/games?limit=${LIMIT}&offset=${offset}&search=${encodeURIComponent(search)}&sort=createdAt&order=desc`
|
||||
)
|
||||
if (res.ok && !cancelled) {
|
||||
const json = (await res.json()) as GamesApiResponse
|
||||
|
||||
@@ -79,6 +79,7 @@ interface Game {
|
||||
playabilityStatus?: "great" | "playable" | "needs_tweaks" | "unplayable" | "unknown" | null
|
||||
steamReviewScore?: number | null
|
||||
steamReviewSentiment?: string | null
|
||||
steamReviewCount?: number | null
|
||||
}
|
||||
|
||||
interface Counts {
|
||||
|
||||
+15
-3
@@ -254,16 +254,25 @@ export default async function GamePage({
|
||||
.orderBy(desc(performanceEntries.isPinned), desc(performanceEntries.upvotes)),
|
||||
])
|
||||
|
||||
// ── Sync logic: force or stale-while-revalidate ─────────────────
|
||||
// ── Sync logic: force or stale-while-revalidate ────────────────
|
||||
const shouldSync =
|
||||
game.source === "steam" &&
|
||||
game.steamAppId &&
|
||||
(forceSync || isSyncStale(game.lastSync))
|
||||
|
||||
if (shouldSync) {
|
||||
after(async () => {
|
||||
if (forceSync) {
|
||||
// Block render on forced sync so user sees fresh data immediately
|
||||
await syncSteamGame(game.steamAppId!)
|
||||
})
|
||||
// Re-fetch game after sync so serialized data is fresh
|
||||
const refreshed = await resolveGame(game.steamAppId!.toString())
|
||||
if (refreshed) game = refreshed
|
||||
} else {
|
||||
// Stale sync happens after response so page isn't delayed
|
||||
after(async () => {
|
||||
await syncSteamGame(game.steamAppId!)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize for client component (Dates → strings)
|
||||
@@ -293,6 +302,9 @@ export default async function GamePage({
|
||||
releaseDate: game.releaseDate,
|
||||
categories: game.categories,
|
||||
platforms: game.platforms,
|
||||
steamReviewScore: game.steamReviewScore,
|
||||
steamReviewSentiment: game.steamReviewSentiment,
|
||||
steamReviewCount: game.steamReviewCount,
|
||||
}
|
||||
|
||||
const serializedPresets = presetRows.map((p) => ({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { notFound } from "next/navigation"
|
||||
import { db } from "@/lib/db/index"
|
||||
import { games, gameVersions, performanceEntries, gamePlatformSupport } from "@/lib/db/schema"
|
||||
import { eq, sql } from "drizzle-orm"
|
||||
import { GameEntryWizard } from "@/components/wizard/game-entry-wizard"
|
||||
import { GameEntryWizard, type GameVersionInfo } from "@/components/wizard/game-entry-wizard"
|
||||
|
||||
// This page needs live data — skip static generation at build time
|
||||
export const dynamic = "force-dynamic"
|
||||
@@ -47,37 +47,34 @@ export default async function SubmitBenchmarkPage({
|
||||
notFound()
|
||||
}
|
||||
|
||||
// Get or create the latest game version
|
||||
let [version] = await db
|
||||
.select()
|
||||
// Fetch all game versions for version selection
|
||||
const allVersions = await db
|
||||
.select({
|
||||
id: gameVersions.id,
|
||||
versionString: gameVersions.versionString,
|
||||
buildId: gameVersions.buildId,
|
||||
isLatest: gameVersions.isLatest,
|
||||
})
|
||||
.from(gameVersions)
|
||||
.where(
|
||||
eq(gameVersions.gameId, game.id),
|
||||
)
|
||||
.where(eq(gameVersions.gameId, game.id))
|
||||
.orderBy(sql`${gameVersions.createdAt} DESC`)
|
||||
.limit(1)
|
||||
|
||||
// Create a default version if none exists
|
||||
if (!version) {
|
||||
[version] = await db
|
||||
if (allVersions.length === 0) {
|
||||
const [newVersion] = await db
|
||||
.insert(gameVersions)
|
||||
.values({
|
||||
gameId: game.id,
|
||||
isLatest: true,
|
||||
})
|
||||
.returning()
|
||||
}
|
||||
|
||||
// Fetch platform support for anti-cheat awareness
|
||||
const platformSupport = await db
|
||||
.select({
|
||||
hardwareSlug: gamePlatformSupport.hardwareSlug,
|
||||
antiCheatRelevant: gamePlatformSupport.antiCheatRelevant,
|
||||
antiCheatName: gamePlatformSupport.antiCheatName,
|
||||
antiCheatStatus: gamePlatformSupport.antiCheatStatus,
|
||||
allVersions.push({
|
||||
id: newVersion.id,
|
||||
versionString: newVersion.versionString,
|
||||
buildId: newVersion.buildId,
|
||||
isLatest: newVersion.isLatest,
|
||||
})
|
||||
.from(gamePlatformSupport)
|
||||
.where(eq(gamePlatformSupport.gameId, game.id))
|
||||
}
|
||||
|
||||
// If editing, fetch the existing performance entry
|
||||
let editEntry = null
|
||||
@@ -90,6 +87,26 @@ export default async function SubmitBenchmarkPage({
|
||||
editEntry = entry ?? null
|
||||
}
|
||||
|
||||
// Determine default version: when editing, use the entry's version;
|
||||
// otherwise, use the latest (first in DESC order)
|
||||
let defaultVersionId = allVersions[0].id
|
||||
if (editEntry?.versionId) {
|
||||
defaultVersionId = editEntry.versionId
|
||||
}
|
||||
|
||||
const gameVersionInfos: GameVersionInfo[] = allVersions
|
||||
|
||||
// Fetch platform support for anti-cheat awareness
|
||||
const platformSupport = await db
|
||||
.select({
|
||||
hardwareSlug: gamePlatformSupport.hardwareSlug,
|
||||
antiCheatRelevant: gamePlatformSupport.antiCheatRelevant,
|
||||
antiCheatName: gamePlatformSupport.antiCheatName,
|
||||
antiCheatStatus: gamePlatformSupport.antiCheatStatus,
|
||||
})
|
||||
.from(gamePlatformSupport)
|
||||
.where(eq(gamePlatformSupport.gameId, game.id))
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 py-8 w-full">
|
||||
<div className="mb-8">
|
||||
@@ -104,7 +121,8 @@ export default async function SubmitBenchmarkPage({
|
||||
|
||||
<GameEntryWizard
|
||||
gameId={game.id}
|
||||
gameVersionId={version.id}
|
||||
gameVersions={gameVersionInfos}
|
||||
defaultVersionId={defaultVersionId}
|
||||
editEntry={editEntry}
|
||||
platformSupport={platformSupport}
|
||||
/>
|
||||
|
||||
@@ -677,7 +677,7 @@ function GameCard({ game }: { game: GamesListItem }) {
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{game.playabilityStatus && (
|
||||
<PlayabilityBadge status={game.playabilityStatus} compact showLabel={false} />
|
||||
<PlayabilityBadge status={game.playabilityStatus} compact />
|
||||
)}
|
||||
{game.antiCheatRelevant && game.antiCheatStatus === "unsupported" && (
|
||||
<AntiCheatBadge
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Metadata } from "next"
|
||||
import { after } from "next/server"
|
||||
import { db } from "@/lib/db/index"
|
||||
import {
|
||||
games,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
hardware,
|
||||
} from "@/lib/db/schema"
|
||||
import { sql, eq, and, desc, inArray } from "drizzle-orm"
|
||||
import { isSyncStale, syncSteamGame } from "@/lib/steam/sync"
|
||||
import { GamesPageClient } from "./games-page-client"
|
||||
|
||||
// This page needs live data — skip static generation at build time
|
||||
@@ -51,6 +53,7 @@ export default async function GamesPage() {
|
||||
steamReviewScore: games.steamReviewScore,
|
||||
playabilityStatus: games.playabilityStatus,
|
||||
onlineMultiplayerStatus: games.onlineMultiplayerStatus,
|
||||
lastSync: games.lastSync,
|
||||
})
|
||||
.from(games)
|
||||
.orderBy(desc(games.createdAt))
|
||||
@@ -163,6 +166,25 @@ export default async function GamesPage() {
|
||||
const allGenres = Array.from(genreSet).sort()
|
||||
const allDevices = deviceRows
|
||||
|
||||
// ── Background sync for stale games ────────────────────────────────
|
||||
const staleSteamAppIds = gamesData
|
||||
.filter((g) => g.source === "steam" && g.steamAppId && isSyncStale(g.lastSync))
|
||||
.map((g) => g.steamAppId!)
|
||||
|
||||
if (staleSteamAppIds.length > 0) {
|
||||
after(async () => {
|
||||
// Sync stale games sequentially with a small delay to avoid rate-limiting
|
||||
for (const appId of staleSteamAppIds) {
|
||||
try {
|
||||
await syncSteamGame(appId)
|
||||
} catch {
|
||||
// Stale sync failure is non-fatal — data will be refreshed on next visit
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 1500))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// JSON-LD ItemList
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
|
||||
+1
-1
@@ -245,7 +245,7 @@ function SearchResultCard({
|
||||
transition={{ duration: 0.3, delay: index * 0.02 }}
|
||||
whileHover={{ scale: 1.005 }}
|
||||
whileTap={{ scale: 0.995 }}
|
||||
className="group relative bg-text/[0.03] border border-border rounded-xl p-4 sm:p-5 transition-colors duration-200 hover:border-text/30 hover:bg-text/[0.06] cursor-pointer focus-within:outline-none focus-within:ring-2 focus-within:ring-text/20 focus-within:ring-offset-2 focus-within:ring-offset-background"
|
||||
className="group relative bg-text/3 border border-border rounded-xl p-4 sm:p-5 transition-colors duration-200 hover:border-text/30 hover:bg-text/[0.06] cursor-pointer focus-within:outline-none focus-within:ring-2 focus-within:ring-text/20 focus-within:ring-offset-2 focus-within:ring-offset-background"
|
||||
title={isLocal
|
||||
? "Click to view game details, benchmarks, and settings"
|
||||
: "Click to add this game to DeckyVault and view its page"
|
||||
|
||||
Reference in New Issue
Block a user