import {useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {ArrowLeft, Link2, Loader2, Search, Send} from 'lucide-react'; import {toast} from 'sonner'; import {Button} from '@/components/ui/button'; import {Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle} from '@/components/ui/dialog'; import {Input} from '@/components/ui/input'; import GameCover from './GameCover.jsx'; import FitText from './FitText.jsx'; import {linkGames, requestLink, searchOwnGames} from '../utils/links.js'; import {MAX_LIBRARY_SEARCH_LENGTH} from '../utils/limits.js'; import {platformLabel} from '../utils/platforms.js'; const SEARCH_DEBOUNCE_MS = 300; const KNOWN_ERRORS = new Set(['SAME_GAME', 'ALREADY_LINKED', 'PAIR_DISMISSED', 'TOO_MANY_REQUESTS', 'GAME_NOT_FOUND']); /** * Picks a game of the own library that is the same game as this one. A user sends a request to the auditors; an * auditor links at once. */ export default function LinkGameDialog({game, open, onOpenChange, auditor, onDone}) { const {t} = useTranslation(); const [query, setQuery] = useState(''); const [hits, setHits] = useState([]); const [picked, setPicked] = useState(null); const [busy, setBusy] = useState(false); useEffect(() => { if (open) { setQuery(''); setHits([]); setPicked(null); } }, [open]); useEffect(() => { if (!open || !query.trim()) { setHits([]); return undefined; } const controller = new AbortController(); const timer = setTimeout(async () => { const {ok, data} = await searchOwnGames(query.trim(), controller.signal); if (ok && data) setHits(data.content); }, SEARCH_DEBOUNCE_MS); return () => { clearTimeout(timer); controller.abort(); }; }, [open, query]); const send = async () => { setBusy(true); try { const response = await (auditor ? linkGames : requestLink)(game.appid, picked.appid); if (response.status === 401) return; if (response.ok) { toast.success(t(auditor ? 'LINKS.LINKED' : 'LINKS.REQUESTED')); onOpenChange(false); if (auditor) onDone(); return; } const data = await response.json().catch(() => ({})); toast.error(t(KNOWN_ERRORS.has(data.code) ? `LINKS.ERROR_CODES.${data.code}` : 'LINKS.ERROR')); } catch (err) { console.error('Link error:', err); toast.error(t('INFO.ERROR.NETWORK_FAILURE')); } finally { setBusy(false); } }; const offered = hits.filter((hit) => hit.appid !== game.appid); return ( {t('LINKS.SAME_AS')} {t('LINKS.DIALOG_HINT', {name: game.name})} {picked ? (

{t(auditor ? 'LINKS.LINK_CONFIRM' : 'LINKS.REQUEST_CONFIRM', {name: game.name, other: picked.name})}

) : (
setQuery(e.target.value)}/>
{query.trim() && offered.length === 0 && (

{t('LINKS.NO_HITS')}

)} {offered.length > 0 && (
    {offered.map((hit) => (
  • ))}
)}
)}
); }