refactor: streamline ClientLayout and PreferencesContext for improved state management and debugging

This commit is contained in:
Julien Froidefond
2025-10-24 17:17:35 +02:00
parent 58cabd9cf2
commit 931185f0f0
7 changed files with 63 additions and 33 deletions

View File

@@ -7,7 +7,6 @@ import { PreferencesService } from "@/lib/services/preferences.service";
import { PreferencesProvider } from "@/contexts/PreferencesContext"; import { PreferencesProvider } from "@/contexts/PreferencesContext";
import { I18nProvider } from "@/components/providers/I18nProvider"; import { I18nProvider } from "@/components/providers/I18nProvider";
import { AuthProvider } from "@/components/providers/AuthProvider"; import { AuthProvider } from "@/components/providers/AuthProvider";
import "@/i18n/i18n"; // Import i18next configuration
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { defaultPreferences } from "@/types/preferences"; import { defaultPreferences } from "@/types/preferences";
import type { UserPreferences } from "@/types/preferences"; import type { UserPreferences } from "@/types/preferences";

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from "next-themes";
import { useState, useEffect, useMemo, useCallback } from "react"; import { useState, useEffect, useMemo, useCallback, useRef } from "react";
import { Header } from "@/components/layout/Header"; import { Header } from "@/components/layout/Header";
import { Sidebar } from "@/components/layout/Sidebar"; import { Sidebar } from "@/components/layout/Sidebar";
import { InstallPWA } from "../ui/InstallPWA"; import { InstallPWA } from "../ui/InstallPWA";
@@ -28,17 +28,35 @@ export default function ClientLayout({ children, initialLibraries = [], initialF
const [randomBookId, setRandomBookId] = useState<string | null>(null); const [randomBookId, setRandomBookId] = useState<string | null>(null);
const pathname = usePathname(); const pathname = usePathname();
const { preferences } = usePreferences(); const { preferences } = usePreferences();
const prevLibraryIdsRef = useRef<string>("");
const backgroundType = preferences.background.type;
const komgaLibraries = preferences.background.komgaLibraries;
// Debug: log renders
useEffect(() => {
console.log('🎨 [ClientLayout] Render:', {
pathname,
backgroundType,
hasLibraries: !!komgaLibraries?.length,
preferencesKeys: Object.keys(preferences)
});
});
// Stabiliser libraryIds - ne change que si le contenu change vraiment
const libraryIdsString = useMemo(() => {
const newIds = komgaLibraries?.join(",") || "";
if (newIds !== prevLibraryIdsRef.current) {
prevLibraryIdsRef.current = newIds;
}
return prevLibraryIdsRef.current;
}, [komgaLibraries]);
// Récupérer un book aléatoire pour le background // Récupérer un book aléatoire pour le background
const fetchRandomBook = useCallback(async () => { const fetchRandomBook = useCallback(async () => {
if ( if (backgroundType === "komga-random" && libraryIdsString) {
preferences.background.type === "komga-random" &&
preferences.background.komgaLibraries &&
preferences.background.komgaLibraries.length > 0
) {
try { try {
const libraryIds = preferences.background.komgaLibraries.join(","); const response = await fetch(`/api/komga/random-book?libraryIds=${libraryIdsString}`);
const response = await fetch(`/api/komga/random-book?libraryIds=${libraryIds}`);
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
setRandomBookId(data.bookId); setRandomBookId(data.bookId);
@@ -47,11 +65,13 @@ export default function ClientLayout({ children, initialLibraries = [], initialF
console.error("Erreur lors de la récupération d'un book aléatoire:", error); console.error("Erreur lors de la récupération d'un book aléatoire:", error);
} }
} }
}, [preferences.background.type, preferences.background.komgaLibraries]); }, [backgroundType, libraryIdsString]);
useEffect(() => { useEffect(() => {
if (backgroundType === "komga-random" && libraryIdsString) {
fetchRandomBook(); fetchRandomBook();
}, [fetchRandomBook]); }
}, [backgroundType, libraryIdsString, fetchRandomBook]);
const backgroundStyle = useMemo(() => { const backgroundStyle = useMemo(() => {
const bg = preferences.background; const bg = preferences.background;

View File

@@ -3,7 +3,7 @@
import { CoverClient } from "./cover-client"; import { CoverClient } from "./cover-client";
import { ProgressBar } from "./progress-bar"; import { ProgressBar } from "./progress-bar";
import type { BookCoverProps } from "./cover-utils"; import type { BookCoverProps } from "./cover-utils";
import { getImageUrl } from "./cover-utils"; import { getImageUrl } from "@/lib/utils/image-url";
import { useImageUrl } from "@/hooks/useImageUrl"; import { useImageUrl } from "@/hooks/useImageUrl";
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service"; import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
import { MarkAsReadButton } from "./mark-as-read-button"; import { MarkAsReadButton } from "./mark-as-read-button";

View File

@@ -19,14 +19,3 @@ export interface BookCoverProps extends BaseCoverProps {
export interface SeriesCoverProps extends BaseCoverProps { export interface SeriesCoverProps extends BaseCoverProps {
series: KomgaSeries; series: KomgaSeries;
} }
/**
* Génère l'URL de base pour une image (sans cache version)
* Utilisez useImageUrl() dans les composants pour obtenir l'URL avec cache busting
*/
export function getImageUrl(type: "series" | "book", id: string) {
if (type === "series") {
return `/api/komga/images/series/${id}/thumbnail`;
}
return `/api/komga/images/books/${id}/thumbnail`;
}

View File

@@ -3,7 +3,7 @@
import { CoverClient } from "./cover-client"; import { CoverClient } from "./cover-client";
import { ProgressBar } from "./progress-bar"; import { ProgressBar } from "./progress-bar";
import type { SeriesCoverProps } from "./cover-utils"; import type { SeriesCoverProps } from "./cover-utils";
import { getImageUrl } from "./cover-utils"; import { getImageUrl } from "@/lib/utils/image-url";
import { useImageUrl } from "@/hooks/useImageUrl"; import { useImageUrl } from "@/hooks/useImageUrl";
export function SeriesCover({ export function SeriesCover({

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import React, { createContext, useContext, useState, useEffect } from "react"; import React, { createContext, useContext, useState, useEffect, useMemo, useCallback } from "react";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { ERROR_CODES } from "../constants/errorCodes"; import { ERROR_CODES } from "../constants/errorCodes";
import { AppError } from "../utils/errors"; import { AppError } from "../utils/errors";
@@ -27,6 +27,12 @@ export function PreferencesProvider({
initialPreferences || defaultPreferences initialPreferences || defaultPreferences
); );
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [hasLoadedPrefs, setHasLoadedPrefs] = useState(!!initialPreferences);
// Debug: log state changes
useEffect(() => {
console.log('⚙️ [PreferencesContext] Update:', { status, hasLoadedPrefs, isLoading });
}, [status, hasLoadedPrefs, isLoading]);
const fetchPreferences = async () => { const fetchPreferences = async () => {
try { try {
@@ -39,6 +45,7 @@ export function PreferencesProvider({
...defaultPreferences, ...defaultPreferences,
...data, ...data,
}); });
setHasLoadedPrefs(true);
} catch (error) { } catch (error) {
console.error("Erreur lors de la récupération des préférences:", error); console.error("Erreur lors de la récupération des préférences:", error);
setPreferences(defaultPreferences); setPreferences(defaultPreferences);
@@ -49,15 +56,16 @@ export function PreferencesProvider({
useEffect(() => { useEffect(() => {
// Recharger les préférences quand la session change (connexion/déconnexion) // Recharger les préférences quand la session change (connexion/déconnexion)
if (status === "authenticated") { if (status === "authenticated" && !hasLoadedPrefs) {
fetchPreferences(); fetchPreferences();
} else if (status === "unauthenticated") { } else if (status === "unauthenticated") {
// Réinitialiser aux préférences par défaut quand l'utilisateur se déconnecte // Réinitialiser aux préférences par défaut quand l'utilisateur se déconnecte
setPreferences(defaultPreferences); setPreferences(defaultPreferences);
setHasLoadedPrefs(false);
} }
}, [status]); }, [status, hasLoadedPrefs]);
const updatePreferences = async (newPreferences: Partial<UserPreferences>) => { const updatePreferences = useCallback(async (newPreferences: Partial<UserPreferences>) => {
try { try {
const response = await fetch("/api/preferences", { const response = await fetch("/api/preferences", {
method: "PUT", method: "PUT",
@@ -78,17 +86,20 @@ export function PreferencesProvider({
...updatedPreferences, ...updatedPreferences,
})); }));
await fetchPreferences();
return updatedPreferences; return updatedPreferences;
} catch (error) { } catch (error) {
console.error("Erreur lors de la mise à jour des préférences:", error); console.error("Erreur lors de la mise à jour des préférences:", error);
throw error; throw error;
} }
}; }, []);
const contextValue = useMemo(
() => ({ preferences, updatePreferences, isLoading }),
[preferences, updatePreferences, isLoading]
);
return ( return (
<PreferencesContext.Provider value={{ preferences, updatePreferences, isLoading }}> <PreferencesContext.Provider value={contextValue}>
{children} {children}
</PreferencesContext.Provider> </PreferencesContext.Provider>
); );

View File

@@ -0,0 +1,11 @@
/**
* Génère l'URL de base pour une image (sans cache version)
* Utilisez useImageUrl() dans les composants pour obtenir l'URL avec cache busting
*/
export function getImageUrl(type: "series" | "book", id: string) {
if (type === "series") {
return `/api/komga/images/series/${id}/thumbnail`;
}
return `/api/komga/images/books/${id}/thumbnail`;
}