refactor: make library rendering server-first and deterministic
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:
2026-02-28 14:06:27 +01:00
parent 26021ea907
commit 01951c806d
14 changed files with 264 additions and 154 deletions

View File

@@ -8,12 +8,11 @@ import { useState, useEffect, useCallback } from "react";
import type { KomgaSeries } from "@/types/komga";
import { SearchInput } from "./SearchInput";
import { useTranslate } from "@/hooks/useTranslate";
import { useDisplayPreferences } from "@/hooks/useDisplayPreferences";
import { usePreferences } from "@/contexts/PreferencesContext";
import { PageSizeSelect } from "@/components/common/PageSizeSelect";
import { CompactModeButton } from "@/components/common/CompactModeButton";
import { ViewModeButton } from "@/components/common/ViewModeButton";
import { UnreadFilterButton } from "@/components/common/UnreadFilterButton";
import { updatePreferences as updatePreferencesAction } from "@/app/actions/preferences";
interface PaginatedSeriesGridProps {
series: KomgaSeries[];
@@ -23,6 +22,8 @@ interface PaginatedSeriesGridProps {
defaultShowOnlyUnread: boolean;
showOnlyUnread: boolean;
pageSize?: number;
initialCompact: boolean;
initialViewMode: "grid" | "list";
}
export function PaginatedSeriesGrid({
@@ -33,18 +34,28 @@ export function PaginatedSeriesGrid({
defaultShowOnlyUnread,
showOnlyUnread: initialShowOnlyUnread,
pageSize,
initialCompact,
initialViewMode,
}: PaginatedSeriesGridProps) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [showOnlyUnread, setShowOnlyUnread] = useState(initialShowOnlyUnread);
const { isCompact, itemsPerPage: displayItemsPerPage, viewMode } = useDisplayPreferences();
const { updatePreferences } = usePreferences();
const [isCompact, setIsCompact] = useState(initialCompact);
const [viewMode, setViewMode] = useState<"grid" | "list">(initialViewMode);
const [currentPageSize, setCurrentPageSize] = useState(pageSize || 20);
// Utiliser la taille de page effective (depuis l'URL ou les préférences)
const effectivePageSize = pageSize || displayItemsPerPage;
const effectivePageSize = pageSize || currentPageSize;
const { t } = useTranslate();
const persistPreferences = useCallback(async (payload: Parameters<typeof updatePreferencesAction>[0]) => {
try {
await updatePreferencesAction(payload);
} catch (error) {
console.error("Erreur lors de la sauvegarde des préférences:", error);
}
}, []);
const updateUrlParams = useCallback(
async (updates: Record<string, string | null>, replace: boolean = false) => {
const params = new URLSearchParams(searchParams.toString());
@@ -71,6 +82,18 @@ export function PaginatedSeriesGrid({
setShowOnlyUnread(initialShowOnlyUnread);
}, [initialShowOnlyUnread]);
useEffect(() => {
setIsCompact(initialCompact);
}, [initialCompact]);
useEffect(() => {
setViewMode(initialViewMode);
}, [initialViewMode]);
useEffect(() => {
setCurrentPageSize(pageSize || 20);
}, [pageSize]);
// Apply default filter on initial load
useEffect(() => {
if (defaultShowOnlyUnread && !searchParams.has("unread")) {
@@ -89,20 +112,47 @@ export function PaginatedSeriesGrid({
page: "1",
unread: newUnreadState ? "true" : "false",
});
// Sauvegarder la préférence dans la base de données
try {
await updatePreferences({ showOnlyUnread: newUnreadState });
} catch (error) {
// Log l'erreur mais ne bloque pas l'utilisateur
console.error("Erreur lors de la sauvegarde de la préférence:", error);
}
await persistPreferences({ showOnlyUnread: newUnreadState });
};
const handlePageSizeChange = async (size: number) => {
setCurrentPageSize(size);
await updateUrlParams({
page: "1",
size: size.toString(),
});
await persistPreferences({
displayMode: {
compact: isCompact,
itemsPerPage: size,
viewMode,
},
});
};
const handleCompactModeToggle = async (nextCompactMode: boolean) => {
setIsCompact(nextCompactMode);
await persistPreferences({
displayMode: {
compact: nextCompactMode,
itemsPerPage: effectivePageSize,
viewMode,
},
});
};
const handleViewModeToggle = async (nextViewMode: "grid" | "list") => {
setViewMode(nextViewMode);
await persistPreferences({
displayMode: {
compact: isCompact,
itemsPerPage: effectivePageSize,
viewMode: nextViewMode,
},
});
};
// Calculate start and end indices for display
@@ -128,9 +178,9 @@ export function PaginatedSeriesGrid({
<SearchInput placeholder={t("series.filters.search")} />
</div>
<div className="flex items-center justify-end gap-2">
<PageSizeSelect onSizeChange={handlePageSizeChange} />
<ViewModeButton />
<CompactModeButton />
<PageSizeSelect pageSize={effectivePageSize} onSizeChange={handlePageSizeChange} />
<ViewModeButton viewMode={viewMode} onToggle={handleViewModeToggle} />
<CompactModeButton isCompact={isCompact} onToggle={handleCompactModeToggle} />
<UnreadFilterButton showOnlyUnread={showOnlyUnread} onToggle={handleUnreadFilter} />
</div>
</div>