feat: enhance sidebar data loading with initial preferences and libraries
This commit is contained in:
@@ -3,10 +3,17 @@ import { Inter } from "next/font/google";
|
|||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import ClientLayout from "@/components/layout/ClientLayout";
|
import ClientLayout from "@/components/layout/ClientLayout";
|
||||||
|
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 "@/i18n/i18n"; // Import i18next configuration
|
import "@/i18n/i18n"; // Import i18next configuration
|
||||||
import { cookies } from "next/headers";
|
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"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
@@ -62,6 +69,41 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const locale = cookieStore.get("NEXT_LOCALE")?.value || "fr";
|
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 (
|
return (
|
||||||
<html lang={locale} suppressHydrationWarning className="h-full">
|
<html lang={locale} suppressHydrationWarning className="h-full">
|
||||||
<head>
|
<head>
|
||||||
@@ -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)}
|
className={cn("min-h-screen bg-background font-sans antialiased h-full", inter.className)}
|
||||||
>
|
>
|
||||||
<I18nProvider locale={locale}>
|
<I18nProvider locale={locale}>
|
||||||
<PreferencesProvider>
|
<PreferencesProvider initialPreferences={preferences}>
|
||||||
<ClientLayout>{children}</ClientLayout>
|
<ClientLayout initialLibraries={libraries} initialFavorites={favorites}>
|
||||||
|
{children}
|
||||||
|
</ClientLayout>
|
||||||
</PreferencesProvider>
|
</PreferencesProvider>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -7,16 +7,22 @@ import { Sidebar } from "@/components/layout/Sidebar";
|
|||||||
import { InstallPWA } from "../ui/InstallPWA";
|
import { InstallPWA } from "../ui/InstallPWA";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { PreferencesProvider } from "@/contexts/PreferencesContext";
|
|
||||||
import { registerServiceWorker } from "@/lib/registerSW";
|
import { registerServiceWorker } from "@/lib/registerSW";
|
||||||
import { NetworkStatus } from "../ui/NetworkStatus";
|
import { NetworkStatus } from "../ui/NetworkStatus";
|
||||||
import { LoadingBar } from "@/components/ui/loading-bar";
|
import { LoadingBar } from "@/components/ui/loading-bar";
|
||||||
import { DebugWrapper } from "@/components/debug/DebugWrapper";
|
import { DebugWrapper } from "@/components/debug/DebugWrapper";
|
||||||
|
import type { KomgaLibrary, KomgaSeries } from "@/types/komga";
|
||||||
|
|
||||||
// Routes qui ne nécessitent pas d'authentification
|
// Routes qui ne nécessitent pas d'authentification
|
||||||
const publicRoutes = ["/login", "/register"];
|
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 [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
@@ -63,18 +69,23 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
<PreferencesProvider>
|
|
||||||
<div className="relative min-h-screen h-full">
|
<div className="relative min-h-screen h-full">
|
||||||
<LoadingBar />
|
<LoadingBar />
|
||||||
{!isPublicRoute && <Header onToggleSidebar={handleToggleSidebar} />}
|
{!isPublicRoute && <Header onToggleSidebar={handleToggleSidebar} />}
|
||||||
{!isPublicRoute && <Sidebar isOpen={isSidebarOpen} onClose={handleCloseSidebar} />}
|
{!isPublicRoute && (
|
||||||
|
<Sidebar
|
||||||
|
isOpen={isSidebarOpen}
|
||||||
|
onClose={handleCloseSidebar}
|
||||||
|
initialLibraries={initialLibraries}
|
||||||
|
initialFavorites={initialFavorites}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<main className={`${!isPublicRoute ? "container pt-safe" : ""}`}>{children}</main>
|
<main className={`${!isPublicRoute ? "container pt-safe" : ""}`}>{children}</main>
|
||||||
<InstallPWA />
|
<InstallPWA />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<NetworkStatus />
|
<NetworkStatus />
|
||||||
<DebugWrapper />
|
<DebugWrapper />
|
||||||
</div>
|
</div>
|
||||||
</PreferencesProvider>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,23 +16,23 @@ import { useTranslate } from "@/hooks/useTranslate";
|
|||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
initialLibraries: KomgaLibrary[];
|
||||||
|
initialFavorites: KomgaSeries[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
export function Sidebar({ isOpen, onClose, initialLibraries, initialFavorites }: SidebarProps) {
|
||||||
const { t } = useTranslate();
|
const { t } = useTranslate();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { preferences } = usePreferences();
|
const { preferences } = usePreferences();
|
||||||
const [libraries, setLibraries] = useState<KomgaLibrary[]>([]);
|
const [libraries, setLibraries] = useState<KomgaLibrary[]>(initialLibraries || []);
|
||||||
const [favorites, setFavorites] = useState<KomgaSeries[]>([]);
|
const [favorites, setFavorites] = useState<KomgaSeries[]>(initialFavorites || []);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const [isLoadingFavorites, setIsLoadingFavorites] = useState(true);
|
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const fetchLibraries = useCallback(async () => {
|
const refreshLibraries = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsRefreshing(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/komga/libraries");
|
const response = await fetch("/api/komga/libraries");
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -50,15 +50,12 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
: getErrorMessage(ERROR_CODES.LIBRARY.FETCH_ERROR),
|
: getErrorMessage(ERROR_CODES.LIBRARY.FETCH_ERROR),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
setLibraries([]);
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
|
||||||
setIsRefreshing(false);
|
setIsRefreshing(false);
|
||||||
}
|
}
|
||||||
}, [toast]);
|
}, [toast]);
|
||||||
|
|
||||||
const fetchFavorites = useCallback(async () => {
|
const refreshFavorites = useCallback(async () => {
|
||||||
setIsLoadingFavorites(true);
|
|
||||||
try {
|
try {
|
||||||
const favoritesResponse = await fetch("/api/komga/favorites");
|
const favoritesResponse = await fetch("/api/komga/favorites");
|
||||||
if (!favoritesResponse.ok) {
|
if (!favoritesResponse.ok) {
|
||||||
@@ -91,28 +88,20 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
: getErrorMessage(ERROR_CODES.FAVORITE.FETCH_ERROR),
|
: getErrorMessage(ERROR_CODES.FAVORITE.FETCH_ERROR),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
setFavorites([]);
|
|
||||||
} finally {
|
|
||||||
setIsLoadingFavorites(false);
|
|
||||||
}
|
}
|
||||||
}, [toast]);
|
}, [toast]);
|
||||||
|
|
||||||
// Chargement initial des données
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchLibraries();
|
if (Object.keys(preferences).length > 0) {
|
||||||
fetchFavorites();
|
refreshLibraries();
|
||||||
}, [fetchLibraries, fetchFavorites]);
|
refreshFavorites();
|
||||||
|
}
|
||||||
// Rafraîchir les données quand les préférences changent
|
}, [preferences, refreshLibraries, refreshFavorites]);
|
||||||
useEffect(() => {
|
|
||||||
fetchLibraries();
|
|
||||||
fetchFavorites();
|
|
||||||
}, [preferences, fetchLibraries, fetchFavorites]);
|
|
||||||
|
|
||||||
// Mettre à jour les favoris quand ils changent
|
// Mettre à jour les favoris quand ils changent
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleFavoritesChange = () => {
|
const handleFavoritesChange = () => {
|
||||||
fetchFavorites();
|
refreshFavorites();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("favoritesChanged", handleFavoritesChange);
|
window.addEventListener("favoritesChanged", handleFavoritesChange);
|
||||||
@@ -120,11 +109,11 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("favoritesChanged", handleFavoritesChange);
|
window.removeEventListener("favoritesChanged", handleFavoritesChange);
|
||||||
};
|
};
|
||||||
}, [fetchFavorites]);
|
}, [refreshFavorites]);
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
await Promise.all([fetchLibraries(), fetchFavorites()]);
|
await Promise.all([refreshLibraries(), refreshFavorites()]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
@@ -217,7 +206,7 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
</h2>
|
</h2>
|
||||||
<span className="text-xs text-muted-foreground">{favorites.length}</span>
|
<span className="text-xs text-muted-foreground">{favorites.length}</span>
|
||||||
</div>
|
</div>
|
||||||
{isLoadingFavorites ? (
|
{isRefreshing ? (
|
||||||
<div className="px-3 py-2 text-sm text-muted-foreground">
|
<div className="px-3 py-2 text-sm text-muted-foreground">
|
||||||
{t("sidebar.favorites.loading")}
|
{t("sidebar.favorites.loading")}
|
||||||
</div>
|
</div>
|
||||||
@@ -258,7 +247,7 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
<RefreshCw className={cn("h-4 w-4", isRefreshing && "animate-spin")} />
|
<RefreshCw className={cn("h-4 w-4", isRefreshing && "animate-spin")} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{isLoading ? (
|
{isRefreshing ? (
|
||||||
<div className="px-3 py-2 text-sm text-muted-foreground">
|
<div className="px-3 py-2 text-sm text-muted-foreground">
|
||||||
{t("sidebar.libraries.loading")}
|
{t("sidebar.libraries.loading")}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 };
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
import React, { createContext, useContext, useState } from "react";
|
||||||
import { ERROR_CODES } from "../constants/errorCodes";
|
import { ERROR_CODES } from "../constants/errorCodes";
|
||||||
import { AppError } from "../utils/errors";
|
import { AppError } from "../utils/errors";
|
||||||
import type { UserPreferences} from "@/types/preferences";
|
import type { UserPreferences } from "@/types/preferences";
|
||||||
import { defaultPreferences } from "@/types/preferences";
|
import { defaultPreferences } from "@/types/preferences";
|
||||||
|
|
||||||
interface PreferencesContextType {
|
interface PreferencesContextType {
|
||||||
@@ -14,8 +14,16 @@ interface PreferencesContextType {
|
|||||||
|
|
||||||
const PreferencesContext = createContext<PreferencesContextType | undefined>(undefined);
|
const PreferencesContext = createContext<PreferencesContextType | undefined>(undefined);
|
||||||
|
|
||||||
export function PreferencesProvider({ children }: { children: React.ReactNode }) {
|
export function PreferencesProvider({
|
||||||
const [preferences, setPreferences] = useState<UserPreferences>(defaultPreferences);
|
children,
|
||||||
|
initialPreferences,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
initialPreferences?: UserPreferences;
|
||||||
|
}) {
|
||||||
|
const [preferences, setPreferences] = useState<UserPreferences>(
|
||||||
|
initialPreferences || defaultPreferences
|
||||||
|
);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
const fetchPreferences = async () => {
|
const fetchPreferences = async () => {
|
||||||
@@ -37,10 +45,6 @@ export function PreferencesProvider({ children }: { children: React.ReactNode })
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchPreferences();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updatePreferences = async (newPreferences: Partial<UserPreferences>) => {
|
const updatePreferences = async (newPreferences: Partial<UserPreferences>) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user