'use client' import { useEffect, useRef, useState } from 'react' import { authFetch, getApiBase } from '../../lib/auth' import type { Row } from './IdentityReviewPanel' type Preview = { revision: string; server_id: string; row: Row before: { jellyfin_user_id: string | null; seerr_user_id: number | null } seerr_users: { id: number; name: string; jellyfin_id: string | null }[] scope: string action: string } export default function ResolveIdentityLink({ row, accounts, onClose, onSaved }: { row: Row; accounts: { id: string; name: string }[]; onClose: () => void; onSaved: () => void }) { const dialog = useRef(null) const controller = useRef(null) const [chosen, setChosen] = useState(row.candidate_jellyfin_id ?? '') const [inspectSeerr, setInspectSeerr] = useState('') const [createSeerr, setCreateSeerr] = useState(false) const [preview, setPreview] = useState(null) const [busy, setBusy] = useState(false) const [saving, setSaving] = useState(false) const [error, setError] = useState('') useEffect(() => { const previous = document.activeElement as HTMLElement | null const overflow = document.body.style.overflow document.body.style.overflow = 'hidden' dialog.current?.showModal() return () => { controller.current?.abort() document.body.style.overflow = overflow previous?.focus() } }, []) const submit = async (confirm: boolean) => { if (!chosen || busy || saving || (confirm && !preview?.row.can_confirm)) return const abort = new AbortController() controller.current = abort setError('') if (confirm) setSaving(true) else { setBusy(true); setPreview(null) } try { const response = await authFetch(`${getApiBase()}/admin/identities/repair/${confirm ? 'confirm' : 'check'}`, { method: 'POST', signal: abort.signal, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: row.user.id, jellyfin_user_id: chosen, create_seerr: createSeerr, ...(confirm ? { revision: preview?.revision } : {}) }), }) const data = await response.json().catch(() => ({})) if (!response.ok) throw new Error(response.status === 401 ? 'Your session has ended. Sign in again.' : typeof data.detail === 'string' ? data.detail : 'Could not check the account links. Try again.') if (!abort.signal.aborted) { if (confirm) onSaved() else setPreview(data) } } catch (err) { if (!abort.signal.aborted) { setError(err instanceof Error ? err.message : 'Could not resolve the link.') setPreview(null) } } finally { if (!abort.signal.aborted) { setBusy(false); setSaving(false) } } } return { event.preventDefault(); if (!saving) onClose() }}>

Review account repair

Review {row.user.username} (Magent {row.user.id}) against their Jellyfin account. Confirm that these identities belong to the same person before repairing Magent.

{error &&

{error}

} {busy &&

Checking live IDs and whether another Magent account already owns this identity.

} {preview &&

{preview.row.can_confirm ? 'Ready to repair' : 'This link needs attention'}

Jellyfin server {preview.server_id ?? 'Unavailable'}

Current Magent links

Jellyfin: {preview.before.jellyfin_user_id ?? 'Not linked'}

Seerr: {preview.before.seerr_user_id ?? 'Not linked'}

Proposed Magent links

Jellyfin: {preview.row.candidate_jellyfin_id}

Seerr: {preview.row.seerr.length === 1 ? preview.row.seerr[0].id : preview.action === 'import_seerr' ? 'Assigned by Seerr during import' : 'Not verified'}

Jellyfin
{preview.row.jellyfin?.name ?? 'Account not found'}{preview.row.candidate_jellyfin_id}
Seerr
{preview.row.seerr.length ? preview.row.seerr.map((account) => `${account.name} (ID ${account.id})`).join(', ') : 'No matching Jellyfin ID. Check this user’s Jellyfin account link in Seerr, then check again.'}
Jellystat
{preview.row.jellystat.id ?? 'Not verified'}{preview.row.jellystat.state === 'matched' ? 'Same Jellyfin ID verified' : preview.row.jellystat.state === 'missing' ? 'This ID is missing from Jellystat. Check its Jellyfin sync, then check again.' : 'Could not verify this ID. Check the Jellystat connection and try again.'}
{preview.row.issues.length > 0 &&
    {preview.row.issues.map((issue) =>
  • {issue}
  • )}
} {preview.row.state === 'unavailable' &&

A required service is unavailable. Restore its connection and check again.

} {preview.row.seerr.length !== 1 &&

Check the existing Seerr account

Choose an account to inspect its current Jellyfin ID. This selection does not change or link it.

{preview.seerr_users.filter((account) => String(account.id) === inspectSeerr).map((account) =>

Current Jellyfin ID: {account.jellyfin_id ?? 'Not linked'}

)}

If this is the same person, use Seerr's account settings to reconnect their existing account to Jellyfin, then preview again. Linking requires that user's Jellyfin sign-in in Seerr. Keep the existing Seerr account to preserve its requests and settings.

If they have never had a Seerr account, import just their Jellyfin account from Seerr's Users page, then preview again. Do not import a second account to work around an existing identity mismatch.

}

{preview.scope}

Repair records the previous and new links, your administrator name and the time. Live IDs and duplicate ownership are rechecked before the change is saved.

{preview.before.jellyfin_user_id && preview.before.jellyfin_user_id !== preview.row.candidate_jellyfin_id &&

Changing the Jellyfin identity also revokes identity-bound email subscriptions. The user will need to opt in again.

}
}
}