diff --git a/apps/backoffice/app/(app)/downloads/DownloadsPage.tsx b/apps/backoffice/app/(app)/downloads/DownloadsPage.tsx index 4eb8238..d758754 100644 --- a/apps/backoffice/app/(app)/downloads/DownloadsPage.tsx +++ b/apps/backoffice/app/(app)/downloads/DownloadsPage.tsx @@ -65,11 +65,12 @@ function formatEta(seconds: number): string { interface DownloadsPageProps { initialDownloads: TorrentDownloadDto[]; initialLatestFound: LatestFoundPerLibraryDto[]; + qbConfigured?: boolean; } const PAGE_SIZE = 10; -export function DownloadsPage({ initialDownloads, initialLatestFound }: DownloadsPageProps) { +export function DownloadsPage({ initialDownloads, initialLatestFound, qbConfigured }: DownloadsPageProps) { const { t } = useTranslation(); const [downloads, setDownloads] = useState(initialDownloads); const [filter, setFilter] = useState("all"); @@ -184,7 +185,7 @@ export function DownloadsPage({ initialDownloads, initialLatestFound }: Download {/* Available downloads from latest detection */} {initialLatestFound.length > 0 && ( - +

diff --git a/apps/backoffice/app/(app)/downloads/page.tsx b/apps/backoffice/app/(app)/downloads/page.tsx index f96bb49..b805292 100644 --- a/apps/backoffice/app/(app)/downloads/page.tsx +++ b/apps/backoffice/app/(app)/downloads/page.tsx @@ -3,10 +3,20 @@ import { DownloadsPage } from "./DownloadsPage"; export const dynamic = "force-dynamic"; +async function isQbConfigured(): Promise { + try { + const data = await apiFetch<{ url?: string; username?: string }>("/settings/qbittorrent"); + return !!(data && data.url?.trim() && data.username?.trim()); + } catch { + return false; + } +} + export default async function Page() { - const [downloads, latestFound] = await Promise.all([ + const [downloads, latestFound, qbConfigured] = await Promise.all([ fetchTorrentDownloads().catch(() => [] as TorrentDownloadDto[]), apiFetch("/download-detection/latest-found").catch(() => [] as LatestFoundPerLibraryDto[]), + isQbConfigured(), ]); - return ; + return ; } diff --git a/apps/backoffice/app/(app)/jobs/[id]/components/DownloadDetectionCards.tsx b/apps/backoffice/app/(app)/jobs/[id]/components/DownloadDetectionCards.tsx index 93d7b41..d62e6ea 100644 --- a/apps/backoffice/app/(app)/jobs/[id]/components/DownloadDetectionCards.tsx +++ b/apps/backoffice/app/(app)/jobs/[id]/components/DownloadDetectionCards.tsx @@ -48,15 +48,16 @@ export function DownloadDetectionErrorsCard({ results, t }: { ); } -export function DownloadDetectionResultsCard({ results, libraryId, t }: { +export function DownloadDetectionResultsCard({ results, libraryId, qbConfigured, t }: { results: DownloadDetectionResultDto[]; libraryId: string | null; + qbConfigured?: boolean; t: TranslateFunction; }) { if (results.length === 0) return null; return ( - + {t("jobDetail.downloadAvailableReleases")} diff --git a/apps/backoffice/app/(app)/jobs/[id]/page.tsx b/apps/backoffice/app/(app)/jobs/[id]/page.tsx index 1b1b4a2..1d43400 100644 --- a/apps/backoffice/app/(app)/jobs/[id]/page.tsx +++ b/apps/backoffice/app/(app)/jobs/[id]/page.tsx @@ -149,11 +149,15 @@ export default async function JobDetailPage({ params }: JobDetailPageProps) { let downloadDetectionReport: DownloadDetectionReportDto | null = null; let downloadDetectionResults: DownloadDetectionResultDto[] = []; let downloadDetectionErrors: DownloadDetectionResultDto[] = []; + let qbConfigured = false; if (isDownloadDetection) { - [downloadDetectionReport, downloadDetectionResults, downloadDetectionErrors] = await Promise.all([ + [downloadDetectionReport, downloadDetectionResults, downloadDetectionErrors, qbConfigured] = await Promise.all([ getDownloadDetectionReport(id).catch(() => null), getDownloadDetectionResults(id, "found").catch(() => []), getDownloadDetectionResults(id, "error").catch(() => []), + apiFetch<{ url?: string; username?: string }>("/settings/qbittorrent") + .then(d => !!(d?.url?.trim() && d?.username?.trim())) + .catch(() => false), ]); } @@ -273,7 +277,7 @@ export default async function JobDetailPage({ params }: JobDetailPageProps) { {/* Download detection */} {isDownloadDetection && downloadDetectionReport && } {isDownloadDetection && } - {isDownloadDetection && } + {isDownloadDetection && } {/* Metadata batch results */} {isMetadataBatch && } diff --git a/apps/backoffice/app/components/QbittorrentDownloadButton.tsx b/apps/backoffice/app/components/QbittorrentDownloadButton.tsx index 63405e0..b6297f5 100644 --- a/apps/backoffice/app/components/QbittorrentDownloadButton.tsx +++ b/apps/backoffice/app/components/QbittorrentDownloadButton.tsx @@ -6,17 +6,19 @@ import { useTranslation } from "@/lib/i18n/context"; const QbConfigContext = createContext(false); -export function QbittorrentProvider({ children }: { children: ReactNode }) { - const [configured, setConfigured] = useState(false); +export function QbittorrentProvider({ children, initialConfigured }: { children: ReactNode; initialConfigured?: boolean }) { + const [configured, setConfigured] = useState(initialConfigured ?? false); useEffect(() => { + // Skip client fetch if server already told us + if (initialConfigured !== undefined) return; fetch("/api/settings/qbittorrent") .then((r) => (r.ok ? r.json() : null)) .then((data) => { setConfigured(!!(data && data.url && data.url.trim() && data.username && data.username.trim())); }) .catch(() => setConfigured(false)); - }, []); + }, [initialConfigured]); return {children}; }