fix: supprime le layout shift du bouton Prowlarr sur la page série

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) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 08:44:19 +01:00
parent f5ddeb461b
commit 2dad295451
2 changed files with 34 additions and 19 deletions

View File

@@ -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({
<ProwlarrSearchModal
seriesName={seriesName}
missingBooks={missingData?.missing_books ?? null}
initialProwlarrConfigured={prowlarrConfigured}
initialQbConfigured={qbConfigured}
/>
<MetadataSearchModal
libraryId={id}

View File

@@ -15,6 +15,8 @@ interface MissingBookItem {
interface ProwlarrSearchModalProps {
seriesName: string;
missingBooks: MissingBookItem[] | null;
initialProwlarrConfigured?: boolean;
initialQbConfigured?: boolean;
}
function formatSize(bytes: number): string {
@@ -24,36 +26,41 @@ function formatSize(bytes: number): string {
return bytes + " B";
}
export function ProwlarrSearchModal({ seriesName, missingBooks }: ProwlarrSearchModalProps) {
export function ProwlarrSearchModal({ seriesName, missingBooks, initialProwlarrConfigured, initialQbConfigured }: ProwlarrSearchModalProps) {
const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const [isConfigured, setIsConfigured] = useState<boolean | null>(null);
const [isConfigured, setIsConfigured] = useState<boolean | null>(initialProwlarrConfigured ?? null);
const [isSearching, setIsSearching] = useState(false);
const [results, setResults] = useState<ProwlarrRelease[]>([]);
const [query, setQuery] = useState("");
const [error, setError] = useState<string | null>(null);
// qBittorrent state
const [isQbConfigured, setIsQbConfigured] = useState(false);
const [isQbConfigured, setIsQbConfigured] = useState(initialQbConfigured ?? false);
const [sendingGuid, setSendingGuid] = useState<string | null>(null);
const [sentGuids, setSentGuids] = useState<Set<string>>(new Set());
const [sendError, setSendError] = useState<string | null>(null);
// Check if Prowlarr and qBittorrent are configured on mount
// Check if Prowlarr and qBittorrent are configured on mount (skip if server provided)
useEffect(() => {
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}"`);