refactor: make library rendering server-first and deterministic
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m7s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m7s
Move library header/covers to deterministic server-side rendering, split preference controls into controlled/uncontrolled modes, and remove client cover wrapper to eliminate hydration mismatches and provider coupling on library pages.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { CoverClient } from "./cover-client";
|
||||
import { ProgressBar } from "./progress-bar";
|
||||
import type { BookCoverProps } from "./cover-utils";
|
||||
import { getImageUrl } from "@/lib/utils/image-url";
|
||||
@@ -70,8 +69,7 @@ export function BookCover({
|
||||
|
||||
const currentPage = ClientOfflineBookService.getCurrentPage(book);
|
||||
const totalPages = book.media.pagesCount;
|
||||
const showProgress =
|
||||
showProgressUi && currentPage && totalPages && currentPage > 0 && !isCompleted;
|
||||
const showProgress = Boolean(showProgressUi && totalPages > 0 && currentPage > 0 && !isCompleted);
|
||||
|
||||
const statusInfo = getReadingStatusInfo(book, t);
|
||||
const isRead = book.readProgress?.completed || false;
|
||||
@@ -91,11 +89,17 @@ export function BookCover({
|
||||
return (
|
||||
<>
|
||||
<div className={`relative w-full h-full ${isUnavailable ? "opacity-40 grayscale" : ""}`}>
|
||||
<CoverClient
|
||||
imageUrl={imageUrl}
|
||||
<img
|
||||
src={imageUrl.trim()}
|
||||
alt={alt || t("books.defaultCoverAlt")}
|
||||
className={className}
|
||||
isCompleted={isCompleted}
|
||||
loading="lazy"
|
||||
className={[
|
||||
"absolute inset-0 w-full h-full object-cover rounded-lg",
|
||||
isCompleted ? "opacity-50" : "",
|
||||
className || "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
/>
|
||||
{showProgress && <ProgressBar progress={currentPage} total={totalPages} type="book" />}
|
||||
{/* Badge hors ligne si non accessible */}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ImageLoader } from "@/components/ui/image-loader";
|
||||
|
||||
interface CoverClientProps {
|
||||
imageUrl: string;
|
||||
alt: string;
|
||||
className?: string;
|
||||
isCompleted?: boolean;
|
||||
}
|
||||
|
||||
export const CoverClient = ({
|
||||
imageUrl,
|
||||
alt,
|
||||
className,
|
||||
isCompleted = false,
|
||||
}: CoverClientProps) => {
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const img = imgRef.current;
|
||||
if (img?.complete && img.naturalWidth > 0) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
<ImageLoader isLoading={isLoading} />
|
||||
<img
|
||||
ref={imgRef}
|
||||
src={imageUrl}
|
||||
alt={alt}
|
||||
loading="lazy"
|
||||
className={cn(
|
||||
"absolute inset-0 w-full h-full object-cover rounded-lg",
|
||||
isCompleted && "opacity-50",
|
||||
className
|
||||
)}
|
||||
onLoad={() => setIsLoading(false)}
|
||||
onError={() => setIsLoading(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { CoverClient } from "./cover-client";
|
||||
import { ProgressBar } from "./progress-bar";
|
||||
import type { SeriesCoverProps } from "./cover-utils";
|
||||
import { getImageUrl } from "@/lib/utils/image-url";
|
||||
@@ -16,12 +13,23 @@ export function SeriesCover({
|
||||
|
||||
const readBooks = series.booksReadCount;
|
||||
const totalBooks = series.booksCount;
|
||||
const showProgress = showProgressUi && readBooks && totalBooks && readBooks > 0 && !isCompleted;
|
||||
const showProgress = Boolean(showProgressUi && totalBooks > 0 && readBooks > 0 && !isCompleted);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full">
|
||||
<CoverClient imageUrl={imageUrl} alt={alt} className={className} isCompleted={isCompleted} />
|
||||
{showProgress && <ProgressBar progress={readBooks} total={totalBooks} type="series" />}
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt={alt}
|
||||
loading="lazy"
|
||||
className={[
|
||||
"absolute inset-0 w-full h-full object-cover rounded-lg",
|
||||
isCompleted ? "opacity-50" : "",
|
||||
className || "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
/>
|
||||
{showProgress ? <ProgressBar progress={readBooks} total={totalBooks} type="series" /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user