From 03cb46f81bba647a76a5dfa63f6d284b5c486fba Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sat, 28 Feb 2026 10:34:26 +0100 Subject: [PATCH] refactor: use Server Actions for read progress updates - Create src/app/actions/read-progress.ts with updateReadProgress and deleteReadProgress - Update mark-as-read-button and mark-as-unread-button to use Server Actions - Update usePageNavigation hook to use Server Action - Use revalidateTag with 'min' profile for cache invalidation --- src/app/actions/read-progress.ts | 53 +++++++++++++++++++ .../reader/hooks/usePageNavigation.ts | 7 +-- src/components/ui/mark-as-read-button.tsx | 14 ++--- src/components/ui/mark-as-unread-button.tsx | 9 ++-- 4 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 src/app/actions/read-progress.ts diff --git a/src/app/actions/read-progress.ts b/src/app/actions/read-progress.ts new file mode 100644 index 0000000..eefe11b --- /dev/null +++ b/src/app/actions/read-progress.ts @@ -0,0 +1,53 @@ +"use server"; + +import { revalidateTag } from "next/cache"; +import { BookService } from "@/lib/services/book.service"; +import { ERROR_CODES } from "@/constants/errorCodes"; +import { AppError } from "@/utils/errors"; + +const HOME_CACHE_TAG = "home-data"; + +/** + * Met à jour la progression de lecture d'un livre + * Note: ne pas utiliser "use server" avec redirect - on gère manuellement + */ +export async function updateReadProgress( + bookId: string, + page: number, + completed: boolean = false +): Promise<{ success: boolean; message: string }> { + try { + await BookService.updateReadProgress(bookId, page, completed); + + // Invalider le cache de la home (sans refresh auto) + revalidateTag(HOME_CACHE_TAG, "min"); + + return { success: true, message: "Progression mise à jour" }; + } catch (error) { + if (error instanceof AppError) { + return { success: false, message: error.message }; + } + return { success: false, message: "Erreur lors de la mise à jour" }; + } +} + +/** + * Supprime la progression de lecture d'un livre + */ +export async function deleteReadProgress( + bookId: string +): Promise<{ success: boolean; message: string }> { + try { + await BookService.deleteReadProgress(bookId); + + // Invalider le cache de la home (sans refresh auto) + revalidateTag(HOME_CACHE_TAG, "min"); + + return { success: true, message: "Progression supprimée" }; + } catch (error) { + if (error instanceof AppError) { + return { success: false, message: error.message }; + } + return { success: false, message: "Erreur lors de la suppression" }; + } +} diff --git a/src/components/reader/hooks/usePageNavigation.ts b/src/components/reader/hooks/usePageNavigation.ts index 511181f..f28a67c 100644 --- a/src/components/reader/hooks/usePageNavigation.ts +++ b/src/components/reader/hooks/usePageNavigation.ts @@ -3,6 +3,7 @@ import { useRouter } from "next/navigation"; import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service"; import type { KomgaBook } from "@/types/komga"; import logger from "@/lib/logger"; +import { updateReadProgress } from "@/app/actions/read-progress"; interface UsePageNavigationProps { book: KomgaBook; @@ -48,11 +49,7 @@ export function usePageNavigation({ try { ClientOfflineBookService.setCurrentPage(bookRef.current, page); const completed = page === pagesLengthRef.current; - await fetch(`/api/komga/books/${bookRef.current.id}/read-progress`, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ page, completed }), - }); + await updateReadProgress(bookRef.current.id, page, completed); } catch (error) { logger.error({ err: error }, "Sync error:"); } diff --git a/src/components/ui/mark-as-read-button.tsx b/src/components/ui/mark-as-read-button.tsx index 4a8e60e..bc27df8 100644 --- a/src/components/ui/mark-as-read-button.tsx +++ b/src/components/ui/mark-as-read-button.tsx @@ -7,6 +7,7 @@ import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.serv import { useState } from "react"; import { useTranslation } from "react-i18next"; import logger from "@/lib/logger"; +import { updateReadProgress } from "@/app/actions/read-progress"; interface MarkAsReadButtonProps { bookId: string; @@ -32,16 +33,11 @@ export function MarkAsReadButton({ setIsLoading(true); try { ClientOfflineBookService.removeCurrentPageById(bookId); - const response = await fetch(`/api/komga/books/${bookId}/read-progress`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ page: pagesCount, completed: true }), - }); + + const result = await updateReadProgress(bookId, pagesCount, true); - if (!response.ok) { - throw new Error(t("books.actions.markAsRead.error.update")); + if (!result.success) { + throw new Error(result.message); } toast({ diff --git a/src/components/ui/mark-as-unread-button.tsx b/src/components/ui/mark-as-unread-button.tsx index c5b206c..12421e3 100644 --- a/src/components/ui/mark-as-unread-button.tsx +++ b/src/components/ui/mark-as-unread-button.tsx @@ -7,6 +7,7 @@ import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.serv import { useState } from "react"; import { useTranslation } from "react-i18next"; import logger from "@/lib/logger"; +import { deleteReadProgress } from "@/app/actions/read-progress"; interface MarkAsUnreadButtonProps { bookId: string; @@ -23,12 +24,10 @@ export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnrea e.stopPropagation(); // Empêcher la propagation au parent setIsLoading(true); try { - const response = await fetch(`/api/komga/books/${bookId}/read-progress`, { - method: "DELETE", - }); + const result = await deleteReadProgress(bookId); - if (!response.ok) { - throw new Error(t("books.actions.markAsUnread.error.update")); + if (!result.success) { + throw new Error(result.message); } // On supprime la page courante du localStorage seulement après que l'API a répondu