diff --git a/components/wizard/steps/environment-step.tsx b/components/wizard/steps/environment-step.tsx new file mode 100644 index 0000000..9cc9530 --- /dev/null +++ b/components/wizard/steps/environment-step.tsx @@ -0,0 +1,238 @@ +"use client" + +import { useEffect, useRef, useState, useCallback } from "react" +import { motion, AnimatePresence } from "motion/react" +import { Terminal, ChevronDown, Loader2 } from "lucide-react" +import { api } from "@/lib/eden" + +const FSR_OPTIONS = [ + { value: "none", label: "None" }, + { value: "fsr1", label: "FSR 1" }, + { value: "fsr2", label: "FSR 2" }, + { value: "fsr3", label: "FSR 3" }, +] as const + +const FRAME_GEN_OPTIONS = [ + { value: "none", label: "None" }, + { value: "fsr_fg", label: "FSR Frame Generation" }, + { value: "dlss_fg", label: "DLSS Frame Generation" }, +] as const + +export interface EnvironmentData { + protonVersion?: string + osVersion?: string + fsrVersion?: string + frameGenMethod?: string + launchOptions?: string +} + +interface EnvironmentStepProps { + value: EnvironmentData + onChange: (value: EnvironmentData) => void +} + +function AutocompleteInput({ + label, + placeholder, + value, + onChange, + field, +}: { + label: string + placeholder: string + value: string + onChange: (val: string) => void + field: "protonVersion" | "osVersion" +}) { + const [suggestions, setSuggestions] = useState([]) + const [loading, setLoading] = useState(false) + const [open, setOpen] = useState(false) + const [inputValue, setInputValue] = useState(value) + const containerRef = useRef(null) + + useEffect(() => { + setInputValue(value) + }, [value]) + + useEffect(() => { + function handleClickOutside(e: MouseEvent) { + if (containerRef.current && !containerRef.current.contains(e.target as Node)) { + setOpen(false) + } + } + document.addEventListener("mousedown", handleClickOutside) + return () => document.removeEventListener("mousedown", handleClickOutside) + }, []) + + const fetchSuggestions = useCallback( + async (query: string) => { + try { + setLoading(true) + const res = await api.performance.autocomplete.get({ query: { field } }) + if (!res.error && res.data?.data) { + const data = res.data.data + const filtered = query + ? data.filter((s) => s.toLowerCase().includes(query.toLowerCase())) + : data + setSuggestions(filtered.slice(0, 8)) + } + } catch { + setSuggestions([]) + } finally { + setLoading(false) + } + }, + [field] + ) + + const handleFocus = () => { + setOpen(true) + fetchSuggestions(inputValue) + } + + const handleChange = (val: string) => { + setInputValue(val) + onChange(val) + fetchSuggestions(val) + setOpen(true) + } + + const handleSelect = (val: string) => { + setInputValue(val) + onChange(val) + setOpen(false) + } + + return ( +
+ +
+ handleChange(e.target.value)} + onFocus={handleFocus} + placeholder={placeholder} + 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" + /> + {loading && ( + + )} + + + {open && suggestions.length > 0 && ( + + {suggestions.map((s) => ( + + ))} + + )} + +
+
+ ) +} + +export function EnvironmentStep({ value, onChange }: EnvironmentStepProps) { + const update = (field: keyof EnvironmentData, val: string) => { + onChange({ ...value, [field]: val }) + } + + return ( +
+
+ +
+

Environment

+

+ Describe the software environment used during testing. +

+
+
+ +
+ update("protonVersion", val)} + field="protonVersion" + /> + + update("osVersion", val)} + field="osVersion" + /> +
+ +
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ +
+ +