From 2dad295451da2c1e46ce29a2392635ed0f5b6e48 Mon Sep 17 00:00:00 2001 From: Froidefond Julien Date: Fri, 27 Mar 2026 08:44:19 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20supprime=20le=20layout=20shift=20du=20bo?= =?UTF-8?q?uton=20Prowlarr=20sur=20la=20page=20s=C3=A9rie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les configs Prowlarr et qBittorrent sont récupérées côté serveur et passées en props au ProwlarrSearchModal, évitant les deux fetchs client qui causaient l'apparition tardive du bouton. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../libraries/[id]/series/[name]/page.tsx | 12 +++++- .../app/components/ProwlarrSearchModal.tsx | 41 +++++++++++-------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/apps/backoffice/app/(app)/libraries/[id]/series/[name]/page.tsx b/apps/backoffice/app/(app)/libraries/[id]/series/[name]/page.tsx index f5d3143..aab9c71 100644 --- a/apps/backoffice/app/(app)/libraries/[id]/series/[name]/page.tsx +++ b/apps/backoffice/app/(app)/libraries/[id]/series/[name]/page.tsx @@ -1,4 +1,4 @@ -import { fetchLibraries, fetchBooks, fetchSeriesMetadata, getBookCoverUrl, getMetadataLink, getMissingBooks, getReadingStatusLink, BookDto, SeriesMetadataDto, ExternalMetadataLinkDto, MissingBooksDto, AnilistSeriesLinkDto } from "@/lib/api"; +import { fetchLibraries, fetchBooks, fetchSeriesMetadata, getBookCoverUrl, getMetadataLink, getMissingBooks, getReadingStatusLink, apiFetch, BookDto, SeriesMetadataDto, ExternalMetadataLinkDto, MissingBooksDto, AnilistSeriesLinkDto } from "@/lib/api"; import { BooksGrid, EmptyState } from "@/app/components/BookCard"; import { MarkSeriesReadButton } from "@/app/components/MarkSeriesReadButton"; import { MarkBookReadButton } from "@/app/components/MarkBookReadButton"; @@ -41,7 +41,7 @@ export default async function SeriesDetailPage({ const seriesName = decodeURIComponent(name); - const [library, booksPage, seriesMeta, metadataLinks, readingStatusLink] = await Promise.all([ + const [library, booksPage, seriesMeta, metadataLinks, readingStatusLink, prowlarrConfigured, qbConfigured] = await Promise.all([ fetchLibraries().then((libs) => libs.find((l) => l.id === id)), fetchBooks(id, seriesName, page, limit).catch(() => ({ items: [] as BookDto[], @@ -52,6 +52,12 @@ export default async function SeriesDetailPage({ fetchSeriesMetadata(id, seriesName).catch(() => null as SeriesMetadataDto | null), getMetadataLink(id, seriesName).catch(() => [] as ExternalMetadataLinkDto[]), getReadingStatusLink(id, seriesName).catch(() => null as AnilistSeriesLinkDto | null), + apiFetch<{ api_key?: string }>("/settings/prowlarr") + .then(d => !!(d?.api_key?.trim())) + .catch(() => false), + apiFetch<{ url?: string; username?: string }>("/settings/qbittorrent") + .then(d => !!(d?.url?.trim() && d?.username?.trim())) + .catch(() => false), ]); const existingLink = metadataLinks.find((l) => l.status === "approved") ?? metadataLinks[0] ?? null; @@ -235,6 +241,8 @@ export default async function SeriesDetailPage({ (null); + const [isConfigured, setIsConfigured] = useState(initialProwlarrConfigured ?? null); const [isSearching, setIsSearching] = useState(false); const [results, setResults] = useState([]); const [query, setQuery] = useState(""); const [error, setError] = useState(null); // qBittorrent state - const [isQbConfigured, setIsQbConfigured] = useState(false); + const [isQbConfigured, setIsQbConfigured] = useState(initialQbConfigured ?? false); const [sendingGuid, setSendingGuid] = useState(null); const [sentGuids, setSentGuids] = useState>(new Set()); const [sendError, setSendError] = useState(null); - // Check if Prowlarr and qBittorrent are configured on mount + // Check if Prowlarr and qBittorrent are configured on mount (skip if server provided) useEffect(() => { - fetch("/api/settings/prowlarr") - .then((r) => (r.ok ? r.json() : null)) - .then((data) => { - setIsConfigured(!!(data && data.api_key && data.api_key.trim())); - }) - .catch(() => setIsConfigured(false)); - fetch("/api/settings/qbittorrent") - .then((r) => (r.ok ? r.json() : null)) - .then((data) => { - setIsQbConfigured(!!(data && data.url && data.url.trim() && data.username && data.username.trim())); - }) - .catch(() => setIsQbConfigured(false)); - }, []); + if (initialProwlarrConfigured !== undefined && initialQbConfigured !== undefined) return; + if (initialProwlarrConfigured === undefined) { + fetch("/api/settings/prowlarr") + .then((r) => (r.ok ? r.json() : null)) + .then((data) => { + setIsConfigured(!!(data && data.api_key && data.api_key.trim())); + }) + .catch(() => setIsConfigured(false)); + } + if (initialQbConfigured === undefined) { + fetch("/api/settings/qbittorrent") + .then((r) => (r.ok ? r.json() : null)) + .then((data) => { + setIsQbConfigured(!!(data && data.url && data.url.trim() && data.username && data.username.trim())); + }) + .catch(() => setIsQbConfigured(false)); + } + }, [initialProwlarrConfigured, initialQbConfigured]); const [searchInput, setSearchInput] = useState(`"${seriesName}"`);