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:
@@ -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}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user