"use client"; import { useState, useEffect, createContext, useContext, type ReactNode } from "react"; import { createPortal } from "react-dom"; import { Icon, Button } from "./ui"; import { useTranslation } from "@/lib/i18n/context"; interface QbContextValue { configured: boolean; onDownloadStarted?: () => void; } const QbConfigContext = createContext({ configured: false }); export function QbittorrentProvider({ children, initialConfigured, onDownloadStarted }: { children: ReactNode; initialConfigured?: boolean; onDownloadStarted?: () => void }) { 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}; } export function QbittorrentDownloadButton({ downloadUrl, releaseId, libraryId, seriesName, expectedVolumes, allVolumes, alwaysShowReplace, }: { downloadUrl: string; releaseId: string; libraryId?: string; seriesName?: string; expectedVolumes?: number[]; allVolumes?: number[]; /** Show replace button even when allVolumes == expectedVolumes (e.g. in Prowlarr search modal) */ alwaysShowReplace?: boolean; }) { const { t } = useTranslation(); const { configured, onDownloadStarted } = useContext(QbConfigContext); const [sending, setSending] = useState(false); const [sent, setSent] = useState(false); const [error, setError] = useState(null); const [showConfirm, setShowConfirm] = useState(false); if (!configured) return null; const showReplaceButton = allVolumes && allVolumes.length > 0 && (alwaysShowReplace || (expectedVolumes && allVolumes.length > expectedVolumes.length)); async function handleSend(volumes?: number[], replaceExisting = false) { setSending(true); setError(null); try { const resp = await fetch("/api/qbittorrent/add", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: downloadUrl, ...(libraryId && { library_id: libraryId }), ...(seriesName && { series_name: seriesName }), ...((volumes || expectedVolumes) && { expected_volumes: volumes || expectedVolumes }), ...(replaceExisting && { replace_existing: true }), }), }); const data = await resp.json(); if (data.error) { setError(data.error); } else if (data.success) { setSent(true); onDownloadStarted?.(); setTimeout(() => setSent(false), 5000); } else { setError(data.message || t("prowlarr.sentError")); } } catch { setError(t("prowlarr.sentError")); } finally { setSending(false); } } return ( <>
{showReplaceButton && ( )}
{showConfirm && createPortal( <>
setShowConfirm(false)} />

{t("prowlarr.replaceAndDownload")}

{t("prowlarr.confirmReplace")}

, document.body )} ); }