fix: SSR pour les providers cachés dans MetadataSearchModal

Les metadata providers sont récupérés côté serveur et les providers
sans API key sont passés en prop initialHiddenProviders, supprimant
le fetch client useEffect qui causait un layout shift.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 09:13:38 +01:00
parent e7295a371d
commit e078b0029f
2 changed files with 9 additions and 16 deletions

View File

@@ -41,7 +41,7 @@ export default async function SeriesDetailPage({
const seriesName = decodeURIComponent(name); const seriesName = decodeURIComponent(name);
const [library, booksPage, seriesMeta, metadataLinks, readingStatusLink, prowlarrConfigured, qbConfigured] = await Promise.all([ const [library, booksPage, seriesMeta, metadataLinks, readingStatusLink, prowlarrConfigured, qbConfigured, metadataProviders] = await Promise.all([
fetchLibraries().then((libs) => libs.find((l) => l.id === id)), fetchLibraries().then((libs) => libs.find((l) => l.id === id)),
fetchBooks(id, seriesName, page, limit).catch(() => ({ fetchBooks(id, seriesName, page, limit).catch(() => ({
items: [] as BookDto[], items: [] as BookDto[],
@@ -58,8 +58,12 @@ export default async function SeriesDetailPage({
apiFetch<{ url?: string; username?: string }>("/settings/qbittorrent") apiFetch<{ url?: string; username?: string }>("/settings/qbittorrent")
.then(d => !!(d?.url?.trim() && d?.username?.trim())) .then(d => !!(d?.url?.trim() && d?.username?.trim()))
.catch(() => false), .catch(() => false),
apiFetch<{ comicvine?: { api_key?: string } }>("/settings/metadata_providers").catch(() => null),
]); ]);
const hiddenProviders: string[] = [];
if (!metadataProviders?.comicvine?.api_key) hiddenProviders.push("comicvine");
const existingLink = metadataLinks.find((l) => l.status === "approved") ?? metadataLinks[0] ?? null; const existingLink = metadataLinks.find((l) => l.status === "approved") ?? metadataLinks[0] ?? null;
let missingData: MissingBooksDto | null = null; let missingData: MissingBooksDto | null = null;
if (existingLink && existingLink.status === "approved") { if (existingLink && existingLink.status === "approved") {
@@ -249,6 +253,7 @@ export default async function SeriesDetailPage({
seriesName={seriesName} seriesName={seriesName}
existingLink={existingLink} existingLink={existingLink}
initialMissing={missingData} initialMissing={missingData}
initialHiddenProviders={hiddenProviders}
/> />
<ReadingStatusModal <ReadingStatusModal
libraryId={id} libraryId={id}

View File

@@ -27,6 +27,7 @@ interface MetadataSearchModalProps {
seriesName: string; seriesName: string;
existingLink: ExternalMetadataLinkDto | null; existingLink: ExternalMetadataLinkDto | null;
initialMissing: MissingBooksDto | null; initialMissing: MissingBooksDto | null;
initialHiddenProviders?: string[];
} }
type ModalStep = "idle" | "searching" | "results" | "confirm" | "syncing" | "done" | "linked"; type ModalStep = "idle" | "searching" | "results" | "confirm" | "syncing" | "done" | "linked";
@@ -36,6 +37,7 @@ export function MetadataSearchModal({
seriesName, seriesName,
existingLink, existingLink,
initialMissing, initialMissing,
initialHiddenProviders,
}: MetadataSearchModalProps) { }: MetadataSearchModalProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
@@ -61,21 +63,7 @@ export function MetadataSearchModal({
// Provider selector: empty string = library default // Provider selector: empty string = library default
const [searchProvider, setSearchProvider] = useState(""); const [searchProvider, setSearchProvider] = useState("");
const [activeProvider, setActiveProvider] = useState(""); const [activeProvider, setActiveProvider] = useState("");
const [hiddenProviders, setHiddenProviders] = useState<Set<string>>(new Set()); const [hiddenProviders] = useState<Set<string>>(new Set(initialHiddenProviders ?? []));
// Fetch metadata provider settings to hide providers without required API keys
useEffect(() => {
fetch("/api/settings/metadata_providers")
.then((r) => r.ok ? r.json() : null)
.then((data) => {
if (!data) return;
const hidden = new Set<string>();
// ComicVine requires an API key
if (!data.comicvine?.api_key) hidden.add("comicvine");
setHiddenProviders(hidden);
})
.catch(() => {});
}, []);
const visibleProviders = PROVIDERS.filter((p) => !hiddenProviders.has(p.value)); const visibleProviders = PROVIDERS.filter((p) => !hiddenProviders.has(p.value));