"use client" import { useState, useCallback, useRef } from "react" import { Info, AlertTriangle, Loader2 } from "lucide-react" export interface BasicInfoData { title: string developer: string publisher: string description: string source: "manual" | "gog" | "epic" storeUrl: string genres: string[] releaseDate: string } interface NonSteamBasicInfoStepProps { value: BasicInfoData onChange: (value: BasicInfoData) => void } interface DuplicateHint { id: string title: string source: string } export function NonSteamBasicInfoStep({ value, onChange }: NonSteamBasicInfoStepProps) { const [checking, setChecking] = useState(false) const [duplicates, setDuplicates] = useState([]) const [genreInput, setGenreInput] = useState(value.genres.join(", ")) const lastCheckedTitle = useRef("") const update = (field: keyof BasicInfoData, val: string | string[]) => { onChange({ ...value, [field]: val }) } const checkDuplicates = useCallback(async (title: string) => { if (!title.trim() || title.trim().length < 2) { setDuplicates([]) return } if (lastCheckedTitle.current === title.trim()) return lastCheckedTitle.current = title.trim() setChecking(true) try { const res = await fetch(`/api/search/unified?q=${encodeURIComponent(title)}`) if (!res.ok) { setDuplicates([]) return } const data = await res.json() const results = (data.results || []) as Array<{ id?: string title: string source: string }> const matches = results .filter( (r) => r.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(r.title.toLowerCase()) ) .slice(0, 3) .map((r) => ({ id: r.id || String(r.title), title: r.title, source: r.source, })) setDuplicates(matches) } catch { setDuplicates([]) } finally { setChecking(false) } }, []) const handleGenreBlur = () => { const parsed = genreInput .split(",") .map((g) => g.trim()) .filter((g) => g.length > 0) onChange({ ...value, genres: parsed }) } return (

Basic Info

Enter the game details. Title is required.

{/* Title */}
update("title", e.target.value)} onBlur={(e) => checkDuplicates(e.target.value)} placeholder="e.g. Hollow Knight" 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" /> {checking && ( )}
{duplicates.length > 0 && (
Did you mean:{" "} {duplicates.map((d, i) => ( {d.title} {i < duplicates.length - 1 ? ", " : ""} ))}
)}
{/* Source */}
{/* Developer / Publisher */}
update("developer", e.target.value)} placeholder="e.g. Team Cherry" 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" />
update("publisher", e.target.value)} placeholder="e.g. Team Cherry" 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" />
{/* Description */}