import {useState} from 'react'; import {Link} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; import {Gamepad, Link2, Loader2} from 'lucide-react'; import {toast} from 'sonner'; import {Button} from '@/components/ui/button'; import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'; import {Input} from '@/components/ui/input'; import UnlinkDialog from './UnlinkDialog.jsx'; import usePsnAccount from '../hooks/usePsnAccount.js'; import {formatDate} from '../utils/format.js'; import {isExpired, linkPsn, NPSSO_PAGE_URL, unlinkPsn} from '../utils/psn.js'; /** * The linked PlayStation account. The user pastes an NPSSO once; Core keeps only an encrypted token for about 10 days, * so the card shows when the link ends. Linking starts a sync; titles the sync could not match wait in an import review. */ export default function PsnAccountCard() { const {t, i18n} = useTranslation(); const {account, setAccount, failed} = usePsnAccount(); const [npsso, setNpsso] = useState(''); const [busy, setBusy] = useState(false); const link = async (event) => { event.preventDefault(); setBusy(true); try { const response = await linkPsn(npsso.trim()); if (response.status === 401) return; if (response.ok) { setAccount(await response.json()); toast.success(t('SETTINGS.PSN.LINKED')); } else if (response.status === 400) { toast.error(t('SETTINGS.PSN.INVALID')); } else if (response.status === 429) { toast.error(t('SETTINGS.PSN.BUSY')); } else if (response.status === 502) { toast.error(t('SETTINGS.PSN.UNAVAILABLE')); } else if (response.status === 503) { setAccount((old) => ({...old, enabled: false})); } else { toast.error(t('SETTINGS.PSN.ERROR')); } } catch (err) { console.error('PSN link error:', err); toast.error(t('SETTINGS.PSN.ERROR')); } finally { // The NPSSO is a session secret: it does not stay in the page after any answer. setNpsso(''); setBusy(false); } }; const unlink = async () => { setBusy(true); try { const response = await unlinkPsn(); if (response.status === 401) return; if (!response.ok) { toast.error(t('SETTINGS.PSN.ERROR')); return; } setAccount((old) => ({...old, linked: false, linkedAt: null, expiresAt: null})); toast.success(t('SETTINGS.PSN.UNLINKED')); } catch (err) { console.error('PSN unlink error:', err); toast.error(t('INFO.ERROR.NETWORK_FAILURE')); } finally { setBusy(false); } }; const expired = isExpired(account); const form = ; return ( {t('SETTINGS.PSN.TITLE')} {failed ? (

{t('SETTINGS.PSN.ERROR')}

) : !account ? ( ) : !account.enabled ? (

{t('SETTINGS.PSN.OFF')}

) : !account.linked ? form : ( <>

{t('SETTINGS.PSN.LINKED_SINCE', {date: formatDate(account.linkedAt, i18n.language, {dateStyle: 'medium'})})}

{t('SETTINGS.PSN.EXPIRES_ON', {date: formatDate(account.expiresAt, i18n.language, {dateStyle: 'medium'})})} {expired && <> ยท {t('SETTINGS.PSN.EXPIRED')}}

{t('SETTINGS.PSN.SYNC_HINT')}

{expired && form}
{account.reviewImportId && ( )}
)}
); } function NpssoForm({npsso, onChange, onSubmit, busy}) { const {t} = useTranslation(); return (
  1. {t('SETTINGS.PSN.STEPS_1')}
  2. {t('SETTINGS.PSN.STEPS_2')}{' '} {NPSSO_PAGE_URL}
  3. {t('SETTINGS.PSN.STEPS_3')}

{t('SETTINGS.PSN.WHAT_IT_GIVES')}

onChange(e.target.value)}/>
); }