refactor: remove client-only GET API routes for lot 1

This commit is contained in:
2026-02-28 11:43:11 +01:00
parent 7f361ce0a2
commit 29f5324bd7
17 changed files with 214 additions and 430 deletions

View File

@@ -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",

View File

@@ -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);

View File

@@ -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({

View File

@@ -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 {

View File

@@ -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">