"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.

)}
)}
); }