feat: rearrange game benchmark wizard and improve overall
This commit is contained in:
@@ -4,8 +4,8 @@ import { useState, useCallback, useEffect } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
import { StepIndicator } from "@/components/wizard/step-indicator"
|
||||
import { HardwareStep } from "@/components/wizard/steps/hardware-step"
|
||||
import { AntiCheatStep } from "@/components/wizard/steps/anti-cheat-step"
|
||||
import { SetupStep, type GameVersionInfo } from "@/components/wizard/steps/setup-step"
|
||||
import { AntiCheatStep, type AntiCheatData } from "@/components/wizard/steps/anti-cheat-step"
|
||||
import { PerformanceStep, type PerformanceData } from "@/components/wizard/steps/performance-step"
|
||||
import { SettingsStep } from "@/components/wizard/steps/settings-step"
|
||||
import { EnvironmentStep, type EnvironmentData } from "@/components/wizard/steps/environment-step"
|
||||
@@ -13,9 +13,11 @@ import { ReviewStep } from "@/components/wizard/steps/review-step"
|
||||
import type { SettingCategory } from "@/components/wizard/settings-editor"
|
||||
import { performanceEntries } from "@/lib/db/schema"
|
||||
|
||||
// Export GameVersionInfo so the server page can use it
|
||||
export type { GameVersionInfo }
|
||||
|
||||
const STEPS = [
|
||||
{ label: "Hardware", tooltip: "Choose the hardware you tested this game on" },
|
||||
{ label: "Anti-Cheat", tooltip: "Review anti-cheat compatibility for your selected hardware" },
|
||||
{ label: "Setup", tooltip: "Choose the hardware, game version, and anti-cheat status" },
|
||||
{ label: "Performance", tooltip: "Enter the performance metrics you observed. FPS Average is required." },
|
||||
{ label: "Settings", tooltip: "Configure the game settings you used. Add categories and settings to help others replicate your setup." },
|
||||
{ label: "Environment", tooltip: "Specify the software environment and any launch options used" },
|
||||
@@ -25,30 +27,63 @@ const STEPS = [
|
||||
interface PlatformSupportEntry {
|
||||
hardwareSlug: string
|
||||
antiCheatRelevant: boolean
|
||||
antiCheatStatus: "none" | "supported" | "unsupported" | "unknown"
|
||||
antiCheatName: string | null
|
||||
antiCheatStatus: "none" | "supported" | "unsupported" | "unknown"
|
||||
}
|
||||
|
||||
interface GameEntryWizardProps {
|
||||
gameId: string
|
||||
gameVersionId: string
|
||||
gameVersions: GameVersionInfo[]
|
||||
defaultVersionId: string
|
||||
editEntry?: typeof performanceEntries.$inferSelect | null
|
||||
platformSupport: PlatformSupportEntry[]
|
||||
}
|
||||
|
||||
export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupport }: GameEntryWizardProps) {
|
||||
export function GameEntryWizard({ gameId, gameVersions, defaultVersionId, editEntry, platformSupport }: GameEntryWizardProps) {
|
||||
const router = useRouter()
|
||||
const [currentStep, setCurrentStep] = useState(0)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [success, setSuccess] = useState(false)
|
||||
|
||||
// Step 1: Hardware
|
||||
// Step 0: Setup — Hardware
|
||||
const [hardwareSlug, setHardwareSlug] = useState(editEntry?.hardwareSlug ?? "")
|
||||
const [hardwareName, setHardwareName] = useState("")
|
||||
|
||||
// Step 2: Anti-Cheat (informational)
|
||||
// Step 3: Performance
|
||||
// Step 0: Setup — Game Version
|
||||
const [selectedVersionId, setSelectedVersionId] = useState(defaultVersionId)
|
||||
const [newVersionString, setNewVersionString] = useState("")
|
||||
const [isCreatingVersion, setIsCreatingVersion] = useState(false)
|
||||
|
||||
// Step 0: Setup — Anti-Cheat
|
||||
const [antiCheat, setAntiCheat] = useState<AntiCheatData>({
|
||||
antiCheatRelevant: false,
|
||||
antiCheatName: "",
|
||||
antiCheatStatus: "none",
|
||||
})
|
||||
|
||||
// Initialize anti-cheat from existing platformSupport when editing
|
||||
useEffect(() => {
|
||||
const entry = platformSupport.find(
|
||||
(p) => p.hardwareSlug === hardwareSlug && p.antiCheatRelevant
|
||||
) ?? platformSupport.find((p) => p.antiCheatRelevant)
|
||||
|
||||
if (entry) {
|
||||
setAntiCheat({
|
||||
antiCheatRelevant: entry.antiCheatRelevant,
|
||||
antiCheatName: entry.antiCheatName ?? "",
|
||||
antiCheatStatus: entry.antiCheatStatus,
|
||||
})
|
||||
} else {
|
||||
setAntiCheat({
|
||||
antiCheatRelevant: false,
|
||||
antiCheatName: "",
|
||||
antiCheatStatus: "none",
|
||||
})
|
||||
}
|
||||
}, [hardwareSlug, platformSupport])
|
||||
|
||||
// Step 1: Performance
|
||||
const [performance, setPerformance] = useState<PerformanceData>(
|
||||
editEntry
|
||||
? {
|
||||
@@ -62,12 +97,12 @@ export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupp
|
||||
: {},
|
||||
)
|
||||
|
||||
// Step 4: Settings
|
||||
// Step 2: Settings
|
||||
const [settingsJson, setSettingsJson] = useState<SettingCategory[]>(
|
||||
editEntry?.settingsJson ?? [],
|
||||
)
|
||||
|
||||
// Step 5: Environment
|
||||
// Step 3: Environment
|
||||
const [environment, setEnvironment] = useState<EnvironmentData>(
|
||||
editEntry
|
||||
? {
|
||||
@@ -86,7 +121,7 @@ export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupp
|
||||
},
|
||||
)
|
||||
|
||||
// Step 6: Notes
|
||||
// Step 4: Notes
|
||||
const [userNotes, setUserNotes] = useState(editEntry?.userNotes ?? "")
|
||||
|
||||
// Fetch hardware name when slug changes
|
||||
@@ -128,19 +163,30 @@ export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupp
|
||||
return () => { cancelled = true }
|
||||
}, [editEntry, hardwareSlug])
|
||||
|
||||
// Resolve the version label for display
|
||||
const getVersionLabel = useCallback(() => {
|
||||
if (selectedVersionId === "__new__") {
|
||||
return newVersionString || "New version"
|
||||
}
|
||||
const v = gameVersions.find((v) => v.id === selectedVersionId)
|
||||
if (!v) return "Unknown"
|
||||
return v.versionString || (v.buildId ? `Build ${v.buildId}` : "Unknown version")
|
||||
}, [selectedVersionId, newVersionString, gameVersions])
|
||||
|
||||
const canProceed = () => {
|
||||
switch (currentStep) {
|
||||
case 0:
|
||||
return hardwareSlug !== ""
|
||||
case 1:
|
||||
return true // Anti-cheat is informational
|
||||
case 2:
|
||||
case 0: // Setup
|
||||
if (hardwareSlug === "") return false
|
||||
// If new version selected, require version string
|
||||
if (selectedVersionId === "__new__" && !newVersionString.trim()) return false
|
||||
return true
|
||||
case 1: // Performance
|
||||
return performance.fpsAvg !== undefined && performance.fpsAvg > 0
|
||||
case 3:
|
||||
return true // Settings are optional
|
||||
case 4:
|
||||
return true // Environment is optional
|
||||
case 5:
|
||||
case 2: // Settings
|
||||
return true
|
||||
case 3: // Environment
|
||||
return true
|
||||
case 4: // Review
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -165,13 +211,46 @@ export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupp
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the final version ID — create a new version if needed
|
||||
const resolveVersionId = async (): Promise<string> => {
|
||||
if (selectedVersionId !== "__new__") {
|
||||
return selectedVersionId
|
||||
}
|
||||
|
||||
// Create a new version via API
|
||||
setIsCreatingVersion(true)
|
||||
try {
|
||||
const res = await fetch(`/api/games/${gameId}/versions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
versionString: newVersionString.trim(),
|
||||
isLatest: false,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json()
|
||||
throw new Error(data.error || "Failed to create game version")
|
||||
}
|
||||
|
||||
const data = await res.json() as { data: { id: string } }
|
||||
return data.data.id
|
||||
} finally {
|
||||
setIsCreatingVersion(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setIsSubmitting(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
// Resolve version ID (may create a new version)
|
||||
const versionId = await resolveVersionId()
|
||||
|
||||
const payload = {
|
||||
versionId: gameVersionId,
|
||||
versionId,
|
||||
hardwareSlug,
|
||||
fpsAvg: Number(performance.fpsAvg),
|
||||
fpsOnePercentLow: performance.fpsOnePercentLow !== undefined ? Number(performance.fpsOnePercentLow) : null,
|
||||
@@ -189,6 +268,9 @@ export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupp
|
||||
customSystem: environment.customSystem ?? false,
|
||||
settingsJson: settingsJson.length > 0 ? settingsJson : null,
|
||||
userNotes: userNotes || null,
|
||||
antiCheatRelevant: antiCheat.antiCheatRelevant,
|
||||
antiCheatName: antiCheat.antiCheatName || null,
|
||||
antiCheatStatus: antiCheat.antiCheatStatus,
|
||||
}
|
||||
|
||||
const url = editEntry
|
||||
@@ -260,25 +342,38 @@ export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupp
|
||||
className="min-h-[300px]"
|
||||
>
|
||||
{currentStep === 0 && (
|
||||
<HardwareStep value={hardwareSlug} onChange={handleHardwareChange} />
|
||||
<SetupStep
|
||||
gameId={gameId}
|
||||
gameVersions={gameVersions}
|
||||
hardwareSlug={hardwareSlug}
|
||||
onHardwareChange={handleHardwareChange}
|
||||
hardwareName={hardwareName}
|
||||
selectedVersionId={selectedVersionId}
|
||||
onVersionChange={setSelectedVersionId}
|
||||
newVersionString={newVersionString}
|
||||
onNewVersionStringChange={setNewVersionString}
|
||||
isCreatingVersion={isCreatingVersion}
|
||||
antiCheat={antiCheat}
|
||||
onAntiCheatChange={setAntiCheat}
|
||||
platformSupport={platformSupport}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 1 && (
|
||||
<AntiCheatStep hardwareSlug={hardwareSlug} platformSupport={platformSupport} />
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<PerformanceStep value={performance} onChange={setPerformance} />
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
{currentStep === 2 && (
|
||||
<SettingsStep value={settingsJson} onChange={setSettingsJson} />
|
||||
)}
|
||||
{currentStep === 4 && (
|
||||
{currentStep === 3 && (
|
||||
<EnvironmentStep value={environment} onChange={setEnvironment} />
|
||||
)}
|
||||
{currentStep === 5 && (
|
||||
{currentStep === 4 && (
|
||||
<ReviewStep
|
||||
data={{
|
||||
hardwareSlug,
|
||||
hardwareName,
|
||||
gameVersionLabel: getVersionLabel(),
|
||||
antiCheat,
|
||||
performance,
|
||||
settings: settingsJson,
|
||||
environment,
|
||||
@@ -294,7 +389,7 @@ export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupp
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
{currentStep < 5 && (
|
||||
{currentStep < 4 && (
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
type="button"
|
||||
@@ -316,4 +411,4 @@ export function GameEntryWizard({ gameId, gameVersionId, editEntry, platformSupp
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
"use client"
|
||||
|
||||
import { Shield, ShieldCheck, ShieldX, ShieldQuestion } from "lucide-react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { Shield, ShieldCheck, ShieldX, ShieldQuestion, Pencil } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export type AntiCheatData = {
|
||||
antiCheatRelevant: boolean
|
||||
antiCheatName: string
|
||||
antiCheatStatus: "none" | "supported" | "unsupported" | "unknown"
|
||||
}
|
||||
|
||||
interface PlatformSupportEntry {
|
||||
hardwareSlug: string
|
||||
antiCheatRelevant: boolean
|
||||
@@ -13,6 +20,8 @@ interface PlatformSupportEntry {
|
||||
interface AntiCheatStepProps {
|
||||
hardwareSlug: string
|
||||
platformSupport: PlatformSupportEntry[]
|
||||
value: AntiCheatData
|
||||
onChange: (data: AntiCheatData) => void
|
||||
}
|
||||
|
||||
const statusConfig = {
|
||||
@@ -21,94 +30,198 @@ const statusConfig = {
|
||||
label: "Supported",
|
||||
color: "border-green-500/30 bg-green-500/10",
|
||||
textColor: "text-green-400",
|
||||
message:
|
||||
"This game's anti-cheat supports Linux/SteamOS. Multiplayer should work.",
|
||||
message: "Anti-cheat works on Linux/SteamOS. Multiplayer should work.",
|
||||
},
|
||||
unsupported: {
|
||||
icon: ShieldX,
|
||||
label: "Unsupported",
|
||||
color: "border-red-500/30 bg-red-500/10",
|
||||
textColor: "text-red-400",
|
||||
message:
|
||||
"This game's anti-cheat does not support Linux/SteamOS. Multiplayer may not work.",
|
||||
message: "Anti-cheat does not support Linux/SteamOS. Multiplayer may not work.",
|
||||
},
|
||||
unknown: {
|
||||
icon: ShieldQuestion,
|
||||
label: "Unknown",
|
||||
color: "border-yellow-500/30 bg-yellow-500/10",
|
||||
textColor: "text-yellow-400",
|
||||
message:
|
||||
"Anti-cheat compatibility is unknown. Multiplayer may or may not work.",
|
||||
message: "Compatibility is unknown. Multiplayer may or may not work.",
|
||||
},
|
||||
none: {
|
||||
icon: Shield,
|
||||
label: "None",
|
||||
color: "border-zinc-500/30 bg-zinc-500/10",
|
||||
textColor: "text-zinc-400",
|
||||
message: "No anti-cheat detected for this game.",
|
||||
message: "No anti-cheat detected.",
|
||||
},
|
||||
} as const
|
||||
|
||||
export function AntiCheatStep({
|
||||
hardwareSlug,
|
||||
platformSupport,
|
||||
value,
|
||||
onChange,
|
||||
}: AntiCheatStepProps) {
|
||||
// Anti-cheat is a game-level property, not device-specific.
|
||||
// Find the first entry with anti-cheat info (any device).
|
||||
const antiCheatEntry = platformSupport.find((p) => p.antiCheatRelevant)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
|
||||
if (!antiCheatEntry) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<Shield className="h-4 w-4 text-text mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-text">
|
||||
Anti-Cheat Status
|
||||
</h3>
|
||||
<p className="text-xs text-text/60 mt-1">
|
||||
Check the anti-cheat compatibility before submitting.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
// Find the best existing entry to prefill:
|
||||
// Prefer the entry for the currently selected hardware,
|
||||
// otherwise fall back to any entry with anti-cheat data.
|
||||
const hardwareEntry = platformSupport.find(
|
||||
(p) => p.hardwareSlug === hardwareSlug && p.antiCheatRelevant
|
||||
)
|
||||
const anyEntry = platformSupport.find((p) => p.antiCheatRelevant)
|
||||
const existingEntry = hardwareEntry ?? anyEntry
|
||||
|
||||
<div className="rounded-lg border border-zinc-500/30 bg-zinc-500/10 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="h-5 w-5 text-zinc-400" />
|
||||
<h4 className="font-medium">No Anti-Cheat</h4>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-zinc-400">
|
||||
This game does not use anti-cheat software. Multiplayer (if
|
||||
available) should work without issues.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
// Prefill once when the component mounts if the current value is the default
|
||||
useEffect(() => {
|
||||
if (existingEntry && !isEditing) {
|
||||
onChange({
|
||||
antiCheatRelevant: existingEntry.antiCheatRelevant,
|
||||
antiCheatName: existingEntry.antiCheatName ?? "",
|
||||
antiCheatStatus: existingEntry.antiCheatStatus,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hardwareSlug]) // re-prefill when hardware changes
|
||||
|
||||
const config = statusConfig[antiCheatEntry.antiCheatStatus]
|
||||
const Icon = config.icon
|
||||
const relevant = value.antiCheatRelevant
|
||||
const currentConfig = statusConfig[value.antiCheatStatus]
|
||||
const CurrentIcon = currentConfig.icon
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<Shield className="h-4 w-4 text-primary mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-text">Anti-Cheat Status</h3>
|
||||
<h3 className="text-sm font-semibold text-text">Anti-Cheat</h3>
|
||||
<p className="text-xs text-text/60 mt-1">
|
||||
Check the anti-cheat compatibility before submitting.
|
||||
Set the anti-cheat status for this game. This helps others know if multiplayer will work.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("rounded-lg border p-4", config.color)}>
|
||||
{/* Toggle: Has anti-cheat? */}
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onChange({
|
||||
...value,
|
||||
antiCheatRelevant: !relevant,
|
||||
antiCheatStatus: !relevant ? "unknown" : "none",
|
||||
antiCheatName: !relevant ? value.antiCheatName : "",
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-6 w-11 items-center rounded-full transition-colors",
|
||||
relevant ? "bg-primary" : "bg-text/20"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-4 w-4 transform rounded-full bg-white transition-transform",
|
||||
relevant ? "translate-x-6" : "translate-x-1"
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm text-text">Game uses anti-cheat</span>
|
||||
<span className="text-xs text-text/50">
|
||||
{relevant
|
||||
? "Yes — select the anti-cheat name and compatibility below"
|
||||
: "No anti-cheat software detected in this game"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{relevant && (
|
||||
<div className="space-y-4">
|
||||
{/* Anti-cheat name */}
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-text/70 mb-1.5">
|
||||
Anti-Cheat Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={value.antiCheatName}
|
||||
onChange={(e) =>
|
||||
onChange({ ...value, antiCheatName: e.target.value })
|
||||
}
|
||||
placeholder="e.g. Easy Anti-Cheat, BattlEye, Ricochet"
|
||||
className="w-full px-3 py-2 rounded-lg bg-background border border-border text-sm text-text placeholder:text-text/30 focus:outline-none focus:ring-2 focus:ring-primary/30"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Status radios */}
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-text/70 mb-2">
|
||||
Compatibility on Linux / SteamOS
|
||||
</label>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||
{(
|
||||
[
|
||||
"supported",
|
||||
"unsupported",
|
||||
"unknown",
|
||||
] as const
|
||||
).map((status) => {
|
||||
const cfg = statusConfig[status]
|
||||
const Icon = cfg.icon
|
||||
const active = value.antiCheatStatus === status
|
||||
return (
|
||||
<button
|
||||
key={status}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onChange({ ...value, antiCheatStatus: status })
|
||||
}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-3 py-2.5 rounded-lg border text-left transition-colors",
|
||||
active
|
||||
? cfg.color
|
||||
: "border-border bg-text/[0.02] hover:bg-text/5"
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
className={cn(
|
||||
"h-4 w-4 shrink-0",
|
||||
active ? cfg.textColor : "text-text/30"
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs font-medium",
|
||||
active ? cfg.textColor : "text-text/60"
|
||||
)}
|
||||
>
|
||||
{cfg.label}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Preview card */}
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-lg border p-4",
|
||||
currentConfig.color
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className={cn("h-5 w-5", config.textColor)} />
|
||||
<h4 className="font-medium">
|
||||
Anti-Cheat: {antiCheatEntry.antiCheatName || config.label}
|
||||
<CurrentIcon className={cn("h-5 w-5", currentConfig.textColor)} />
|
||||
<h4 className="font-medium text-sm">
|
||||
{relevant && value.antiCheatName
|
||||
? value.antiCheatName
|
||||
: currentConfig.label}
|
||||
</h4>
|
||||
</div>
|
||||
<p className={cn("mt-2 text-sm", config.textColor)}>{config.message}</p>
|
||||
<p className={cn("mt-2 text-sm", currentConfig.textColor)}>
|
||||
{currentConfig.message}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { motion } from "motion/react"
|
||||
import { Send, Loader2, AlertCircle, Monitor, Gauge, SlidersHorizontal, Terminal, FileText } from "lucide-react"
|
||||
import { Send, Loader2, AlertCircle, Monitor, Gauge, SlidersHorizontal, Terminal, FileText, GitBranch, Shield } from "lucide-react"
|
||||
import { TiptapEditor } from "@/components/tiptap-editor"
|
||||
import type { SettingCategory } from "@/components/wizard/settings-editor"
|
||||
import type { PerformanceData } from "./performance-step"
|
||||
@@ -11,6 +11,12 @@ import { UPSCALER_TYPE_OPTIONS, FRAME_GEN_OPTIONS } from "./environment-step"
|
||||
export interface ReviewData {
|
||||
hardwareSlug: string
|
||||
hardwareName: string
|
||||
gameVersionLabel: string
|
||||
antiCheat: {
|
||||
antiCheatRelevant: boolean
|
||||
antiCheatName: string
|
||||
antiCheatStatus: "none" | "supported" | "unsupported" | "unknown"
|
||||
}
|
||||
performance: PerformanceData
|
||||
settings: SettingCategory[]
|
||||
environment: EnvironmentData
|
||||
@@ -56,7 +62,7 @@ export function ReviewStep({
|
||||
isSubmitting,
|
||||
error,
|
||||
}: ReviewStepProps) {
|
||||
const { hardwareName, performance, environment, settings } = data
|
||||
const { hardwareName, gameVersionLabel, antiCheat, performance, environment, settings } = data
|
||||
|
||||
const upscalerLabel = (() => {
|
||||
if (!data.environment.upscalerType || data.environment.upscalerType === "none") return "None"
|
||||
@@ -85,10 +91,24 @@ export function ReviewStep({
|
||||
|
||||
{/* Summary Cards */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{/* Hardware */}
|
||||
{/* Setup: Hardware + Version + Anti-Cheat */}
|
||||
<div className="rounded-lg border border-border bg-text/5 p-4">
|
||||
<SectionHeader icon={Monitor} label="Hardware" />
|
||||
<SectionHeader icon={Monitor} label="Setup" />
|
||||
<SummaryRow label="Device" value={hardwareName || data.hardwareSlug || "Not selected"} />
|
||||
<SummaryRow label="Game Version" value={gameVersionLabel} />
|
||||
{antiCheat.antiCheatRelevant && (
|
||||
<>
|
||||
<SummaryRow label="Anti-Cheat" value={antiCheat.antiCheatName || "Unknown"} />
|
||||
<SummaryRow label="Anti-Cheat Status" value={
|
||||
antiCheat.antiCheatStatus === "supported" ? "Supported" :
|
||||
antiCheat.antiCheatStatus === "unsupported" ? "Unsupported" :
|
||||
antiCheat.antiCheatStatus === "unknown" ? "Unknown" : "None"
|
||||
} />
|
||||
</>
|
||||
)}
|
||||
{!antiCheat.antiCheatRelevant && (
|
||||
<SummaryRow label="Anti-Cheat" value="None" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Performance */}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
"use client"
|
||||
|
||||
import { GitBranch } from "lucide-react"
|
||||
import { HardwareStep } from "./hardware-step"
|
||||
import { AntiCheatStep, type AntiCheatData } from "./anti-cheat-step"
|
||||
|
||||
export interface GameVersionInfo {
|
||||
id: string
|
||||
versionString: string | null
|
||||
buildId: string | null
|
||||
isLatest: boolean
|
||||
}
|
||||
|
||||
interface SetupStepProps {
|
||||
gameId: string
|
||||
gameVersions: GameVersionInfo[]
|
||||
hardwareSlug: string
|
||||
onHardwareChange: (slug: string) => void
|
||||
hardwareName: string
|
||||
selectedVersionId: string
|
||||
onVersionChange: (versionId: string) => void
|
||||
newVersionString: string
|
||||
onNewVersionStringChange: (value: string) => void
|
||||
isCreatingVersion: boolean
|
||||
antiCheat: AntiCheatData
|
||||
onAntiCheatChange: (data: AntiCheatData) => void
|
||||
platformSupport: {
|
||||
hardwareSlug: string
|
||||
antiCheatRelevant: boolean
|
||||
antiCheatName: string | null
|
||||
antiCheatStatus: "none" | "supported" | "unsupported" | "unknown"
|
||||
}[]
|
||||
}
|
||||
|
||||
export function SetupStep({
|
||||
gameId,
|
||||
gameVersions,
|
||||
hardwareSlug,
|
||||
onHardwareChange,
|
||||
hardwareName,
|
||||
selectedVersionId,
|
||||
onVersionChange,
|
||||
newVersionString,
|
||||
onNewVersionStringChange,
|
||||
isCreatingVersion,
|
||||
antiCheat,
|
||||
onAntiCheatChange,
|
||||
platformSupport,
|
||||
}: SetupStepProps) {
|
||||
const isNewVersion = selectedVersionId === "__new__"
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Hardware Section */}
|
||||
<section>
|
||||
<HardwareStep value={hardwareSlug} onChange={onHardwareChange} />
|
||||
</section>
|
||||
|
||||
<div className="border-t border-border" />
|
||||
|
||||
{/* Game Version Section */}
|
||||
<section>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<GitBranch className="h-4 w-4 text-primary" />
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-text">Game Version</h3>
|
||||
<p className="text-xs text-text/60">
|
||||
Which version of the game did you test? This helps others know if benchmarks match their version.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-medium text-text/60">Version</label>
|
||||
<select
|
||||
value={selectedVersionId}
|
||||
onChange={(e) => onVersionChange(e.target.value)}
|
||||
className="w-full appearance-none px-4 py-3 rounded-lg border border-border bg-text/5 text-text text-sm outline-none focus:border-primary focus:ring-2 focus:ring-primary/50 transition-colors cursor-pointer"
|
||||
>
|
||||
{gameVersions.map((v) => (
|
||||
<option key={v.id} value={v.id}>
|
||||
{v.versionString
|
||||
? v.versionString
|
||||
: v.buildId
|
||||
? `Build ${v.buildId}`
|
||||
: "Unknown version"}
|
||||
{v.isLatest ? " (latest)" : ""}
|
||||
</option>
|
||||
))}
|
||||
<option value="__new__">
|
||||
+ New version...
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isNewVersion && (
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-medium text-text/60">
|
||||
Version String <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newVersionString}
|
||||
onChange={(e) => onNewVersionStringChange(e.target.value)}
|
||||
placeholder="e.g. 1.2.3, Patch 4.0, Hotfix Jan 2025"
|
||||
className="w-full px-4 py-3 rounded-lg border border-border bg-text/5 text-text text-sm placeholder:text-text/40 outline-none focus:border-primary focus:ring-2 focus:ring-primary/50 transition-colors"
|
||||
disabled={isCreatingVersion}
|
||||
/>
|
||||
<p className="text-xs text-text/40">
|
||||
Enter the game version you tested. This will create a new version entry.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="border-t border-border" />
|
||||
|
||||
{/* Anti-Cheat Section */}
|
||||
<section>
|
||||
<AntiCheatStep
|
||||
hardwareSlug={hardwareSlug}
|
||||
platformSupport={platformSupport}
|
||||
value={antiCheat}
|
||||
onChange={onAntiCheatChange}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user