diff --git a/src/app/actions/read-progress.ts b/src/app/actions/read-progress.ts index d5367ec..35a1508 100644 --- a/src/app/actions/read-progress.ts +++ b/src/app/actions/read-progress.ts @@ -20,8 +20,8 @@ export async function updateReadProgress( await BookService.updateReadProgress(bookId, page, completed); // Invalider le cache home et libraries (statut de lecture des séries) - revalidateTag(HOME_CACHE_TAG); - revalidateTag(LIBRARY_SERIES_CACHE_TAG); + revalidateTag(HOME_CACHE_TAG, "max"); + revalidateTag(LIBRARY_SERIES_CACHE_TAG, "max"); return { success: true, message: "Progression mise à jour" }; } catch (error) { @@ -42,8 +42,8 @@ export async function deleteReadProgress( await BookService.deleteReadProgress(bookId); // Invalider le cache home et libraries (statut de lecture des séries) - revalidateTag(HOME_CACHE_TAG); - revalidateTag(LIBRARY_SERIES_CACHE_TAG); + revalidateTag(HOME_CACHE_TAG, "max"); + revalidateTag(LIBRARY_SERIES_CACHE_TAG, "max"); return { success: true, message: "Progression supprimée" }; } catch (error) { diff --git a/src/app/actions/refresh.ts b/src/app/actions/refresh.ts new file mode 100644 index 0000000..be5b224 --- /dev/null +++ b/src/app/actions/refresh.ts @@ -0,0 +1,32 @@ +"use server"; + +import { revalidatePath, revalidateTag } from "next/cache"; +import { LIBRARY_SERIES_CACHE_TAG } from "@/lib/services/library.service"; + +const HOME_CACHE_TAG = "home-data"; + +export type RefreshScope = "home" | "library" | "series"; + +/** + * Invalide le cache Next.js pour forcer un re-fetch au prochain router.refresh(). + * À appeler côté client avant router.refresh() sur les boutons / pull-to-refresh. + */ +export async function revalidateForRefresh(scope: RefreshScope, id: string): Promise { + switch (scope) { + case "home": + revalidateTag(HOME_CACHE_TAG, "max"); + revalidatePath("/"); + break; + case "library": + revalidateTag(LIBRARY_SERIES_CACHE_TAG, "max"); + revalidatePath(`/libraries/${id}`); + revalidatePath("/libraries"); + break; + case "series": + revalidatePath(`/series/${id}`); + revalidatePath("/series"); + break; + default: + break; + } +} diff --git a/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx b/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx index fd29446..c2c4ceb 100644 --- a/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx +++ b/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx @@ -1,29 +1,44 @@ "use client"; -import { useState, type ReactNode } from "react"; +import { useState, useCallback, type ReactNode } from "react"; import { useRouter } from "next/navigation"; import { PullToRefreshIndicator } from "@/components/common/PullToRefreshIndicator"; import { usePullToRefresh } from "@/hooks/usePullToRefresh"; +import { RefreshProvider } from "@/contexts/RefreshContext"; +import { revalidateForRefresh } from "@/app/actions/refresh"; interface LibraryClientWrapperProps { children: ReactNode; + libraryId?: string; } -export function LibraryClientWrapper({ children }: LibraryClientWrapperProps) { +const REFRESH_ANIMATION_MS = 400; + +export function LibraryClientWrapper({ children, libraryId }: LibraryClientWrapperProps) { const router = useRouter(); const [isRefreshing, setIsRefreshing] = useState(false); - const handleRefresh = async () => { - try { - setIsRefreshing(true); - router.refresh(); - return { success: true }; - } catch { - return { success: false, error: "Error refreshing library" }; - } finally { - setIsRefreshing(false); - } - }; + const handleRefresh = useCallback( + async (libraryIdArg?: string) => { + const id = libraryIdArg ?? libraryId; + if (!id) { + router.refresh(); + return { success: true }; + } + try { + setIsRefreshing(true); + await revalidateForRefresh("library", id); + router.refresh(); + await new Promise((r) => setTimeout(r, REFRESH_ANIMATION_MS)); + return { success: true }; + } catch { + return { success: false, error: "Error refreshing library" }; + } finally { + setIsRefreshing(false); + } + }, + [libraryId, router] + ); const pullToRefresh = usePullToRefresh({ onRefresh: async () => { @@ -33,7 +48,9 @@ export function LibraryClientWrapper({ children }: LibraryClientWrapperProps) { }); return ( - <> + handleRefresh(id) : undefined} + > {children} - + ); } diff --git a/src/app/libraries/[libraryId]/page.tsx b/src/app/libraries/[libraryId]/page.tsx index 4dca9a5..49eb580 100644 --- a/src/app/libraries/[libraryId]/page.tsx +++ b/src/app/libraries/[libraryId]/page.tsx @@ -43,7 +43,7 @@ export default async function LibraryPage({ params, searchParams }: PageProps) { ]); return ( - + { - try { - setIsRefreshing(true); - router.refresh(); - return { success: true }; - } catch { - return { success: false, error: "Error refreshing series" }; - } finally { - setIsRefreshing(false); - } - }; + const handleRefresh = useCallback( + async (seriesIdArg?: string) => { + const id = seriesIdArg ?? seriesId; + try { + setIsRefreshing(true); + if (id) { + await revalidateForRefresh("series", id); + } + router.refresh(); + await new Promise((r) => setTimeout(r, REFRESH_ANIMATION_MS)); + return { success: true }; + } catch { + return { success: false, error: "Error refreshing series" }; + } finally { + setIsRefreshing(false); + } + }, + [seriesId, router] + ); const pullToRefresh = usePullToRefresh({ onRefresh: async () => { @@ -44,7 +57,9 @@ export function SeriesClientWrapper({ canRefresh={pullToRefresh.canRefresh} isHiding={pullToRefresh.isHiding} /> - {children} + handleRefresh(id) : undefined}> + {children} + ); } diff --git a/src/app/series/[seriesId]/page.tsx b/src/app/series/[seriesId]/page.tsx index 8841c10..fd99bb2 100644 --- a/src/app/series/[seriesId]/page.tsx +++ b/src/app/series/[seriesId]/page.tsx @@ -36,7 +36,7 @@ export default async function SeriesPage({ params, searchParams }: PageProps) { ]); return ( - + { + const handleRefresh = useCallback(async () => { try { setIsRefreshing(true); - // Re-fetch server-side data + await revalidateForRefresh("home", "home"); router.refresh(); + await new Promise((r) => setTimeout(r, REFRESH_ANIMATION_MS)); return { success: true }; } catch (_error) { return { success: false, error: "Erreur lors du rafraîchissement de la page d'accueil" }; } finally { setIsRefreshing(false); } - }; + }, [router]); const pullToRefresh = usePullToRefresh({ onRefresh: async () => { diff --git a/src/components/library/RefreshButton.tsx b/src/components/library/RefreshButton.tsx index 543dd1d..90be1bf 100644 --- a/src/components/library/RefreshButton.tsx +++ b/src/components/library/RefreshButton.tsx @@ -7,17 +7,20 @@ import { Button } from "@/components/ui/button"; import { useToast } from "@/components/ui/use-toast"; import { cn } from "@/lib/utils"; import { useTranslation } from "react-i18next"; +import { useRefresh } from "@/contexts/RefreshContext"; interface RefreshButtonProps { libraryId: string; refreshLibrary?: (libraryId: string) => Promise<{ success: boolean; error?: string }>; } -export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps) { +export function RefreshButton({ libraryId, refreshLibrary: refreshLibraryProp }: RefreshButtonProps) { const [isRefreshing, setIsRefreshing] = useState(false); const router = useRouter(); const { toast } = useToast(); const { t } = useTranslation(); + const { refreshLibrary: refreshLibraryFromContext } = useRefresh(); + const refreshLibrary = refreshLibraryProp ?? refreshLibraryFromContext; const handleRefresh = async () => { setIsRefreshing(true);