refacto: tentative de refacto

This commit is contained in:
Julien Froidefond
2025-02-17 16:37:48 +01:00
parent 7ee99ac31a
commit ba725bb1a3
28 changed files with 195 additions and 170 deletions

View File

@@ -23,10 +23,6 @@ export function HomeContent({ data }: HomeContentProps) {
await router.push(path);
};
const handleSeriesClick = (seriesId: string) => {
router.push(`/series/${seriesId}`);
};
// Vérification des données pour le debug
// console.log("HomeContent - Données reçues:", {
// ongoingCount: data.ongoing?.length || 0,

View File

@@ -86,18 +86,9 @@ function MediaCard({ item, onClick }: MediaCardProps) {
? item.metadata.title
: item.metadata.title || `Tome ${item.metadata.number}`;
const handleClick = () => {
console.log("MediaCard - handleClick:", {
itemType: isSeries ? "series" : "book",
itemId: item.id,
itemTitle: title,
});
onClick?.();
};
return (
<div
onClick={handleClick}
onClick={onClick}
className="flex-shrink-0 w-[200px] relative flex flex-col rounded-lg border bg-card text-card-foreground shadow-sm hover:bg-accent hover:text-accent-foreground transition-colors overflow-hidden cursor-pointer"
>
{/* Image de couverture */}

View File

@@ -6,8 +6,7 @@ import { Header } from "@/components/layout/Header";
import { Sidebar } from "@/components/layout/Sidebar";
import { InstallPWA } from "../ui/InstallPWA";
import { Toaster } from "@/components/ui/toaster";
import { usePathname, useRouter } from "next/navigation";
import { authService } from "@/lib/services/auth.service";
import { usePathname } from "next/navigation";
import { PreferencesProvider } from "@/contexts/PreferencesContext";
// Routes qui ne nécessitent pas d'authentification
@@ -15,7 +14,6 @@ const publicRoutes = ["/login", "/register"];
export default function ClientLayout({ children }: { children: React.ReactNode }) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const router = useRouter();
const pathname = usePathname();
const handleCloseSidebar = () => {
@@ -56,8 +54,8 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("Service Worker enregistré avec succès:", registration);
.then(() => {
// Succès silencieux
})
.catch((error) => {
console.error("Erreur lors de l'enregistrement du Service Worker:", error);

View File

@@ -1,7 +1,6 @@
"use client";
import { BookOpen, Home, Library, Settings, LogOut, RefreshCw, Star } from "lucide-react";
import Link from "next/link";
import { Home, Library, Settings, LogOut, RefreshCw, Star } from "lucide-react";
import { usePathname, useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
import { authService } from "@/lib/services/auth.service";
@@ -32,7 +31,9 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
const data = await response.json();
setLibraries(data);
} catch (error) {
console.error("Erreur:", error);
if (error instanceof Error) {
console.error("Erreur de chargement des bibliothèques:", error.message);
}
setLibraries([]);
} finally {
setIsLoading(false);
@@ -43,7 +44,6 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
const fetchFavorites = useCallback(async () => {
setIsLoadingFavorites(true);
try {
// Récupérer les IDs des favoris depuis l'API
const favoritesResponse = await fetch("/api/komga/favorites");
if (!favoritesResponse.ok) {
throw new Error("Erreur lors de la récupération des favoris");
@@ -55,7 +55,6 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
return;
}
// Récupérer les détails des séries pour chaque ID
const promises = favoriteIds.map(async (id: string) => {
const response = await fetch(`/api/komga/series/${id}`);
if (!response.ok) return null;
@@ -65,7 +64,9 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
const results = await Promise.all(promises);
setFavorites(results.filter((series): series is KomgaSeries => series !== null));
} catch (error) {
console.error("Erreur lors de la récupération des favoris:", error);
if (error instanceof Error) {
console.error("Erreur de chargement des favoris:", error.message);
}
setFavorites([]);
} finally {
setIsLoadingFavorites(false);

View File

@@ -6,9 +6,10 @@ import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
import { Loader2, Filter } from "lucide-react";
import { cn } from "@/lib/utils";
import { KomgaSeries } from "@/types/komga";
interface PaginatedSeriesGridProps {
series: any[];
series: KomgaSeries[];
currentPage: number;
totalPages: number;
totalElements: number;
@@ -61,10 +62,6 @@ export function PaginatedSeriesGrid({
await router.push(`${pathname}?${params.toString()}`);
};
const handleSeriesClick = (seriesId: string) => {
router.push(`/series/${seriesId}`);
};
// Calcul des indices de début et de fin pour l'affichage
const startIndex = (currentPage - 1) * pageSize + 1;
const endIndex = Math.min(currentPage * pageSize, totalElements);

View File

@@ -24,12 +24,9 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
setIsLoading,
secondPageLoading,
setSecondPageLoading,
imageError,
setImageError,
handlePreviousPage,
handleNextPage,
shouldShowDoublePage,
syncReadProgress,
} = usePageNavigation({
book,
pages,
@@ -66,9 +63,12 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
}
}
} catch (error) {
console.error("Erreur lors du chargement des URLs:", error);
setImageError(true);
} finally {
if (error instanceof Error) {
console.error(
`Erreur de chargement des URLs pour la page ${currentPage}:`,
error.message
);
}
// On s'assure que le chargement est terminé même en cas d'erreur
if (isMounted) {
setIsLoading(false);
@@ -91,7 +91,6 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
getPageUrl,
setIsLoading,
setSecondPageLoading,
setImageError,
]);
// Effet pour précharger la page courante et les pages adjacentes
@@ -205,6 +204,13 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
{/* Pages */}
<div className="relative flex-1 flex items-center justify-center overflow-hidden p-1">
<div className="relative w-full h-[calc(100vh-2rem)] flex items-center justify-center gap-0">
{/*
Note: Nous utilisons intentionnellement des balises <img> natives au lieu de next/image pour :
1. Avoir un contrôle précis sur le chargement et le préchargement des pages
2. Gérer efficacement le mode double page et les transitions
3. Les images sont déjà optimisées côté serveur
4. La performance est critique pour une lecture fluide
*/}
{/* Page courante */}
<div
className={cn(

View File

@@ -3,8 +3,6 @@
import { KomgaBook } from "@/types/komga";
import { BookReader } from "./BookReader";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useToast } from "@/components/ui/use-toast";
interface ClientBookWrapperProps {
book: KomgaBook;

View File

@@ -31,6 +31,10 @@ export const ControlButtons = ({
"absolute top-4 left-1/2 -translate-x-1/2 z-30 flex items-center gap-2 transition-all duration-300",
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
)}
onClick={(e) => {
e.stopPropagation();
onToggleControls();
}}
>
<button
onClick={(e) => {

View File

@@ -2,7 +2,7 @@ import { NavigationBarProps } from "../types";
import { cn } from "@/lib/utils";
import { Thumbnail } from "./Thumbnail";
import { useThumbnails } from "../hooks/useThumbnails";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
export const NavigationBar = ({
currentPage,
@@ -11,16 +11,13 @@ export const NavigationBar = ({
showControls,
book,
}: NavigationBarProps) => {
const {
loadedThumbnails,
handleThumbnailLoad,
getThumbnailUrl,
visibleThumbnails,
scrollToActiveThumbnail,
} = useThumbnails({
book,
currentPage,
});
const { loadedThumbnails, handleThumbnailLoad, getThumbnailUrl, visibleThumbnails } =
useThumbnails({
book,
currentPage,
});
const thumbnailsContainerRef = useRef<HTMLDivElement>(null);
// Scroll à l'ouverture des contrôles et au changement de page
useEffect(() => {
@@ -53,6 +50,7 @@ export const NavigationBar = ({
onTouchStart={(e) => e.stopPropagation()}
onTouchMove={(e) => e.stopPropagation()}
onTouchEnd={(e) => e.stopPropagation()}
ref={thumbnailsContainerRef}
>
<div className="w-[calc(50vw-18rem)] flex-shrink-0" />
{pages.map((_, index) => {

View File

@@ -6,15 +6,7 @@ import { forwardRef, useEffect, useState, useCallback, useRef } from "react";
export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
(
{
pageNumber,
currentPage,
onPageChange,
getThumbnailUrl,
loadedThumbnails,
onThumbnailLoad,
isVisible,
},
{ pageNumber, currentPage, onPageChange, getThumbnailUrl, loadedThumbnails, onThumbnailLoad },
ref
) => {
const [imageUrl, setImageUrl] = useState<string | null>(null);
@@ -59,9 +51,6 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
if (loadAttempts.current < maxAttempts) {
// Réessayer avec un délai croissant
const delay = Math.min(1000 * Math.pow(2, loadAttempts.current - 1), 5000);
console.log(
`Réessai ${loadAttempts.current}/${maxAttempts} dans ${delay}ms pour la page ${pageNumber}`
);
setTimeout(() => {
setImageUrl((prev) => (prev ? `${prev}?retry=${loadAttempts.current}` : null));
}, delay);
@@ -94,7 +83,7 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
src={imageUrl}
alt={`Miniature page ${pageNumber}`}
className={cn(
"object-cover transition-opacity duration-300",
"object-contain transition-opacity duration-300",
isLoading ? "opacity-0" : "opacity-100"
)}
fill

View File

@@ -12,12 +12,11 @@ export const usePageNavigation = ({
book,
pages,
isDoublePage,
onClose = () => {},
onClose,
}: UsePageNavigationProps) => {
const [currentPage, setCurrentPage] = useState(book.readProgress?.page || 1);
const [isLoading, setIsLoading] = useState(true);
const [secondPageLoading, setSecondPageLoading] = useState(true);
const [imageError, setImageError] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const touchStartXRef = useRef<number | null>(null);
const touchStartYRef = useRef<number | null>(null);
@@ -39,7 +38,12 @@ export const usePageNavigation = ({
body: JSON.stringify({ page, completed }),
});
} catch (error) {
console.error("Erreur lors de la synchronisation de la progression:", error);
if (error instanceof Error) {
console.error(
`Erreur de synchronisation de la progression pour le livre ${book.id} à la page ${page}:`,
error.message
);
}
}
},
[book.id, pages.length]
@@ -73,7 +77,6 @@ export const usePageNavigation = ({
setCurrentPage(page);
setIsLoading(true);
setSecondPageLoading(true);
setImageError(false);
debouncedSyncReadProgress(page);
},
[debouncedSyncReadProgress]
@@ -167,11 +170,8 @@ export const usePageNavigation = ({
setIsLoading,
secondPageLoading,
setSecondPageLoading,
imageError,
setImageError,
handlePreviousPage,
handleNextPage,
shouldShowDoublePage,
syncReadProgress: debouncedSyncReadProgress,
};
};

View File

@@ -16,7 +16,8 @@ export const useThumbnails = ({ book, currentPage }: UseThumbnailsProps) => {
const getThumbnailUrl = useCallback(
(pageNumber: number) => {
return `/api/komga/images/books/${book.id}/pages/${pageNumber}/thumbnail`;
const zeroBasedPage = pageNumber - 1;
return `/api/komga/images/books/${book.id}/pages/${zeroBasedPage}/thumbnail?zero_based=true`;
},
[book.id]
);

View File

@@ -70,10 +70,6 @@ export function PaginatedBookGrid({
const startIndex = (currentPage - 1) * pageSize + 1;
const endIndex = Math.min(currentPage * pageSize, totalElements);
const getBookThumbnailUrl = (bookId: string) => {
return `/api/komga/images/books/${bookId}/thumbnail`;
};
return (
<div className="space-y-8">
<div className="flex items-center justify-between flex-wrap gap-4">
@@ -115,11 +111,7 @@ export function PaginatedBookGrid({
isChangingPage ? "opacity-25" : "opacity-100"
)}
>
<BookGrid
books={books}
onBookClick={handleBookClick}
getBookThumbnailUrl={getBookThumbnailUrl}
/>
<BookGrid books={books} onBookClick={handleBookClick} />
</div>
</div>

View File

@@ -5,15 +5,13 @@ import { KomgaSeries } from "@/types/komga";
import { useState, useEffect } from "react";
import { Button } from "../ui/button";
import { useToast } from "@/components/ui/use-toast";
import { cn } from "@/lib/utils";
import { Cover } from "@/components/ui/cover";
interface SeriesHeaderProps {
series: KomgaSeries;
onSeriesUpdate?: (series: KomgaSeries) => void;
}
export const SeriesHeader = ({ series, onSeriesUpdate }: SeriesHeaderProps) => {
export const SeriesHeader = ({ series }: SeriesHeaderProps) => {
const { toast } = useToast();
const [isFavorite, setIsFavorite] = useState(false);

View File

@@ -9,10 +9,6 @@ import { usePreferences } from "@/contexts/PreferencesContext";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
interface ErrorMessage {
message: string;
}
interface KomgaConfig {
url: string;
username: string;
@@ -256,7 +252,10 @@ export function ClientSettings({ initialConfig, initialTTLConfig }: ClientSettin
toast({
variant: "destructive",
title: "Erreur",
description: "Une erreur est survenue lors de la mise à jour des préférences",
description:
error instanceof Error
? error.message
: "Une erreur est survenue lors de la mise à jour des préférences",
});
}
};

View File

@@ -1,6 +1,5 @@
"use client";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
interface ImageLoaderProps {

View File

@@ -12,7 +12,7 @@ type ToasterToast = ToastProps & {
action?: ToastActionElement;
};
const actionTypes = {
const _actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
@@ -26,7 +26,7 @@ function genId() {
return count.toString();
}
type ActionType = typeof actionTypes;
type ActionType = typeof _actionTypes;
type Action =
| {