diff --git a/apps/admin-ui/src/app/downloads/page.tsx b/apps/admin-ui/src/app/downloads/page.tsx index 902e04d..8606133 100644 --- a/apps/admin-ui/src/app/downloads/page.tsx +++ b/apps/admin-ui/src/app/downloads/page.tsx @@ -42,6 +42,16 @@ interface MetaHit { imdbId: string | null; } +interface SubtitleHit { + fileId: number; + language: string; + release: string; + fileName: string; + downloadCount: number; + hearingImpaired: boolean; + uploader: string | null; +} + interface JobRow { id: string; nodeId: string; @@ -120,6 +130,11 @@ export default function DownloadsPage() { const [selectedMeta, setSelectedMeta] = useState(null); const [jobBusy, setJobBusy] = useState(false); const [jobErr, setJobErr] = useState(null); + const [subs, setSubs] = useState([]); + const [subsBusy, setSubsBusy] = useState(false); + const [subsConfigured, setSubsConfigured] = useState(true); + const [selectedSubId, setSelectedSubId] = useState(null); + const [subsMsg, setSubsMsg] = useState(null); const [jobs, setJobs] = useState([]); @@ -223,6 +238,9 @@ export default function DownloadsPage() { setPick(hit); setSelectedMeta(null); setJobErr(null); + setSubs([]); + setSelectedSubId(null); + setSubsMsg(null); setMetaQ(hit.suggestQuery || hit.title); setMetaHits([]); await searchMeta(hit.suggestQuery || hit.title); @@ -244,6 +262,45 @@ export default function DownloadsPage() { } } + async function loadSubtitles(meta: MetaHit, releaseHint: string) { + setSubsBusy(true); + setSubsMsg(null); + setSubs([]); + setSelectedSubId(null); + try { + const params = new URLSearchParams({ + languages: "nl", + releaseHint, + }); + if (meta.imdbId) params.set("imdbId", meta.imdbId); + else params.set("tmdbId", String(meta.tmdbId)); + const res = await fetch(`/api/v1/admin/downloads/subtitles?${params}`, { + credentials: "include", + }); + const data = await res.json().catch(() => ({})); + if (!res.ok) { + setSubsMsg(data.error?.message ?? "Ondertitels zoeken mislukt"); + return; + } + setSubsConfigured(data.configured !== false); + if (data.configured === false) { + setSubsMsg("OpenSubtitles API-key niet geconfigureerd op Master"); + return; + } + const results: SubtitleHit[] = data.results ?? []; + setSubs(results); + if (results[0]) setSelectedSubId(results[0].fileId); + else setSubsMsg("Geen NL-ondertitels gevonden"); + } finally { + setSubsBusy(false); + } + } + + async function selectMeta(m: MetaHit) { + setSelectedMeta(m); + if (pick) await loadSubtitles(m, pick.title); + } + async function confirmDownload() { if (!pick || !selectedMeta || !pick.uri) { setJobErr("Kies een match en zorg dat er een download-URI is"); @@ -265,6 +322,8 @@ export default function DownloadsPage() { movieTitle: selectedMeta.title, movieYear: selectedMeta.year, posterUrl: selectedMeta.posterUrl, + subtitleFileId: selectedSubId, + subtitleLang: selectedSubId ? "nl" : null, }), }); const data = await res.json().catch(() => ({})); @@ -276,6 +335,8 @@ export default function DownloadsPage() { setHits([]); setQuery(""); setSearchErr(null); + setSubs([]); + setSelectedSubId(null); loadJobs(); } finally { setJobBusy(false); @@ -560,7 +621,7 @@ export default function DownloadsPage() { key={m.tmdbId} type="button" className={`match-item${selectedMeta?.tmdbId === m.tmdbId ? " selected" : ""}`} - onClick={() => setSelectedMeta(m)} + onClick={() => void selectMeta(m)} > {m.posterUrl && ( // eslint-disable-next-line @next/next/no-img-element @@ -579,6 +640,48 @@ export default function DownloadsPage() { ))} + + {selectedMeta && ( +
+

Ondertitel (NL)

+ {subsBusy &&

OpenSubtitles zoeken…

} + {subsMsg &&

{subsMsg}

} + {!subsBusy && subsConfigured && ( +
+ + {subs.map((s) => ( + + ))} +
+ )} +
+ )} + {jobErr &&

{jobErr}

}