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:
@@ -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 { BooksGrid, EmptyState } from "@/app/components/BookCard";
|
||||||
import { MarkSeriesReadButton } from "@/app/components/MarkSeriesReadButton";
|
import { MarkSeriesReadButton } from "@/app/components/MarkSeriesReadButton";
|
||||||
import { MarkBookReadButton } from "@/app/components/MarkBookReadButton";
|
import { MarkBookReadButton } from "@/app/components/MarkBookReadButton";
|
||||||
@@ -41,7 +41,7 @@ export default async function SeriesDetailPage({
|
|||||||
|
|
||||||
const seriesName = decodeURIComponent(name);
|
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)),
|
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[],
|
||||||
@@ -52,6 +52,12 @@ export default async function SeriesDetailPage({
|
|||||||
fetchSeriesMetadata(id, seriesName).catch(() => null as SeriesMetadataDto | null),
|
fetchSeriesMetadata(id, seriesName).catch(() => null as SeriesMetadataDto | null),
|
||||||
getMetadataLink(id, seriesName).catch(() => [] as ExternalMetadataLinkDto[]),
|
getMetadataLink(id, seriesName).catch(() => [] as ExternalMetadataLinkDto[]),
|
||||||
getReadingStatusLink(id, seriesName).catch(() => null as AnilistSeriesLinkDto | null),
|
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;
|
const existingLink = metadataLinks.find((l) => l.status === "approved") ?? metadataLinks[0] ?? null;
|
||||||
@@ -235,6 +241,8 @@ export default async function SeriesDetailPage({
|
|||||||
<ProwlarrSearchModal
|
<ProwlarrSearchModal
|
||||||
seriesName={seriesName}
|
seriesName={seriesName}
|
||||||
missingBooks={missingData?.missing_books ?? null}
|
missingBooks={missingData?.missing_books ?? null}
|
||||||
|
initialProwlarrConfigured={prowlarrConfigured}
|
||||||
|
initialQbConfigured={qbConfigured}
|
||||||
/>
|
/>
|
||||||
<MetadataSearchModal
|
<MetadataSearchModal
|
||||||
libraryId={id}
|
libraryId={id}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ interface MissingBookItem {
|
|||||||
interface ProwlarrSearchModalProps {
|
interface ProwlarrSearchModalProps {
|
||||||
seriesName: string;
|
seriesName: string;
|
||||||
missingBooks: MissingBookItem[] | null;
|
missingBooks: MissingBookItem[] | null;
|
||||||
|
initialProwlarrConfigured?: boolean;
|
||||||
|
initialQbConfigured?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSize(bytes: number): string {
|
function formatSize(bytes: number): string {
|
||||||
@@ -24,36 +26,41 @@ function formatSize(bytes: number): string {
|
|||||||
return bytes + " B";
|
return bytes + " B";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProwlarrSearchModal({ seriesName, missingBooks }: ProwlarrSearchModalProps) {
|
export function ProwlarrSearchModal({ seriesName, missingBooks, initialProwlarrConfigured, initialQbConfigured }: ProwlarrSearchModalProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
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 [isSearching, setIsSearching] = useState(false);
|
||||||
const [results, setResults] = useState<ProwlarrRelease[]>([]);
|
const [results, setResults] = useState<ProwlarrRelease[]>([]);
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// qBittorrent state
|
// qBittorrent state
|
||||||
const [isQbConfigured, setIsQbConfigured] = useState(false);
|
const [isQbConfigured, setIsQbConfigured] = useState(initialQbConfigured ?? false);
|
||||||
const [sendingGuid, setSendingGuid] = useState<string | null>(null);
|
const [sendingGuid, setSendingGuid] = useState<string | null>(null);
|
||||||
const [sentGuids, setSentGuids] = useState<Set<string>>(new Set());
|
const [sentGuids, setSentGuids] = useState<Set<string>>(new Set());
|
||||||
const [sendError, setSendError] = useState<string | null>(null);
|
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(() => {
|
useEffect(() => {
|
||||||
|
if (initialProwlarrConfigured !== undefined && initialQbConfigured !== undefined) return;
|
||||||
|
if (initialProwlarrConfigured === undefined) {
|
||||||
fetch("/api/settings/prowlarr")
|
fetch("/api/settings/prowlarr")
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setIsConfigured(!!(data && data.api_key && data.api_key.trim()));
|
setIsConfigured(!!(data && data.api_key && data.api_key.trim()));
|
||||||
})
|
})
|
||||||
.catch(() => setIsConfigured(false));
|
.catch(() => setIsConfigured(false));
|
||||||
|
}
|
||||||
|
if (initialQbConfigured === undefined) {
|
||||||
fetch("/api/settings/qbittorrent")
|
fetch("/api/settings/qbittorrent")
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setIsQbConfigured(!!(data && data.url && data.url.trim() && data.username && data.username.trim()));
|
setIsQbConfigured(!!(data && data.url && data.url.trim() && data.username && data.username.trim()));
|
||||||
})
|
})
|
||||||
.catch(() => setIsQbConfigured(false));
|
.catch(() => setIsQbConfigured(false));
|
||||||
}, []);
|
}
|
||||||
|
}, [initialProwlarrConfigured, initialQbConfigured]);
|
||||||
|
|
||||||
const [searchInput, setSearchInput] = useState(`"${seriesName}"`);
|
const [searchInput, setSearchInput] = useState(`"${seriesName}"`);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user