diff --git a/components/wizard/game-entry-wizard.tsx b/components/wizard/game-entry-wizard.tsx new file mode 100644 index 0000000..4b49842 --- /dev/null +++ b/components/wizard/game-entry-wizard.tsx @@ -0,0 +1,240 @@ +"use client" + +import { useState, useCallback } 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 { 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" +import { ReviewStep } from "@/components/wizard/steps/review-step" +import type { SettingCategory } from "@/components/wizard/settings-editor" + +const STEPS = [ + { label: "Hardware", tooltip: "Choose the hardware you tested this game on" }, + { 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" }, + { label: "Review", tooltip: "Review your entry before submitting. Add any additional notes." }, +] + +interface GameEntryWizardProps { + gameId: string + gameVersionId: string +} + +export function GameEntryWizard({ gameId, gameVersionId }: GameEntryWizardProps) { + const router = useRouter() + const [currentStep, setCurrentStep] = useState(0) + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState(null) + const [success, setSuccess] = useState(false) + + // Step 1: Hardware + const [hardwareSlug, setHardwareSlug] = useState("") + const [hardwareName, setHardwareName] = useState("") + + // Step 2: Performance + const [performance, setPerformance] = useState({}) + + // Step 3: Settings + const [settingsJson, setSettingsJson] = useState([]) + + // Step 4: Environment + const [environment, setEnvironment] = useState({ + fsrVersion: "none", + frameGenMethod: "none", + }) + + // Step 5: Notes + const [userNotes, setUserNotes] = useState("") + + // Fetch hardware name when slug changes + const handleHardwareChange = useCallback(async (slug: string) => { + setHardwareSlug(slug) + if (!slug) { + setHardwareName("") + return + } + try { + const res = await fetch("/api/performance/hardware") + if (res.ok) { + const data = await res.json() as { data: Array<{ slug: string; name: string }> } + const device = data.data.find((d) => d.slug === slug) + if (device) setHardwareName(device.name) + } + } catch { + // ignore + } + }, []) + + const canProceed = () => { + switch (currentStep) { + case 0: + return hardwareSlug !== "" + case 1: + return performance.fpsAvg !== undefined && performance.fpsAvg > 0 + case 2: + return true // Settings are optional + case 3: + return true // Environment is optional + case 4: + return true + default: + return false + } + } + + const handleNext = () => { + if (currentStep < STEPS.length - 1 && canProceed()) { + setCurrentStep(currentStep + 1) + } + } + + const handleBack = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1) + } + } + + const handleStepClick = (step: number) => { + if (step <= currentStep) { + setCurrentStep(step) + } + } + + const handleSubmit = async () => { + setIsSubmitting(true) + setError(null) + + try { + const res = await fetch("/api/performance/submit", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + versionId: gameVersionId, + hardwareSlug, + fpsAvg: Number(performance.fpsAvg), + fpsLow: performance.fpsLow !== undefined ? Number(performance.fpsLow) : null, + fpsHigh: performance.fpsHigh !== undefined ? Number(performance.fpsHigh) : null, + loadTimeSsd: performance.loadTimeSsd !== undefined ? Number(performance.loadTimeSsd) : null, + loadTimeSd: performance.loadTimeSd !== undefined ? Number(performance.loadTimeSd) : null, + protonVersion: environment.protonVersion || null, + osVersion: environment.osVersion || null, + fsrVersion: environment.fsrVersion ?? "none", + frameGenMethod: environment.frameGenMethod ?? "none", + launchOptions: environment.launchOptions || null, + settingsJson: settingsJson.length > 0 ? settingsJson : null, + userNotes: userNotes || null, + }), + }) + + if (!res.ok) { + const data = await res.json() + throw new Error(data.error || "Failed to submit entry") + } + + setSuccess(true) + setTimeout(() => { + router.push(`/game/${gameId}`) + }, 2000) + } catch (err) { + setError(err instanceof Error ? err.message : "An error occurred") + } finally { + setIsSubmitting(false) + } + } + + if (success) { + return ( + +
+ + + +
+

Entry Submitted!

+

Redirecting to game page...

+
+ ) + } + + return ( +
+ {/* Step Indicator */} + + + {/* Step Content */} + + + {currentStep === 0 && ( + + )} + {currentStep === 1 && ( + + )} + {currentStep === 2 && ( + + )} + {currentStep === 3 && ( + + )} + {currentStep === 4 && ( + + )} + + + + {/* Navigation Buttons */} + {currentStep < 4 && ( +
+ + +
+ )} +
+ ) +}