Add reviewed duplicate account consolidation and prevent duplicate imports
Magent CI/CD / verify (push) Successful in 11m16s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 1m33s

This commit is contained in:
2026-09-11 16:38:53 +12:00
parent df6fe58278
commit 52c85daae3
10 changed files with 562 additions and 2 deletions
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from 'react'
import { useRouter } from 'next/navigation'
import { authFetch, getApiBase } from '../../lib/auth'
import './identities.css'
import DuplicateAccountRepair from './DuplicateAccountRepair'
import ResolveIdentityLink from './ResolveIdentityLink'
type Identity = { id: string; name: string }
@@ -44,12 +45,14 @@ export default function IdentityReviewPanel() {
const [query, setQuery] = useState('')
const [filter, setFilter] = useState('all')
const [selected, setSelected] = useState<number[]>([])
const [duplicates, setDuplicates] = useState<Row | null>(null)
const [resolving, setResolving] = useState<Row | null>(null)
const [reviewing, setReviewing] = useState(false)
const controller = useRef<AbortController | null>(null)
const reviewPanel = useRef<HTMLElement | null>(null)
useEffect(() => {
setQuery(new URLSearchParams(window.location.search).get('user') ?? '')
const abort = new AbortController()
void authFetch(`${getApiBase()}/auth/me`, { signal: abort.signal }).then(async (response) => {
if (response.status === 401) { router.replace('/login'); return }
@@ -150,7 +153,7 @@ export default function IdentityReviewPanel() {
<div><dt>Jellystat user ID</dt><dd><code>{row.jellystat.id ?? 'Not verified'}</code><span>{statsLabels[row.jellystat.state] ?? row.jellystat.state}</span></dd></div>
</dl>
{row.issues.length > 0 && <ul className="identity-issues">{row.issues.map((issue) => <li key={issue}>{issue}</li>)}</ul>}
{(row.state === 'unlinked' || row.state === 'conflict') && <div className="identity-resolution-entry"><p className="identity-meta">Compare the correct Jellyfin identity with the stored links and review the smallest safe repair.</p><button type="button" className="ghost-button" disabled={saving || report.services.jellyfin !== 'available'} onClick={() => setResolving(row)}>Review repair</button></div>}
{(row.state === 'unlinked' || row.state === 'conflict') && <div className="identity-resolution-entry"><p className="identity-meta">Compare the correct Jellyfin identity with the stored links and review the smallest safe repair.</p><button type="button" className="ghost-button" disabled={saving || report.services.jellyfin !== 'available'} onClick={() => setResolving(row)}>Review repair</button>{row.issues.some((issue) => issue.includes("share this username")) && <button type="button" className="ghost-button" disabled={saving} onClick={() => setDuplicates(row)}>Repair duplicate accounts</button>}</div>}
{row.state === 'unavailable' && <p className="identity-meta">A required service could not be checked. Check its connection and run this again.</p>}
{row.confirmed_at && <p className="identity-meta">Last confirmed {new Date(row.confirmed_at).toLocaleString()}</p>}
</article>)}
@@ -158,6 +161,7 @@ export default function IdentityReviewPanel() {
{report.upstream.length > 0 && <details className="identity-upstream"><summary>{report.upstream.length} upstream accounts need review</summary><ul>{report.upstream.map((entry) => <li key={`${entry.platform}-${entry.id}`}><strong>{entry.platform}: {entry.name}</strong> · ID <code>{entry.id}</code>{entry.jellyfin_id && <span> · Jellyfin <code>{entry.jellyfin_id}</code></span>}<p>{entry.detail}</p></li>)}</ul></details>}
</>}
</>}
{duplicates && <DuplicateAccountRepair row={duplicates} onClose={() => setDuplicates(null)} onSaved={() => { setDuplicates(null); void runCheck().then(() => setNotice('Duplicate accounts repaired. History retained and links rechecked.')) }} />}
{resolving && report && <ResolveIdentityLink row={resolving} accounts={report.jellyfin_users} onClose={() => setResolving(null)} onSaved={() => {
setResolving(null); setReport(null); setSelected([]); setReviewing(false)
setNotice('Account links repaired and saved. Run another check to see the updated mappings.')