From dd2be14bffd0a6d7416b1a38e11e67ac93a88deb Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Wed, 5 Mar 2025 09:23:02 +0100 Subject: [PATCH] feat: enhance sidebar data loading with initial preferences and libraries --- src/app/layout.tsx | 48 +++++++++++++++++++++++- src/components/layout/ClientLayout.tsx | 21 ++++++++--- src/components/layout/Sidebar.tsx | 47 +++++++++-------------- src/components/layout/SidebarWrapper.tsx | 16 -------- src/contexts/PreferencesContext.tsx | 20 ++++++---- 5 files changed, 92 insertions(+), 60 deletions(-) delete mode 100644 src/components/layout/SidebarWrapper.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c323d9e..92400bc 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,10 +3,17 @@ import { Inter } from "next/font/google"; import "@/styles/globals.css"; import { cn } from "@/lib/utils"; import ClientLayout from "@/components/layout/ClientLayout"; +import { PreferencesService } from "@/lib/services/preferences.service"; import { PreferencesProvider } from "@/contexts/PreferencesContext"; import { I18nProvider } from "@/components/providers/I18nProvider"; import "@/i18n/i18n"; // Import i18next configuration import { cookies } from "next/headers"; +import { defaultPreferences } from "@/types/preferences"; +import type { UserPreferences } from "@/types/preferences"; +import type { KomgaLibrary, KomgaSeries } from "@/types/komga"; +import { FavoriteService } from "@/lib/services/favorite.service"; +import { LibraryService } from "@/lib/services/library.service"; +import { SeriesService } from "@/lib/services/series.service"; const inter = Inter({ subsets: ["latin"] }); @@ -62,6 +69,41 @@ export default async function RootLayout({ children }: { children: React.ReactNo const cookieStore = await cookies(); const locale = cookieStore.get("NEXT_LOCALE")?.value || "fr"; + // Récupération des données pour la sidebar côté serveur + let libraries: KomgaLibrary[] = []; + let favorites: KomgaSeries[] = []; + let preferences: UserPreferences = defaultPreferences; + + try { + // Tentative de chargement des données. Si l'utilisateur n'est pas authentifié, + // les services lanceront une erreur mais l'application continuera de fonctionner + const [librariesData, favoritesData, preferencesData] = await Promise.allSettled([ + LibraryService.getLibraries(), + FavoriteService.getAllFavoriteIds(), + PreferencesService.getPreferences(), + ]); + + if (librariesData.status === "fulfilled") { + libraries = librariesData.value; + } + + if (favoritesData.status === "fulfilled") { + favorites = await SeriesService.getMultipleSeries(favoritesData.value); + } + + if (preferencesData.status === "fulfilled") { + const { showThumbnails, cacheMode, showOnlyUnread, debug } = preferencesData.value; + preferences = { + showThumbnails, + cacheMode, + showOnlyUnread, + debug, + }; + } + } catch (error) { + console.error("Erreur lors du chargement des données de la sidebar:", error); + } + return ( @@ -123,8 +165,10 @@ export default async function RootLayout({ children }: { children: React.ReactNo className={cn("min-h-screen bg-background font-sans antialiased h-full", inter.className)} > - - {children} + + + {children} + diff --git a/src/components/layout/ClientLayout.tsx b/src/components/layout/ClientLayout.tsx index 924c968..f7aa78f 100644 --- a/src/components/layout/ClientLayout.tsx +++ b/src/components/layout/ClientLayout.tsx @@ -7,16 +7,22 @@ import { Sidebar } from "@/components/layout/Sidebar"; import { InstallPWA } from "../ui/InstallPWA"; import { Toaster } from "@/components/ui/toaster"; import { usePathname } from "next/navigation"; -import { PreferencesProvider } from "@/contexts/PreferencesContext"; import { registerServiceWorker } from "@/lib/registerSW"; import { NetworkStatus } from "../ui/NetworkStatus"; import { LoadingBar } from "@/components/ui/loading-bar"; import { DebugWrapper } from "@/components/debug/DebugWrapper"; +import type { KomgaLibrary, KomgaSeries } from "@/types/komga"; // Routes qui ne nécessitent pas d'authentification const publicRoutes = ["/login", "/register"]; -export default function ClientLayout({ children }: { children: React.ReactNode }) { +interface ClientLayoutProps { + children: React.ReactNode; + initialLibraries: KomgaLibrary[]; + initialFavorites: KomgaSeries[]; +} + +export default function ClientLayout({ children, initialLibraries = [], initialFavorites = [] }: ClientLayoutProps) { const [isSidebarOpen, setIsSidebarOpen] = useState(false); const pathname = usePathname(); @@ -63,18 +69,23 @@ export default function ClientLayout({ children }: { children: React.ReactNode } return ( -
{!isPublicRoute &&
} - {!isPublicRoute && } + {!isPublicRoute && ( + + )}
{children}
-
); } diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 7543a10..7e69c90 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -16,23 +16,23 @@ import { useTranslate } from "@/hooks/useTranslate"; interface SidebarProps { isOpen: boolean; onClose: () => void; + initialLibraries: KomgaLibrary[]; + initialFavorites: KomgaSeries[]; } -export function Sidebar({ isOpen, onClose }: SidebarProps) { +export function Sidebar({ isOpen, onClose, initialLibraries, initialFavorites }: SidebarProps) { const { t } = useTranslate(); const pathname = usePathname(); const router = useRouter(); const { preferences } = usePreferences(); - const [libraries, setLibraries] = useState([]); - const [favorites, setFavorites] = useState([]); - const [isLoading, setIsLoading] = useState(true); + const [libraries, setLibraries] = useState(initialLibraries || []); + const [favorites, setFavorites] = useState(initialFavorites || []); const [isRefreshing, setIsRefreshing] = useState(false); - const [isLoadingFavorites, setIsLoadingFavorites] = useState(true); const { toast } = useToast(); - const fetchLibraries = useCallback(async () => { - setIsLoading(true); + const refreshLibraries = useCallback(async () => { + setIsRefreshing(true); try { const response = await fetch("/api/komga/libraries"); if (!response.ok) { @@ -50,15 +50,12 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { : getErrorMessage(ERROR_CODES.LIBRARY.FETCH_ERROR), variant: "destructive", }); - setLibraries([]); } finally { - setIsLoading(false); setIsRefreshing(false); } }, [toast]); - const fetchFavorites = useCallback(async () => { - setIsLoadingFavorites(true); + const refreshFavorites = useCallback(async () => { try { const favoritesResponse = await fetch("/api/komga/favorites"); if (!favoritesResponse.ok) { @@ -91,28 +88,20 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { : getErrorMessage(ERROR_CODES.FAVORITE.FETCH_ERROR), variant: "destructive", }); - setFavorites([]); - } finally { - setIsLoadingFavorites(false); } }, [toast]); - // Chargement initial des données useEffect(() => { - fetchLibraries(); - fetchFavorites(); - }, [fetchLibraries, fetchFavorites]); - - // Rafraîchir les données quand les préférences changent - useEffect(() => { - fetchLibraries(); - fetchFavorites(); - }, [preferences, fetchLibraries, fetchFavorites]); + if (Object.keys(preferences).length > 0) { + refreshLibraries(); + refreshFavorites(); + } + }, [preferences, refreshLibraries, refreshFavorites]); // Mettre à jour les favoris quand ils changent useEffect(() => { const handleFavoritesChange = () => { - fetchFavorites(); + refreshFavorites(); }; window.addEventListener("favoritesChanged", handleFavoritesChange); @@ -120,11 +109,11 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { return () => { window.removeEventListener("favoritesChanged", handleFavoritesChange); }; - }, [fetchFavorites]); + }, [refreshFavorites]); const handleRefresh = async () => { setIsRefreshing(true); - await Promise.all([fetchLibraries(), fetchFavorites()]); + await Promise.all([refreshLibraries(), refreshFavorites()]); }; const handleLogout = async () => { @@ -217,7 +206,7 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { {favorites.length} - {isLoadingFavorites ? ( + {isRefreshing ? (
{t("sidebar.favorites.loading")}
@@ -258,7 +247,7 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) { - {isLoading ? ( + {isRefreshing ? (
{t("sidebar.libraries.loading")}
diff --git a/src/components/layout/SidebarWrapper.tsx b/src/components/layout/SidebarWrapper.tsx deleted file mode 100644 index 6ac09a2..0000000 --- a/src/components/layout/SidebarWrapper.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { FavoriteService } from "@/lib/services/favorite.service"; -import { LibraryService } from "@/lib/services/library.service"; -import { SeriesService } from "@/lib/services/series.service"; - -export async function SidebarWrapper() { - // Récupérer les favoris depuis le serveur - const favoriteIds = await FavoriteService.getAllFavoriteIds(); - - // Récupérer les détails des séries favorites - const favorites = await SeriesService.getMultipleSeries(favoriteIds); - - // Récupérer les bibliothèques - const libraries = await LibraryService.getLibraries(); - - return { favorites, libraries }; -} diff --git a/src/contexts/PreferencesContext.tsx b/src/contexts/PreferencesContext.tsx index caeb5a1..e919bcb 100644 --- a/src/contexts/PreferencesContext.tsx +++ b/src/contexts/PreferencesContext.tsx @@ -1,9 +1,9 @@ "use client"; -import React, { createContext, useContext, useEffect, useState } from "react"; +import React, { createContext, useContext, useState } from "react"; import { ERROR_CODES } from "../constants/errorCodes"; import { AppError } from "../utils/errors"; -import type { UserPreferences} from "@/types/preferences"; +import type { UserPreferences } from "@/types/preferences"; import { defaultPreferences } from "@/types/preferences"; interface PreferencesContextType { @@ -14,8 +14,16 @@ interface PreferencesContextType { const PreferencesContext = createContext(undefined); -export function PreferencesProvider({ children }: { children: React.ReactNode }) { - const [preferences, setPreferences] = useState(defaultPreferences); +export function PreferencesProvider({ + children, + initialPreferences, +}: { + children: React.ReactNode; + initialPreferences?: UserPreferences; +}) { + const [preferences, setPreferences] = useState( + initialPreferences || defaultPreferences + ); const [isLoading, setIsLoading] = useState(true); const fetchPreferences = async () => { @@ -37,10 +45,6 @@ export function PreferencesProvider({ children }: { children: React.ReactNode }) } }; - useEffect(() => { - fetchPreferences(); - }, []); - const updatePreferences = async (newPreferences: Partial) => { try {