From 279f6c6e884b9af6c72cf6a739d1cd740fb43f07 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Thu, 27 Feb 2025 21:59:14 +0100 Subject: [PATCH] refacto: error and types --- src/app/api/auth/login/route.ts | 9 +- src/app/api/auth/logout/route.ts | 6 +- src/app/api/auth/register/route.ts | 7 +- src/app/api/debug/route.ts | 18 +- .../[bookId]/pages/[pageNumber]/route.ts | 3 + .../books/[bookId]/read-progress/route.ts | 5 + src/app/api/komga/books/[bookId]/route.ts | 6 +- src/app/api/komga/cache/clear/route.ts | 1 + src/app/api/komga/cache/mode/route.ts | 3 + src/app/api/komga/config/route.ts | 5 + src/app/api/komga/favorites/route.ts | 6 + .../[bookId]/pages/[pageNumber]/route.ts | 2 + .../pages/[pageNumber]/thumbnail/route.ts | 3 + .../images/books/[bookId]/thumbnail/route.ts | 2 + .../series/[seriesId]/first-page/route.ts | 2 + .../series/[seriesId]/thumbnail/route.ts | 2 + src/app/api/komga/libraries/route.ts | 2 + src/app/api/komga/series/[seriesId]/route.ts | 2 + src/app/api/komga/test/route.ts | 1 + src/app/api/komga/ttl-config/route.ts | 11 +- src/app/api/preferences/route.ts | 4 + src/app/libraries/[libraryId]/page.tsx | 5 +- src/app/page.tsx | 4 +- src/app/series/[seriesId]/page.tsx | 2 +- src/components/auth/LoginForm.tsx | 8 +- src/components/auth/RegisterForm.tsx | 11 +- src/components/settings/CacheSettings.tsx | 232 +++++++ src/components/settings/ClientSettings.tsx | 643 +----------------- src/components/settings/DisplaySettings.tsx | 113 +++ src/components/settings/KomgaSettings.tsx | 280 ++++++++ src/components/ui/ErrorMessage.tsx | 5 +- src/i18n/messages/en/common.json | 15 + src/i18n/messages/fr/common.json | 15 + src/lib/services/auth.service.ts | 20 +- src/middleware.ts | 16 +- src/types/auth.ts | 8 - src/types/global.d.ts | 7 + 37 files changed, 800 insertions(+), 684 deletions(-) create mode 100644 src/components/settings/CacheSettings.tsx create mode 100644 src/components/settings/DisplaySettings.tsx create mode 100644 src/components/settings/KomgaSettings.tsx create mode 100644 src/types/global.d.ts diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 8523be6..d628f81 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -3,6 +3,7 @@ import { AuthServerService } from "@/lib/services/auth-server.service"; import { ERROR_CODES } from "@/constants/errorCodes"; import { AppError } from "@/utils/errors"; import { UserData } from "@/lib/services/auth-server.service"; +import { getErrorMessage } from "@/utils/errors"; export async function POST(request: Request) { try { @@ -20,9 +21,7 @@ export async function POST(request: Request) { if (error instanceof AppError) { return NextResponse.json( { - error: { - code: error.code, - }, + error: AppError, }, { status: 401 } ); @@ -35,7 +34,9 @@ export async function POST(request: Request) { { error: { code: ERROR_CODES.AUTH.INVALID_CREDENTIALS, - }, + name: "Invalid credentials", + message: getErrorMessage(ERROR_CODES.AUTH.INVALID_CREDENTIALS), + } as AppError, }, { status: 500 } ); diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts index d31dbe9..474fa3e 100644 --- a/src/app/api/auth/logout/route.ts +++ b/src/app/api/auth/logout/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from "next/server"; import { cookies } from "next/headers"; import { ERROR_CODES } from "@/constants/errorCodes"; +import { getErrorMessage } from "@/utils/errors"; +import { AppErrorType } from "@/types/global"; export async function POST() { try { @@ -13,7 +15,9 @@ export async function POST() { { error: { code: ERROR_CODES.AUTH.LOGOUT_ERROR, - }, + name: "Logout error", + message: getErrorMessage(ERROR_CODES.AUTH.LOGOUT_ERROR), + } as AppErrorType, }, { status: 500 } ); diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts index 7d2693e..7561fe3 100644 --- a/src/app/api/auth/register/route.ts +++ b/src/app/api/auth/register/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from "next/server"; import { AuthServerService, UserData } from "@/lib/services/auth-server.service"; import { ERROR_CODES } from "@/constants/errorCodes"; import { AppError } from "@/utils/errors"; +import { getErrorMessage } from "@/utils/errors"; export async function POST(request: Request) { try { @@ -24,9 +25,7 @@ export async function POST(request: Request) { : 500; return NextResponse.json( { - error: { - code: error.code, - }, + error: AppError, }, { status } ); @@ -39,6 +38,8 @@ export async function POST(request: Request) { { error: { code: ERROR_CODES.AUTH.INVALID_USER_DATA, + name: "Invalid user data", + message: getErrorMessage(ERROR_CODES.AUTH.INVALID_USER_DATA), }, }, { status: 500 } diff --git a/src/app/api/debug/route.ts b/src/app/api/debug/route.ts index 4e307b9..4e84405 100644 --- a/src/app/api/debug/route.ts +++ b/src/app/api/debug/route.ts @@ -15,8 +15,9 @@ export async function GET() { { error: { code: error.code, + name: "Debug fetch error", message: getErrorMessage(error.code), - }, + } as AppError, }, { status: 500 } ); @@ -25,8 +26,9 @@ export async function GET() { { error: { code: ERROR_CODES.DEBUG.FETCH_ERROR, + name: "Debug fetch error", message: getErrorMessage(ERROR_CODES.DEBUG.FETCH_ERROR), - }, + } as AppError, }, { status: 500 } ); @@ -47,8 +49,9 @@ export async function POST(request: NextRequest) { { error: { code: error.code, + name: "Debug save error", message: getErrorMessage(error.code), - }, + } as AppError, }, { status: 500 } ); @@ -57,8 +60,9 @@ export async function POST(request: NextRequest) { { error: { code: ERROR_CODES.DEBUG.SAVE_ERROR, + name: "Debug save error", message: getErrorMessage(ERROR_CODES.DEBUG.SAVE_ERROR), - }, + } as AppError, }, { status: 500 } ); @@ -78,8 +82,9 @@ export async function DELETE() { { error: { code: error.code, + name: "Debug clear error", message: getErrorMessage(error.code), - }, + } as AppError, }, { status: 500 } ); @@ -88,8 +93,9 @@ export async function DELETE() { { error: { code: ERROR_CODES.DEBUG.CLEAR_ERROR, + name: "Debug clear error", message: getErrorMessage(ERROR_CODES.DEBUG.CLEAR_ERROR), - }, + } as AppError, }, { status: 500 } ); diff --git a/src/app/api/komga/books/[bookId]/pages/[pageNumber]/route.ts b/src/app/api/komga/books/[bookId]/pages/[pageNumber]/route.ts index d6d5d0a..9de8ba8 100644 --- a/src/app/api/komga/books/[bookId]/pages/[pageNumber]/route.ts +++ b/src/app/api/komga/books/[bookId]/pages/[pageNumber]/route.ts @@ -17,6 +17,7 @@ export async function GET( { error: { code: ERROR_CODES.IMAGE.FETCH_ERROR, + name: "Image fetch error", message: getErrorMessage(ERROR_CODES.IMAGE.FETCH_ERROR), }, }, @@ -41,6 +42,7 @@ export async function GET( { error: { code: error.code, + name: "Image fetch error", message: getErrorMessage(error.code), }, }, @@ -51,6 +53,7 @@ export async function GET( { error: { code: ERROR_CODES.IMAGE.FETCH_ERROR, + name: "Image fetch error", message: getErrorMessage(ERROR_CODES.IMAGE.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/books/[bookId]/read-progress/route.ts b/src/app/api/komga/books/[bookId]/read-progress/route.ts index 0044142..5c23d42 100644 --- a/src/app/api/komga/books/[bookId]/read-progress/route.ts +++ b/src/app/api/komga/books/[bookId]/read-progress/route.ts @@ -13,6 +13,7 @@ export async function PATCH(request: NextRequest, { params }: { params: { bookId { error: { code: ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR, + name: "Progress update error", message: getErrorMessage(ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR), }, }, @@ -29,6 +30,7 @@ export async function PATCH(request: NextRequest, { params }: { params: { bookId { error: { code: error.code, + name: "Progress update error", message: getErrorMessage(error.code), }, }, @@ -39,6 +41,7 @@ export async function PATCH(request: NextRequest, { params }: { params: { bookId { error: { code: ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR, + name: "Progress update error", message: getErrorMessage(ERROR_CODES.BOOK.PROGRESS_UPDATE_ERROR), }, }, @@ -58,6 +61,7 @@ export async function DELETE(request: NextRequest, { params }: { params: { bookI { error: { code: error.code, + name: "Progress delete error", message: getErrorMessage(error.code), }, }, @@ -68,6 +72,7 @@ export async function DELETE(request: NextRequest, { params }: { params: { bookI { error: { code: ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR, + name: "Progress delete error", message: getErrorMessage(ERROR_CODES.BOOK.PROGRESS_DELETE_ERROR), }, }, diff --git a/src/app/api/komga/books/[bookId]/route.ts b/src/app/api/komga/books/[bookId]/route.ts index 4074821..7b7ceaa 100644 --- a/src/app/api/komga/books/[bookId]/route.ts +++ b/src/app/api/komga/books/[bookId]/route.ts @@ -15,8 +15,9 @@ export async function GET(request: Request, { params }: { params: { bookId: stri { error: { code: error.code, + name: "Book fetch error", message: getErrorMessage(error.code), - }, + } as AppError, }, { status: 500 } ); @@ -25,8 +26,9 @@ export async function GET(request: Request, { params }: { params: { bookId: stri { error: { code: ERROR_CODES.BOOK.NOT_FOUND, + name: "Book fetch error", message: getErrorMessage(ERROR_CODES.BOOK.NOT_FOUND), - }, + } as AppError, }, { status: 500 } ); diff --git a/src/app/api/komga/cache/clear/route.ts b/src/app/api/komga/cache/clear/route.ts index 05ccb14..bdca979 100644 --- a/src/app/api/komga/cache/clear/route.ts +++ b/src/app/api/komga/cache/clear/route.ts @@ -14,6 +14,7 @@ export async function POST() { { error: { code: ERROR_CODES.CACHE.CLEAR_ERROR, + name: "Cache clear error", message: getErrorMessage(ERROR_CODES.CACHE.CLEAR_ERROR), }, }, diff --git a/src/app/api/komga/cache/mode/route.ts b/src/app/api/komga/cache/mode/route.ts index 74de763..58150ed 100644 --- a/src/app/api/komga/cache/mode/route.ts +++ b/src/app/api/komga/cache/mode/route.ts @@ -17,6 +17,7 @@ export async function GET() { { error: { code: ERROR_CODES.CACHE.MODE_FETCH_ERROR, + name: "Cache mode fetch error", message: getErrorMessage(ERROR_CODES.CACHE.MODE_FETCH_ERROR), }, }, @@ -33,6 +34,7 @@ export async function POST(request: Request) { { error: { code: ERROR_CODES.CACHE.INVALID_MODE, + name: "Invalid cache mode", message: getErrorMessage(ERROR_CODES.CACHE.INVALID_MODE), }, }, @@ -49,6 +51,7 @@ export async function POST(request: Request) { { error: { code: ERROR_CODES.CACHE.MODE_UPDATE_ERROR, + name: "Cache mode update error", message: getErrorMessage(ERROR_CODES.CACHE.MODE_UPDATE_ERROR), }, }, diff --git a/src/app/api/komga/config/route.ts b/src/app/api/komga/config/route.ts index 16c5723..6db9905 100644 --- a/src/app/api/komga/config/route.ts +++ b/src/app/api/komga/config/route.ts @@ -22,6 +22,7 @@ export async function POST(request: Request) { { error: { code: ERROR_CODES.MIDDLEWARE.UNAUTHORIZED, + name: "Unauthorized", message: getErrorMessage(ERROR_CODES.MIDDLEWARE.UNAUTHORIZED), }, }, @@ -32,6 +33,7 @@ export async function POST(request: Request) { { error: { code: ERROR_CODES.CONFIG.SAVE_ERROR, + name: "Config save error", message: getErrorMessage(ERROR_CODES.CONFIG.SAVE_ERROR), }, }, @@ -53,6 +55,7 @@ export async function GET() { { error: { code: ERROR_CODES.MIDDLEWARE.UNAUTHORIZED, + name: "Unauthorized", message: getErrorMessage(ERROR_CODES.MIDDLEWARE.UNAUTHORIZED), }, }, @@ -64,6 +67,7 @@ export async function GET() { { error: { code: ERROR_CODES.KOMGA.MISSING_CONFIG, + name: "Missing config", message: getErrorMessage(ERROR_CODES.KOMGA.MISSING_CONFIG), }, }, @@ -75,6 +79,7 @@ export async function GET() { { error: { code: ERROR_CODES.CONFIG.FETCH_ERROR, + name: "Config fetch error", message: getErrorMessage(ERROR_CODES.CONFIG.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/favorites/route.ts b/src/app/api/komga/favorites/route.ts index c8bf3b1..7e1940b 100644 --- a/src/app/api/komga/favorites/route.ts +++ b/src/app/api/komga/favorites/route.ts @@ -15,6 +15,7 @@ export async function GET() { { error: { code: error.code, + name: "Favorite fetch error", message: getErrorMessage(error.code), }, }, @@ -25,6 +26,7 @@ export async function GET() { { error: { code: ERROR_CODES.FAVORITE.FETCH_ERROR, + name: "Favorite fetch error", message: getErrorMessage(ERROR_CODES.FAVORITE.FETCH_ERROR), }, }, @@ -45,6 +47,7 @@ export async function POST(request: Request) { { error: { code: error.code, + name: "Favorite add error", message: getErrorMessage(error.code), }, }, @@ -55,6 +58,7 @@ export async function POST(request: Request) { { error: { code: ERROR_CODES.FAVORITE.ADD_ERROR, + name: "Favorite add error", message: getErrorMessage(ERROR_CODES.FAVORITE.ADD_ERROR), }, }, @@ -75,6 +79,7 @@ export async function DELETE(request: Request) { { error: { code: error.code, + name: "Favorite delete error", message: getErrorMessage(error.code), }, }, @@ -85,6 +90,7 @@ export async function DELETE(request: Request) { { error: { code: ERROR_CODES.FAVORITE.DELETE_ERROR, + name: "Favorite delete error", message: getErrorMessage(ERROR_CODES.FAVORITE.DELETE_ERROR), }, }, diff --git a/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/route.ts b/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/route.ts index 7da585c..cf9c498 100644 --- a/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/route.ts +++ b/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/route.ts @@ -20,6 +20,7 @@ export async function GET( { error: { code: error.code, + name: "Image fetch error", message: getErrorMessage(error.code), }, }, @@ -30,6 +31,7 @@ export async function GET( { error: { code: ERROR_CODES.IMAGE.FETCH_ERROR, + name: "Image fetch error", message: getErrorMessage(ERROR_CODES.IMAGE.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/thumbnail/route.ts b/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/thumbnail/route.ts index eb529d1..8754a2e 100644 --- a/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/thumbnail/route.ts +++ b/src/app/api/komga/images/books/[bookId]/pages/[pageNumber]/thumbnail/route.ts @@ -18,6 +18,7 @@ export async function GET( { error: { code: ERROR_CODES.BOOK.PAGES_FETCH_ERROR, + name: "Image fetch error", message: getErrorMessage(ERROR_CODES.BOOK.PAGES_FETCH_ERROR), }, }, @@ -34,6 +35,7 @@ export async function GET( { error: { code: error.code, + name: "Image fetch error", message: getErrorMessage(error.code), }, }, @@ -44,6 +46,7 @@ export async function GET( { error: { code: ERROR_CODES.IMAGE.FETCH_ERROR, + name: "Image fetch error", message: getErrorMessage(ERROR_CODES.IMAGE.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/images/books/[bookId]/thumbnail/route.ts b/src/app/api/komga/images/books/[bookId]/thumbnail/route.ts index 720fedb..4308fe1 100644 --- a/src/app/api/komga/images/books/[bookId]/thumbnail/route.ts +++ b/src/app/api/komga/images/books/[bookId]/thumbnail/route.ts @@ -15,6 +15,7 @@ export async function GET(request: NextRequest, { params }: { params: { bookId: { error: { code: error.code, + name: "Image fetch error", message: getErrorMessage(error.code), }, }, @@ -25,6 +26,7 @@ export async function GET(request: NextRequest, { params }: { params: { bookId: { error: { code: ERROR_CODES.IMAGE.FETCH_ERROR, + name: "Image fetch error", message: getErrorMessage(ERROR_CODES.IMAGE.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/images/series/[seriesId]/first-page/route.ts b/src/app/api/komga/images/series/[seriesId]/first-page/route.ts index ba7243a..cde44ab 100644 --- a/src/app/api/komga/images/series/[seriesId]/first-page/route.ts +++ b/src/app/api/komga/images/series/[seriesId]/first-page/route.ts @@ -17,6 +17,7 @@ export async function GET(request: NextRequest, { params }: { params: { seriesId { error: { code: error.code, + name: "Image fetch error", message: getErrorMessage(error.code), }, }, @@ -27,6 +28,7 @@ export async function GET(request: NextRequest, { params }: { params: { seriesId { error: { code: ERROR_CODES.IMAGE.FETCH_ERROR, + name: "Image fetch error", message: getErrorMessage(ERROR_CODES.IMAGE.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/images/series/[seriesId]/thumbnail/route.ts b/src/app/api/komga/images/series/[seriesId]/thumbnail/route.ts index 6f748be..7568a5f 100644 --- a/src/app/api/komga/images/series/[seriesId]/thumbnail/route.ts +++ b/src/app/api/komga/images/series/[seriesId]/thumbnail/route.ts @@ -15,6 +15,7 @@ export async function GET(request: NextRequest, { params }: { params: { seriesId { error: { code: error.code, + name: "Image fetch error", message: getErrorMessage(error.code), }, }, @@ -25,6 +26,7 @@ export async function GET(request: NextRequest, { params }: { params: { seriesId { error: { code: ERROR_CODES.IMAGE.FETCH_ERROR, + name: "Image fetch error", message: getErrorMessage(ERROR_CODES.IMAGE.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/libraries/route.ts b/src/app/api/komga/libraries/route.ts index a0d3172..2e895b8 100644 --- a/src/app/api/komga/libraries/route.ts +++ b/src/app/api/komga/libraries/route.ts @@ -17,6 +17,7 @@ export async function GET() { { error: { code: error.code, + name: "Library fetch error", message: getErrorMessage(error.code), }, }, @@ -27,6 +28,7 @@ export async function GET() { { error: { code: ERROR_CODES.LIBRARY.FETCH_ERROR, + name: "Library fetch error", message: getErrorMessage(ERROR_CODES.LIBRARY.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/series/[seriesId]/route.ts b/src/app/api/komga/series/[seriesId]/route.ts index 78fb74c..f31d76c 100644 --- a/src/app/api/komga/series/[seriesId]/route.ts +++ b/src/app/api/komga/series/[seriesId]/route.ts @@ -18,6 +18,7 @@ export async function GET(request: Request, { params }: { params: { seriesId: st { error: { code: error.code, + name: "Series fetch error", message: getErrorMessage(error.code), }, }, @@ -28,6 +29,7 @@ export async function GET(request: Request, { params }: { params: { seriesId: st { error: { code: ERROR_CODES.SERIES.FETCH_ERROR, + name: "Series fetch error", message: getErrorMessage(ERROR_CODES.SERIES.FETCH_ERROR), }, }, diff --git a/src/app/api/komga/test/route.ts b/src/app/api/komga/test/route.ts index c428c01..c9f04e8 100644 --- a/src/app/api/komga/test/route.ts +++ b/src/app/api/komga/test/route.ts @@ -25,6 +25,7 @@ export async function POST(request: Request) { { error: { code: ERROR_CODES.KOMGA.CONNECTION_ERROR, + name: "Connection error", message: getErrorMessage(ERROR_CODES.KOMGA.CONNECTION_ERROR), }, }, diff --git a/src/app/api/komga/ttl-config/route.ts b/src/app/api/komga/ttl-config/route.ts index 47f9ee4..057050b 100644 --- a/src/app/api/komga/ttl-config/route.ts +++ b/src/app/api/komga/ttl-config/route.ts @@ -11,10 +11,11 @@ export async function GET() { } catch (error) { console.error("Erreur lors de la récupération de la configuration TTL:", error); if (error instanceof Error) { - if (error.message === "Utilisateur non authentifié") { + if (error.message === getErrorMessage(ERROR_CODES.MIDDLEWARE.UNAUTHORIZED)) { return NextResponse.json( { error: { + name: "Unauthorized", code: ERROR_CODES.MIDDLEWARE.UNAUTHORIZED, message: getErrorMessage(ERROR_CODES.MIDDLEWARE.UNAUTHORIZED), }, @@ -26,6 +27,7 @@ export async function GET() { return NextResponse.json( { error: { + name: "TTL fetch error", code: ERROR_CODES.CONFIG.TTL_FETCH_ERROR, message: getErrorMessage(ERROR_CODES.CONFIG.TTL_FETCH_ERROR), }, @@ -53,10 +55,14 @@ export async function POST(request: Request) { }); } catch (error) { console.error("Erreur lors de la sauvegarde de la configuration TTL:", error); - if (error instanceof Error && error.message === "Utilisateur non authentifié") { + if ( + error instanceof Error && + error.message === getErrorMessage(ERROR_CODES.MIDDLEWARE.UNAUTHORIZED) + ) { return NextResponse.json( { error: { + name: "Unauthorized", code: ERROR_CODES.MIDDLEWARE.UNAUTHORIZED, message: getErrorMessage(ERROR_CODES.MIDDLEWARE.UNAUTHORIZED), }, @@ -67,6 +73,7 @@ export async function POST(request: Request) { return NextResponse.json( { error: { + name: "TTL save error", code: ERROR_CODES.CONFIG.TTL_SAVE_ERROR, message: getErrorMessage(ERROR_CODES.CONFIG.TTL_SAVE_ERROR), }, diff --git a/src/app/api/preferences/route.ts b/src/app/api/preferences/route.ts index 7d06516..409478e 100644 --- a/src/app/api/preferences/route.ts +++ b/src/app/api/preferences/route.ts @@ -15,6 +15,7 @@ export async function GET() { return NextResponse.json( { error: { + name: "Preferences fetch error", code: error.code, message: getErrorMessage(error.code), }, @@ -25,6 +26,7 @@ export async function GET() { return NextResponse.json( { error: { + name: "Preferences fetch error", code: ERROR_CODES.PREFERENCES.FETCH_ERROR, message: getErrorMessage(ERROR_CODES.PREFERENCES.FETCH_ERROR), }, @@ -47,6 +49,7 @@ export async function PUT(request: NextRequest) { return NextResponse.json( { error: { + name: "Preferences update error", code: error.code, message: getErrorMessage(error.code), }, @@ -57,6 +60,7 @@ export async function PUT(request: NextRequest) { return NextResponse.json( { error: { + name: "Preferences update error", code: ERROR_CODES.PREFERENCES.UPDATE_ERROR, message: getErrorMessage(ERROR_CODES.PREFERENCES.UPDATE_ERROR), }, diff --git a/src/app/libraries/[libraryId]/page.tsx b/src/app/libraries/[libraryId]/page.tsx index 2c3a6d8..63dde01 100644 --- a/src/app/libraries/[libraryId]/page.tsx +++ b/src/app/libraries/[libraryId]/page.tsx @@ -8,7 +8,6 @@ import { ErrorMessage } from "@/components/ui/ErrorMessage"; import { LibraryResponse } from "@/types/library"; import { KomgaSeries, KomgaLibrary } from "@/types/komga"; import { UserPreferences } from "@/types/preferences"; -import { ERROR_CODES } from "@/constants/errorCodes"; interface PageProps { params: { libraryId: string }; @@ -99,13 +98,13 @@ async function LibraryPage({ params, searchParams }: PageProps) {

Séries

- + ); } return (
- +
); } diff --git a/src/app/page.tsx b/src/app/page.tsx index df4a5be..028737b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,7 +5,7 @@ import { revalidatePath } from "next/cache"; import { withPageTiming } from "@/lib/hoc/withPageTiming"; import { ErrorMessage } from "@/components/ui/ErrorMessage"; import { HomeData } from "@/lib/services/home.service"; -import { ERROR_CODES } from "@/constants/errorCodes"; + async function refreshHome() { "use server"; @@ -32,7 +32,7 @@ async function HomePage() { return (
- +
); } diff --git a/src/app/series/[seriesId]/page.tsx b/src/app/series/[seriesId]/page.tsx index f6064f6..a06d597 100644 --- a/src/app/series/[seriesId]/page.tsx +++ b/src/app/series/[seriesId]/page.tsx @@ -78,7 +78,7 @@ async function SeriesPage({ params, searchParams }: PageProps) { return (

Série

- +
); } diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx index f35955c..e5f58b7 100644 --- a/src/components/auth/LoginForm.tsx +++ b/src/components/auth/LoginForm.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import { authService } from "@/lib/services/auth.service"; -import { AuthError } from "@/types/auth"; +import { AppErrorType } from "@/types/global"; import { ErrorMessage } from "@/components/ui/ErrorMessage"; import { useTranslate } from "@/hooks/useTranslate"; @@ -14,7 +14,7 @@ interface LoginFormProps { export function LoginForm({ from }: LoginFormProps) { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const { t } = useTranslate(); const handleSubmit = async (event: React.FormEvent) => { @@ -32,7 +32,7 @@ export function LoginForm({ from }: LoginFormProps) { router.push(from || "/"); router.refresh(); } catch (error) { - setError(error as AuthError); + setError(error as AppErrorType); } finally { setIsLoading(false); } @@ -89,7 +89,7 @@ export function LoginForm({ from }: LoginFormProps) { {t("login.form.remember")} - {error && } + {error && } + + + + + + ); +} diff --git a/src/components/settings/ClientSettings.tsx b/src/components/settings/ClientSettings.tsx index 388c5cc..1de43b0 100644 --- a/src/components/settings/ClientSettings.tsx +++ b/src/components/settings/ClientSettings.tsx @@ -1,16 +1,10 @@ "use client"; -import { useState } from "react"; -import { Loader2, Network, Trash2 } from "lucide-react"; -import { useRouter } from "next/navigation"; -import { AuthError } from "@/types/auth"; -import { useToast } from "@/components/ui/use-toast"; -import { usePreferences } from "@/contexts/PreferencesContext"; -import { Switch } from "@/components/ui/switch"; -import { Label } from "@/components/ui/label"; -import { CacheModeSwitch } from "@/components/settings/CacheModeSwitch"; import { KomgaConfig, TTLConfigData } from "@/types/komga"; import { useTranslate } from "@/hooks/useTranslate"; +import { DisplaySettings } from "./DisplaySettings"; +import { KomgaSettings } from "./KomgaSettings"; +import { CacheSettings } from "./CacheSettings"; interface ClientSettingsProps { initialConfig: KomgaConfig | null; @@ -19,633 +13,14 @@ interface ClientSettingsProps { export function ClientSettings({ initialConfig, initialTTLConfig }: ClientSettingsProps) { const { t } = useTranslate(); - const router = useRouter(); - const { toast } = useToast(); - const [isLoading, setIsLoading] = useState(false); - const [isSaving, setIsSaving] = useState(false); - const [isCacheClearing, setIsCacheClearing] = useState(false); - const [error, setError] = useState(null); - const [success, setSuccess] = useState(null); - const [config, setConfig] = useState({ - serverUrl: initialConfig?.url || "", - username: initialConfig?.username || "", - password: initialConfig?.password || "", - authHeader: initialConfig?.authHeader || "", - }); - const [isEditingConfig, setIsEditingConfig] = useState(false); - const [localInitialConfig, setLocalInitialConfig] = useState(initialConfig); - const [ttlConfig, setTTLConfig] = useState( - initialTTLConfig || { - defaultTTL: 5, - homeTTL: 5, - librariesTTL: 1440, - seriesTTL: 5, - booksTTL: 5, - imagesTTL: 1440, - } - ); - const { preferences, updatePreferences } = usePreferences(); - - const hasToShowEditForm = - localInitialConfig && config.serverUrl !== null && config.username !== null; - const shouldShowForm = !hasToShowEditForm || isEditingConfig; - - const handleClearCache = async () => { - setIsCacheClearing(true); - setError(null); - setSuccess(null); - - try { - const response = await fetch("/api/komga/cache/clear", { - method: "POST", - }); - - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || "Erreur lors de la suppression du cache"); - } - - toast({ - title: t("settings.cache.title"), - description: t("settings.cache.messages.cleared"), - }); - router.refresh(); - } catch (error) { - console.error("Erreur:", error); - toast({ - variant: "destructive", - title: "Erreur", - description: error instanceof Error ? error.message : "Une erreur est survenue", - }); - } finally { - setIsCacheClearing(false); - } - }; - - const handleTest = async () => { - setIsLoading(true); - setError(null); - setSuccess(null); - - const form = document.querySelector("form") as HTMLFormElement; - const formData = new FormData(form); - const serverUrl = formData.get("serverUrl") as string; - const username = formData.get("username") as string; - const password = formData.get("password") as string; - - try { - const response = await fetch("/api/komga/test", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - serverUrl: serverUrl.trim(), - username, - password: password || config.password, - }), - }); - - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || "Erreur lors du test de connexion"); - } - - toast({ - title: t("settings.komga.title"), - description: t("settings.komga.messages.connectionSuccess"), - }); - } catch (error) { - console.error("Erreur:", error); - toast({ - variant: "destructive", - title: "Erreur", - description: error instanceof Error ? error.message : "Une erreur est survenue", - }); - } finally { - setIsLoading(false); - } - }; - - const handleSave = async (event: React.FormEvent) => { - event.preventDefault(); - setSuccess(null); - setError(null); - setIsSaving(true); - - const formData = new FormData(event.currentTarget); - const serverUrl = formData.get("serverUrl") as string; - const username = formData.get("username") as string; - const password = formData.get("password") as string; - - const newConfig = { - serverUrl: serverUrl.trim(), - username, - password, - authHeader: config.authHeader, - }; - - try { - const response = await fetch("/api/komga/config", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - url: newConfig.serverUrl, - username: newConfig.username, - password: newConfig.password, - }), - }); - - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || "Erreur lors de la sauvegarde de la configuration"); - } - - const savedConfig = await response.json(); - - setConfig(newConfig); - setLocalInitialConfig({ - url: newConfig.serverUrl, - username: newConfig.username, - userId: savedConfig.userId, - authHeader: savedConfig.authHeader, - }); - setIsEditingConfig(false); - - toast({ - title: t("settings.komga.title"), - description: t("settings.komga.messages.configSaved"), - }); - - // Forcer un rechargement complet de la page - window.location.reload(); - } catch (error) { - console.error("Erreur lors de la sauvegarde:", error); - toast({ - variant: "destructive", - title: "Erreur", - description: - error instanceof Error ? error.message : "Une erreur est survenue lors de la sauvegarde", - }); - } finally { - setIsSaving(false); - } - }; - - const handleInputChange = (event: React.ChangeEvent) => { - const { name, value } = event.target; - setConfig((prev) => ({ - ...prev, - [name]: value, - })); - }; - - const handleTTLChange = (event: React.ChangeEvent) => { - const { name, value } = event.target; - setTTLConfig((prev) => ({ - ...prev, - [name]: parseInt(value || "0", 10), - })); - }; - - const handleSaveTTL = async (event: React.FormEvent) => { - event.preventDefault(); - setSuccess(null); - - try { - const response = await fetch("/api/komga/ttl-config", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(ttlConfig), - }); - - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || "Erreur lors de la sauvegarde de la configuration TTL"); - } - - toast({ - title: t("settings.cache.title"), - description: t("settings.cache.messages.ttlSaved"), - }); - } catch (error) { - console.error("Erreur lors de la sauvegarde:", error); - toast({ - variant: "destructive", - title: "Erreur", - description: - error instanceof Error ? error.message : "Une erreur est survenue lors de la sauvegarde", - }); - } - }; - - const handleToggleThumbnails = async (checked: boolean) => { - try { - await updatePreferences({ showThumbnails: checked }); - toast({ - title: t("settings.title"), - description: t("settings.komga.messages.configSaved"), - }); - } catch (error) { - toast({ - variant: "destructive", - title: "Erreur", - description: - error instanceof Error - ? error.message - : "Une erreur est survenue lors de la mise à jour des préférences", - }); - } - }; return ( -
-
-

{t("settings.title")}

-
- - {/* Messages de succès/erreur */} - {error && ( -
-

{t(`errors.${error.code}`)}

-
- )} - {success && ( -
-

{success}

-
- )} - -
- {/* Section Préférences d'affichage */} -
-
-
-

- {t("settings.display.title")} -

-

- {t("settings.display.description")} -

-
- -
-
-
- -

- {t("settings.display.thumbnails.description")} -

-
- -
-
-
- -

- {t("settings.display.unreadFilter.description")} -

-
- { - try { - await updatePreferences({ showOnlyUnread: checked }); - toast({ - title: t("settings.title"), - description: t("settings.komga.messages.configSaved"), - }); - } catch (error) { - console.error("Erreur détaillée:", error); - toast({ - variant: "destructive", - title: "Erreur", - description: - error instanceof Error - ? error.message - : "Une erreur est survenue lors de la mise à jour des préférences", - }); - } - }} - /> -
-
-
- -

- {t("settings.display.debugMode.description")} -

-
- { - try { - await updatePreferences({ debug: checked }); - toast({ - title: t("settings.title"), - description: t("settings.komga.messages.configSaved"), - }); - } catch (error) { - console.error("Erreur détaillée:", error); - toast({ - variant: "destructive", - title: "Erreur", - description: - error instanceof Error - ? error.message - : "Une erreur est survenue lors de la mise à jour des préférences", - }); - } - }} - /> -
-
-
-
- - {/* Section Configuration Komga */} -
-
-
-

- - {t("settings.komga.title")} -

-

- {t("settings.komga.description")} -

-
- - {!shouldShowForm ? ( -
-
-
- -

{config.serverUrl}

-
-
- -

{config.username}

-
-
- -

••••••••

-
-
- -
- ) : ( -
-
-
- - -
-
- - -
-
- - -
-
-
- - - {initialConfig && ( - - )} -
-
- )} -
-
- - {/* Section Configuration du Cache */} -
-
-
-

- - {t("settings.cache.title")} -

-

- {t("settings.cache.description")} -

-
- -
-
- -

- {t("settings.cache.mode.description")} -

-
- -
- - {/* Formulaire TTL */} -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
-
+
+

{t("settings.title")}

+
+ + +
); diff --git a/src/components/settings/DisplaySettings.tsx b/src/components/settings/DisplaySettings.tsx new file mode 100644 index 0000000..53a3ae6 --- /dev/null +++ b/src/components/settings/DisplaySettings.tsx @@ -0,0 +1,113 @@ +import { useTranslate } from "@/hooks/useTranslate"; +import { usePreferences } from "@/contexts/PreferencesContext"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; +import { useToast } from "@/components/ui/use-toast"; + +export function DisplaySettings() { + const { t } = useTranslate(); + const { toast } = useToast(); + const { preferences, updatePreferences } = usePreferences(); + + const handleToggleThumbnails = async (checked: boolean) => { + try { + await updatePreferences({ showThumbnails: checked }); + toast({ + title: t("settings.title"), + description: t("settings.komga.messages.configSaved"), + }); + } catch (error) { + console.error("Erreur détaillée:", error); + toast({ + variant: "destructive", + title: t("settings.error.title"), + description: t("settings.error.message"), + }); + } + }; + + return ( +
+
+
+

+ {t("settings.display.title")} +

+

{t("settings.display.description")}

+
+ +
+
+
+ +

+ {t("settings.display.thumbnails.description")} +

+
+ +
+
+
+ +

+ {t("settings.display.unreadFilter.description")} +

+
+ { + try { + await updatePreferences({ showOnlyUnread: checked }); + toast({ + title: t("settings.title"), + description: t("settings.komga.messages.configSaved"), + }); + } catch (error) { + console.error("Erreur détaillée:", error); + toast({ + variant: "destructive", + title: t("settings.error.title"), + description: t("settings.error.message"), + }); + } + }} + /> +
+
+
+ +

+ {t("settings.display.debugMode.description")} +

+
+ { + try { + await updatePreferences({ debug: checked }); + toast({ + title: t("settings.title"), + description: t("settings.komga.messages.configSaved"), + }); + } catch (error) { + console.error("Erreur détaillée:", error); + toast({ + variant: "destructive", + title: t("settings.error.title"), + description: t("settings.error.message"), + }); + } + }} + /> +
+
+
+
+ ); +} diff --git a/src/components/settings/KomgaSettings.tsx b/src/components/settings/KomgaSettings.tsx new file mode 100644 index 0000000..4453ded --- /dev/null +++ b/src/components/settings/KomgaSettings.tsx @@ -0,0 +1,280 @@ +"use client"; + +import { useState } from "react"; +import { useTranslate } from "@/hooks/useTranslate"; +import { useToast } from "@/components/ui/use-toast"; +import { Network, Loader2 } from "lucide-react"; +import { KomgaConfig } from "@/types/komga"; + +interface KomgaSettingsProps { + initialConfig: KomgaConfig | null; +} + +export function KomgaSettings({ initialConfig }: KomgaSettingsProps) { + const { t } = useTranslate(); + const { toast } = useToast(); + const [isLoading, setIsLoading] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [config, setConfig] = useState({ + serverUrl: initialConfig?.url || "", + username: initialConfig?.username || "", + password: initialConfig?.password || "", + authHeader: initialConfig?.authHeader || "", + }); + const [isEditingConfig, setIsEditingConfig] = useState(false); + const [localInitialConfig, setLocalInitialConfig] = useState(initialConfig); + + const hasToShowEditForm = + localInitialConfig && config.serverUrl !== null && config.username !== null; + const shouldShowForm = !hasToShowEditForm || isEditingConfig; + + const handleTest = async () => { + setIsLoading(true); + + const form = document.querySelector("form") as HTMLFormElement; + const formData = new FormData(form); + const serverUrl = formData.get("serverUrl") as string; + const username = formData.get("username") as string; + const password = formData.get("password") as string; + + try { + const response = await fetch("/api/komga/test", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + serverUrl: serverUrl.trim(), + username, + password: password || config.password, + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || t("settings.komga.error.message")); + } + + toast({ + title: t("settings.komga.title"), + description: t("settings.komga.messages.connectionSuccess"), + }); + } catch (error) { + console.error("Erreur:", error); + toast({ + variant: "destructive", + title: t("settings.komga.error.title"), + description: t("settings.komga.error.message"), + }); + } finally { + setIsLoading(false); + } + }; + + const handleSave = async (event: React.FormEvent) => { + event.preventDefault(); + setIsSaving(true); + + const formData = new FormData(event.currentTarget); + const serverUrl = formData.get("serverUrl") as string; + const username = formData.get("username") as string; + const password = formData.get("password") as string; + + const newConfig = { + serverUrl: serverUrl.trim(), + username, + password, + authHeader: config.authHeader, + }; + + try { + const response = await fetch("/api/komga/config", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url: newConfig.serverUrl, + username: newConfig.username, + password: newConfig.password, + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || t("settings.komga.error.message")); + } + + const savedConfig = await response.json(); + + setConfig(newConfig); + setLocalInitialConfig({ + url: newConfig.serverUrl, + username: newConfig.username, + userId: savedConfig.userId, + authHeader: savedConfig.authHeader, + }); + setIsEditingConfig(false); + + toast({ + title: t("settings.komga.title"), + description: t("settings.komga.messages.configSaved"), + }); + + // Forcer un rechargement complet de la page + window.location.reload(); + } catch (error) { + console.error("Erreur lors de la sauvegarde:", error); + toast({ + variant: "destructive", + title: t("settings.komga.error.title"), + description: t("settings.komga.error.message"), + }); + } finally { + setIsSaving(false); + } + }; + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setConfig((prev) => ({ + ...prev, + [name]: value, + })); + }; + + return ( +
+
+
+

+ + {t("settings.komga.title")} +

+

{t("settings.komga.description")}

+
+ + {!shouldShowForm ? ( +
+
+
+ +

{config.serverUrl}

+
+
+ +

{config.username}

+
+
+ +

••••••••

+
+
+ +
+ ) : ( +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + {initialConfig && ( + + )} +
+
+ )} +
+
+ ); +} diff --git a/src/components/ui/ErrorMessage.tsx b/src/components/ui/ErrorMessage.tsx index d3e14af..5705191 100644 --- a/src/components/ui/ErrorMessage.tsx +++ b/src/components/ui/ErrorMessage.tsx @@ -5,13 +5,16 @@ import { useTranslate } from "@/hooks/useTranslate"; interface ErrorMessageProps { errorCode: string; + error?: Error; variant?: "default" | "form"; } -export const ErrorMessage = ({ errorCode, variant = "default" }: ErrorMessageProps) => { +export const ErrorMessage = ({ errorCode, error, variant = "default" }: ErrorMessageProps) => { const { t } = useTranslate(); const message = t(`errors.${errorCode}`); + console.error(error); + if (variant === "form") { return (