refactor: remove client-only GET API routes for lot 1
This commit is contained in:
@@ -1,10 +1,36 @@
|
||||
"use server";
|
||||
|
||||
import { AdminService } from "@/lib/services/admin.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import type { AdminUserData } from "@/lib/services/admin.service";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import { AuthServerService } from "@/lib/services/auth-server.service";
|
||||
|
||||
export interface AdminStatsData {
|
||||
totalUsers: number;
|
||||
totalAdmins: number;
|
||||
usersWithKomga: number;
|
||||
usersWithPreferences: number;
|
||||
}
|
||||
|
||||
export async function getAdminDashboardData(): Promise<{
|
||||
success: boolean;
|
||||
users?: AdminUserData[];
|
||||
stats?: AdminStatsData;
|
||||
message?: string;
|
||||
}> {
|
||||
try {
|
||||
const [users, stats] = await Promise.all([AdminService.getAllUsers(), AdminService.getUserStats()]);
|
||||
|
||||
return { success: true, users, stats };
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
|
||||
return { success: false, message: "Erreur lors de la récupération des données admin" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour les rôles d'un utilisateur
|
||||
*/
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { AdminService } from "@/lib/services/admin.service";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const stats = await AdminService.getUserStats();
|
||||
return NextResponse.json(stats);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la récupération des stats:");
|
||||
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, code: error.code },
|
||||
{
|
||||
status:
|
||||
error.code === "AUTH_FORBIDDEN"
|
||||
? 403
|
||||
: error.code === "AUTH_UNAUTHENTICATED"
|
||||
? 401
|
||||
: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la récupération des stats" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { AdminService } from "@/lib/services/admin.service";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const users = await AdminService.getAllUsers();
|
||||
return NextResponse.json(users);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la récupération des utilisateurs:");
|
||||
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, code: error.code },
|
||||
{
|
||||
status:
|
||||
error.code === "AUTH_FORBIDDEN"
|
||||
? 403
|
||||
: error.code === "AUTH_UNAUTHENTICATED"
|
||||
? 401
|
||||
: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la récupération des utilisateurs" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { FavoriteService } from "@/lib/services/favorite.service";
|
||||
import { SeriesService } from "@/lib/services/series.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
// GET reste utilisé par Sidebar et SeriesHeader pour récupérer la liste des favoris
|
||||
export async function GET() {
|
||||
try {
|
||||
const favoriteIds: string[] = await FavoriteService.getAllFavoriteIds();
|
||||
|
||||
// Valider que chaque série existe encore dans Komga
|
||||
const validFavoriteIds: string[] = [];
|
||||
|
||||
for (const seriesId of favoriteIds) {
|
||||
try {
|
||||
await SeriesService.getSeries(seriesId);
|
||||
validFavoriteIds.push(seriesId);
|
||||
} catch {
|
||||
// Si la série n'existe plus dans Komga, on la retire des favoris
|
||||
try {
|
||||
await FavoriteService.removeFromFavorites(seriesId);
|
||||
} catch {
|
||||
// Erreur silencieuse, la série reste dans les favoris
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(validFavoriteIds);
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
// Si la config Komga n'existe pas, retourner un tableau vide au lieu d'une erreur
|
||||
if (error.code === ERROR_CODES.KOMGA.MISSING_CONFIG) {
|
||||
return NextResponse.json([]);
|
||||
}
|
||||
}
|
||||
logger.error({ err: error }, "Erreur lors de la récupération des favoris:");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: error.code,
|
||||
name: "Favorite fetch error",
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.FAVORITE.FETCH_ERROR,
|
||||
name: "Favorite fetch error",
|
||||
message: getErrorMessage(ERROR_CODES.FAVORITE.FETCH_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { LibraryService } from "@/lib/services/library.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import type { KomgaLibrary } from "@/types/komga";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
// Cache handled in service via fetchFromApi options
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const libraries: KomgaLibrary[] = await LibraryService.getLibraries();
|
||||
return NextResponse.json(libraries);
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
// Si la config Komga n'existe pas, retourner un tableau vide au lieu d'une erreur
|
||||
if (error.code === ERROR_CODES.KOMGA.MISSING_CONFIG) {
|
||||
return NextResponse.json([]);
|
||||
}
|
||||
}
|
||||
logger.error({ err: error }, "API Libraries - Erreur:");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: error.code,
|
||||
name: "Library fetch error",
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
code: ERROR_CODES.LIBRARY.FETCH_ERROR,
|
||||
name: "Library fetch error",
|
||||
message: getErrorMessage(ERROR_CODES.LIBRARY.FETCH_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { PreferencesService } from "@/lib/services/preferences.service";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import type { UserPreferences } from "@/types/preferences";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
// GET reste utilisé par PreferencesContext pour récupérer les préférences
|
||||
export async function GET() {
|
||||
try {
|
||||
const preferences: UserPreferences = await PreferencesService.getPreferences();
|
||||
return NextResponse.json(preferences);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la récupération des préférences:");
|
||||
if (error instanceof AppError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
name: "Preferences fetch error",
|
||||
code: error.code,
|
||||
message: getErrorMessage(error.code),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: {
|
||||
name: "Preferences fetch error",
|
||||
code: ERROR_CODES.PREFERENCES.FETCH_ERROR,
|
||||
message: getErrorMessage(ERROR_CODES.PREFERENCES.FETCH_ERROR),
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ interface SeriesContentProps {
|
||||
preferences: UserPreferences;
|
||||
unreadOnly: boolean;
|
||||
pageSize: number;
|
||||
initialIsFavorite: boolean;
|
||||
}
|
||||
|
||||
export function SeriesContent({
|
||||
@@ -23,6 +24,7 @@ export function SeriesContent({
|
||||
currentPage,
|
||||
preferences,
|
||||
unreadOnly,
|
||||
initialIsFavorite,
|
||||
}: SeriesContentProps) {
|
||||
const { refreshSeries } = useRefresh();
|
||||
|
||||
@@ -31,6 +33,7 @@ export function SeriesContent({
|
||||
<SeriesHeader
|
||||
series={series}
|
||||
refreshSeries={refreshSeries || (async () => ({ success: false }))}
|
||||
initialIsFavorite={initialIsFavorite}
|
||||
/>
|
||||
<Container>
|
||||
<PaginatedBookGrid
|
||||
@@ -45,4 +48,3 @@ export function SeriesContent({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PreferencesService } from "@/lib/services/preferences.service";
|
||||
import { SeriesService } from "@/lib/services/series.service";
|
||||
import { FavoriteService } from "@/lib/services/favorite.service";
|
||||
import { SeriesClientWrapper } from "./SeriesClientWrapper";
|
||||
import { SeriesContent } from "./SeriesContent";
|
||||
import { ErrorMessage } from "@/components/ui/ErrorMessage";
|
||||
@@ -28,9 +29,10 @@ export default async function SeriesPage({ params, searchParams }: PageProps) {
|
||||
const effectivePageSize = size ? parseInt(size) : preferences.displayMode?.itemsPerPage || DEFAULT_PAGE_SIZE;
|
||||
|
||||
try {
|
||||
const [books, series] = await Promise.all([
|
||||
const [books, series, isFavorite] = await Promise.all([
|
||||
SeriesService.getSeriesBooks(seriesId, currentPage - 1, effectivePageSize, unreadOnly),
|
||||
SeriesService.getSeries(seriesId),
|
||||
FavoriteService.isFavorite(seriesId),
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -48,6 +50,7 @@ export default async function SeriesPage({ params, searchParams }: PageProps) {
|
||||
preferences={preferences}
|
||||
unreadOnly={unreadOnly}
|
||||
pageSize={effectivePageSize}
|
||||
initialIsFavorite={isFavorite}
|
||||
/>
|
||||
</SeriesClientWrapper>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ConfigDBService } from "@/lib/services/config-db.service";
|
||||
import { LibraryService } from "@/lib/services/library.service";
|
||||
import { ClientSettings } from "@/components/settings/ClientSettings";
|
||||
import type { Metadata } from "next";
|
||||
import type { KomgaConfig } from "@/types/komga";
|
||||
import type { KomgaConfig, KomgaLibrary } from "@/types/komga";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -13,6 +14,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default async function SettingsPage() {
|
||||
let config: KomgaConfig | null = null;
|
||||
let libraries: KomgaLibrary[] = [];
|
||||
|
||||
try {
|
||||
// Récupérer la configuration Komga
|
||||
@@ -26,10 +28,12 @@ export default async function SettingsPage() {
|
||||
password: null,
|
||||
};
|
||||
}
|
||||
|
||||
libraries = await LibraryService.getLibraries();
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la récupération de la configuration:");
|
||||
// On ne fait rien si la config n'existe pas, on laissera le composant client gérer l'état initial
|
||||
}
|
||||
|
||||
return <ClientSettings initialConfig={config} />;
|
||||
return <ClientSettings initialConfig={config} initialLibraries={libraries} />;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import type { AdminUserData } from "@/lib/services/admin.service";
|
||||
import { StatsCards } from "./StatsCards";
|
||||
import { UsersTable } from "./UsersTable";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getAdminDashboardData, type AdminStatsData } from "@/app/actions/admin";
|
||||
|
||||
interface AdminContentProps {
|
||||
initialUsers: AdminUserData[];
|
||||
initialStats: {
|
||||
totalUsers: number;
|
||||
totalAdmins: number;
|
||||
usersWithKomga: number;
|
||||
usersWithPreferences: number;
|
||||
};
|
||||
initialStats: AdminStatsData;
|
||||
}
|
||||
|
||||
export function AdminContent({ initialUsers, initialStats }: AdminContentProps) {
|
||||
@@ -24,22 +20,25 @@ export function AdminContent({ initialUsers, initialStats }: AdminContentProps)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
setUsers(initialUsers);
|
||||
}, [initialUsers]);
|
||||
|
||||
useEffect(() => {
|
||||
setStats(initialStats);
|
||||
}, [initialStats]);
|
||||
|
||||
const refreshData = useCallback(async () => {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
const [usersResponse, statsResponse] = await Promise.all([
|
||||
fetch("/api/admin/users"),
|
||||
fetch("/api/admin/stats"),
|
||||
]);
|
||||
const result = await getAdminDashboardData();
|
||||
|
||||
if (!usersResponse.ok || !statsResponse.ok) {
|
||||
if (!result.success || !result.users || !result.stats) {
|
||||
throw new Error("Erreur lors du rafraîchissement");
|
||||
}
|
||||
|
||||
const [newUsers, newStats] = await Promise.all([usersResponse.json(), statsResponse.json()]);
|
||||
|
||||
setUsers(newUsers);
|
||||
setStats(newStats);
|
||||
setUsers(result.users);
|
||||
setStats(result.stats);
|
||||
|
||||
toast({
|
||||
title: "Données rafraîchies",
|
||||
|
||||
@@ -16,9 +16,6 @@ import { cn } from "@/lib/utils";
|
||||
import { signOut } from "next-auth/react";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import type { KomgaLibrary, KomgaSeries } from "@/types/komga";
|
||||
import { AppError } from "@/utils/errors";
|
||||
import { ERROR_CODES } from "@/constants/errorCodes";
|
||||
import { getErrorMessage } from "@/utils/errors";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { NavButton } from "@/components/ui/nav-button";
|
||||
@@ -49,73 +46,42 @@ export function Sidebar({
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const refreshFavorites = useCallback(async () => {
|
||||
try {
|
||||
const favoritesResponse = await fetch("/api/komga/favorites");
|
||||
if (!favoritesResponse.ok) {
|
||||
throw new AppError(ERROR_CODES.FAVORITE.FETCH_ERROR);
|
||||
}
|
||||
const favoriteIds = await favoritesResponse.json();
|
||||
useEffect(() => {
|
||||
setLibraries(initialLibraries || []);
|
||||
}, [initialLibraries]);
|
||||
|
||||
if (favoriteIds.length === 0) {
|
||||
setFavorites([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const promises = favoriteIds.map(async (id: string) => {
|
||||
const response = await fetch(`/api/komga/series/${id}`);
|
||||
if (!response.ok) {
|
||||
throw new AppError(ERROR_CODES.SERIES.FETCH_ERROR);
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
setFavorites(results.filter((series): series is KomgaSeries => series !== null));
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur de chargement des favoris:");
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description:
|
||||
error instanceof AppError
|
||||
? error.message
|
||||
: getErrorMessage(ERROR_CODES.FAVORITE.FETCH_ERROR),
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}, [toast]);
|
||||
useEffect(() => {
|
||||
setFavorites(initialFavorites || []);
|
||||
}, [initialFavorites]);
|
||||
|
||||
// Mettre à jour les favoris quand ils changent (mise à jour optimiste)
|
||||
useEffect(() => {
|
||||
const handleFavoritesChange = async (event: Event) => {
|
||||
const customEvent = event as CustomEvent<{ seriesId: string; action: "add" | "remove" }>;
|
||||
const handleFavoritesChange = (event: Event) => {
|
||||
const customEvent = event as CustomEvent<{
|
||||
seriesId?: string;
|
||||
action?: "add" | "remove";
|
||||
series?: KomgaSeries;
|
||||
}>;
|
||||
|
||||
// Si on a les détails de l'action, faire une mise à jour optimiste locale
|
||||
if (customEvent.detail?.seriesId) {
|
||||
const { seriesId, action } = customEvent.detail;
|
||||
const { seriesId, action, series } = customEvent.detail;
|
||||
|
||||
if (action === "add") {
|
||||
// Fetch les détails de la série ajoutée et l'ajouter au state
|
||||
try {
|
||||
const response = await fetch(`/api/komga/series/${seriesId}`);
|
||||
if (response.ok) {
|
||||
const seriesData = await response.json();
|
||||
setFavorites((prev) => {
|
||||
// Éviter les doublons
|
||||
if (prev.some((s) => s.id === seriesId)) return prev;
|
||||
return [...prev, seriesData];
|
||||
});
|
||||
if (action === "add" && series) {
|
||||
setFavorites((prev) => {
|
||||
if (prev.some((s) => s.id === series.id)) {
|
||||
return prev;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de l'ajout optimiste du favori:");
|
||||
}
|
||||
|
||||
return [...prev, series];
|
||||
});
|
||||
} else if (action === "remove") {
|
||||
// Retirer la série du state directement
|
||||
setFavorites((prev) => prev.filter((s) => s.id !== seriesId));
|
||||
} else {
|
||||
router.refresh();
|
||||
}
|
||||
} else {
|
||||
// Fallback: refetch complet si pas de détails (ex: événement externe)
|
||||
refreshFavorites();
|
||||
router.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -124,7 +90,7 @@ export function Sidebar({
|
||||
return () => {
|
||||
window.removeEventListener("favoritesChanged", handleFavoritesChange);
|
||||
};
|
||||
}, [refreshFavorites]);
|
||||
}, [router]);
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
|
||||
@@ -18,37 +18,17 @@ import { addToFavorites, removeFromFavorites } from "@/app/actions/favorites";
|
||||
interface SeriesHeaderProps {
|
||||
series: KomgaSeries;
|
||||
refreshSeries: (seriesId: string) => Promise<{ success: boolean; error?: string }>;
|
||||
initialIsFavorite: boolean;
|
||||
}
|
||||
|
||||
export const SeriesHeader = ({ series, refreshSeries }: SeriesHeaderProps) => {
|
||||
export const SeriesHeader = ({ series, refreshSeries, initialIsFavorite }: SeriesHeaderProps) => {
|
||||
const { toast } = useToast();
|
||||
const [isFavorite, setIsFavorite] = useState(false);
|
||||
const [isFavorite, setIsFavorite] = useState(initialIsFavorite);
|
||||
const { t } = useTranslate();
|
||||
|
||||
useEffect(() => {
|
||||
const checkFavorite = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/komga/favorites");
|
||||
if (!response.ok) {
|
||||
throw new AppError(ERROR_CODES.FAVORITE.STATUS_CHECK_ERROR);
|
||||
}
|
||||
const favoriteIds = await response.json();
|
||||
setIsFavorite(favoriteIds.includes(series.id));
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la vérification des favoris:");
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description:
|
||||
error instanceof AppError
|
||||
? error.message
|
||||
: getErrorMessage(ERROR_CODES.FAVORITE.NETWORK_ERROR),
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
checkFavorite();
|
||||
}, [series.id, toast]);
|
||||
setIsFavorite(initialIsFavorite);
|
||||
}, [series.id, initialIsFavorite]);
|
||||
|
||||
const handleToggleFavorite = async () => {
|
||||
try {
|
||||
@@ -59,7 +39,11 @@ export const SeriesHeader = ({ series, refreshSeries }: SeriesHeaderProps) => {
|
||||
setIsFavorite(!isFavorite);
|
||||
// Dispatcher l'événement avec le seriesId pour mise à jour optimiste de la sidebar
|
||||
const event = new CustomEvent("favoritesChanged", {
|
||||
detail: { seriesId: series.id, action: isFavorite ? "remove" : "add" },
|
||||
detail: {
|
||||
seriesId: series.id,
|
||||
action: isFavorite ? "remove" : "add",
|
||||
series: isFavorite ? undefined : series,
|
||||
},
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
toast({
|
||||
|
||||
@@ -17,34 +17,28 @@ import { SliderControl } from "@/components/ui/slider-control";
|
||||
import type { KomgaLibrary } from "@/types/komga";
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export function BackgroundSettings() {
|
||||
interface BackgroundSettingsProps {
|
||||
initialLibraries: KomgaLibrary[];
|
||||
}
|
||||
|
||||
export function BackgroundSettings({ initialLibraries }: BackgroundSettingsProps) {
|
||||
const { t } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
const { preferences, updatePreferences } = usePreferences();
|
||||
const [customImageUrl, setCustomImageUrl] = useState(preferences.background.imageUrl || "");
|
||||
const [komgaConfigValid, setKomgaConfigValid] = useState(false);
|
||||
const [libraries, setLibraries] = useState<KomgaLibrary[]>([]);
|
||||
const [libraries, setLibraries] = useState<KomgaLibrary[]>(initialLibraries || []);
|
||||
const [selectedLibraries, setSelectedLibraries] = useState<string[]>(
|
||||
preferences.background.komgaLibraries || []
|
||||
);
|
||||
|
||||
// Vérifier la config Komga au chargement
|
||||
useEffect(() => {
|
||||
const checkKomgaConfig = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/komga/libraries");
|
||||
if (response.ok) {
|
||||
const libs = await response.json();
|
||||
setLibraries(libs);
|
||||
setKomgaConfigValid(libs.length > 0);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la vérification de la config Komga:");
|
||||
setKomgaConfigValid(false);
|
||||
}
|
||||
};
|
||||
checkKomgaConfig();
|
||||
}, []);
|
||||
setLibraries(initialLibraries || []);
|
||||
}, [initialLibraries]);
|
||||
|
||||
useEffect(() => {
|
||||
setKomgaConfigValid(libraries.length > 0);
|
||||
}, [libraries]);
|
||||
|
||||
const handleBackgroundTypeChange = async (type: BackgroundType) => {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import type { KomgaConfig } from "@/types/komga";
|
||||
import type { KomgaLibrary } from "@/types/komga";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { DisplaySettings } from "./DisplaySettings";
|
||||
import { KomgaSettings } from "./KomgaSettings";
|
||||
@@ -12,9 +13,10 @@ import { Monitor, Network } from "lucide-react";
|
||||
|
||||
interface ClientSettingsProps {
|
||||
initialConfig: KomgaConfig | null;
|
||||
initialLibraries: KomgaLibrary[];
|
||||
}
|
||||
|
||||
export function ClientSettings({ initialConfig }: ClientSettingsProps) {
|
||||
export function ClientSettings({ initialConfig, initialLibraries }: ClientSettingsProps) {
|
||||
const { t } = useTranslate();
|
||||
|
||||
return (
|
||||
@@ -35,7 +37,7 @@ export function ClientSettings({ initialConfig }: ClientSettingsProps) {
|
||||
|
||||
<TabsContent value="display" className="mt-6 space-y-6">
|
||||
<DisplaySettings />
|
||||
<BackgroundSettings />
|
||||
<BackgroundSettings initialLibraries={initialLibraries} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="connection" className="mt-6 space-y-6">
|
||||
|
||||
@@ -17,10 +17,6 @@ interface PreferencesContextType {
|
||||
|
||||
const PreferencesContext = createContext<PreferencesContextType | undefined>(undefined);
|
||||
|
||||
// Module-level flag to prevent duplicate fetches (survives StrictMode remounts)
|
||||
let preferencesFetchInProgress = false;
|
||||
let preferencesFetched = false;
|
||||
|
||||
export function PreferencesProvider({
|
||||
children,
|
||||
initialPreferences,
|
||||
@@ -32,58 +28,23 @@ export function PreferencesProvider({
|
||||
const [preferences, setPreferences] = useState<UserPreferences>(
|
||||
initialPreferences || defaultPreferences
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const isLoading = false;
|
||||
|
||||
// Check if we have valid initial preferences from server
|
||||
const hasValidInitialPreferences =
|
||||
initialPreferences && Object.keys(initialPreferences).length > 0;
|
||||
|
||||
const fetchPreferences = useCallback(async () => {
|
||||
// Prevent concurrent fetches
|
||||
if (preferencesFetchInProgress || preferencesFetched) {
|
||||
useEffect(() => {
|
||||
if (status === "authenticated" && hasValidInitialPreferences) {
|
||||
setPreferences(initialPreferences);
|
||||
return;
|
||||
}
|
||||
preferencesFetchInProgress = true;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/preferences");
|
||||
if (!response.ok) {
|
||||
throw new AppError(ERROR_CODES.PREFERENCES.FETCH_ERROR);
|
||||
}
|
||||
const data = await response.json();
|
||||
setPreferences({
|
||||
...defaultPreferences,
|
||||
...data,
|
||||
displayMode: {
|
||||
...defaultPreferences.displayMode,
|
||||
...(data.displayMode || {}),
|
||||
viewMode: data.displayMode?.viewMode || defaultPreferences.displayMode.viewMode,
|
||||
},
|
||||
});
|
||||
preferencesFetched = true;
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Erreur lors de la récupération des préférences");
|
||||
setPreferences(defaultPreferences);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
preferencesFetchInProgress = false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "authenticated") {
|
||||
// Skip refetch if we already have valid initial preferences from server
|
||||
if (hasValidInitialPreferences) {
|
||||
preferencesFetched = true; // Mark as fetched since we have server data
|
||||
return;
|
||||
}
|
||||
fetchPreferences();
|
||||
} else if (status === "unauthenticated") {
|
||||
if (status === "unauthenticated") {
|
||||
// Reset to defaults when user logs out
|
||||
setPreferences(defaultPreferences);
|
||||
preferencesFetched = false; // Allow refetch on next login
|
||||
}
|
||||
}, [status, fetchPreferences, hasValidInitialPreferences]);
|
||||
}, [status, hasValidInitialPreferences, initialPreferences]);
|
||||
|
||||
const updatePreferences = useCallback(async (newPreferences: Partial<UserPreferences>): Promise<UserPreferences | undefined> => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user