feat: enhance image loading in CoverClient component with timeout handling and error management, update PreferencesContext to initialize loading state as false, and refine type casting in PreferencesService

This commit is contained in:
Julien Froidefond
2025-10-17 10:47:36 +02:00
parent 2e183bb5d6
commit 8d6f8f4de7
3 changed files with 43 additions and 13 deletions

View File

@@ -1,8 +1,7 @@
"use client"; "use client";
import { ImageOff } from "lucide-react"; import { ImageOff } from "lucide-react";
import Image from "next/image"; import { useState, useEffect, useRef } from "react";
import { useState } from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ImageLoader } from "@/components/ui/image-loader"; import { ImageLoader } from "@/components/ui/image-loader";
@@ -21,6 +20,38 @@ export const CoverClient = ({
}: CoverClientProps) => { }: CoverClientProps) => {
const [imageError, setImageError] = useState(false); const [imageError, setImageError] = useState(false);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
// Timeout de sécurité : si l'image ne se charge pas en 10 secondes, on arrête le loading
useEffect(() => {
timeoutRef.current = setTimeout(() => {
if (isLoading) {
console.warn("Image loading timeout for:", imageUrl);
setIsLoading(false);
}
}, 10000);
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [imageUrl, isLoading]);
const handleLoad = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setIsLoading(false);
};
const handleError = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
console.error("Image loading error for:", imageUrl);
setImageError(true);
};
if (imageError) { if (imageError) {
return ( return (
@@ -33,19 +64,18 @@ export const CoverClient = ({
return ( return (
<div className="relative w-full h-full"> <div className="relative w-full h-full">
<ImageLoader isLoading={isLoading} /> <ImageLoader isLoading={isLoading} />
<Image {/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={imageUrl} src={imageUrl}
alt={alt} alt={alt}
fill loading="lazy"
className={cn( className={cn(
"object-cover transition-opacity duration-300 rounded-lg", "absolute inset-0 w-full h-full object-cover transition-opacity duration-300 rounded-lg",
isCompleted && "opacity-50", isCompleted && "opacity-50",
className className
)} )}
onError={() => setImageError(true)} onError={handleError}
onLoad={() => setIsLoading(false)} onLoad={handleLoad}
loading="lazy"
unoptimized
/> />
</div> </div>
); );

View File

@@ -24,7 +24,7 @@ export function PreferencesProvider({
const [preferences, setPreferences] = useState<UserPreferences>( const [preferences, setPreferences] = useState<UserPreferences>(
initialPreferences || defaultPreferences initialPreferences || defaultPreferences
); );
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(false);
const fetchPreferences = async () => { const fetchPreferences = async () => {
try { try {

View File

@@ -33,7 +33,7 @@ export class PreferencesService {
showOnlyUnread: preferences.showOnlyUnread, showOnlyUnread: preferences.showOnlyUnread,
debug: preferences.debug, debug: preferences.debug,
displayMode: preferences.displayMode as UserPreferences["displayMode"], displayMode: preferences.displayMode as UserPreferences["displayMode"],
background: (preferences.background as Prisma.JsonValue) as BackgroundPreferences, background: preferences.background as unknown as BackgroundPreferences,
}; };
} catch (error) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {
@@ -65,7 +65,7 @@ export class PreferencesService {
showOnlyUnread: preferences.showOnlyUnread ?? defaultPreferences.showOnlyUnread, showOnlyUnread: preferences.showOnlyUnread ?? defaultPreferences.showOnlyUnread,
debug: preferences.debug ?? defaultPreferences.debug, debug: preferences.debug ?? defaultPreferences.debug,
displayMode: preferences.displayMode ?? defaultPreferences.displayMode, displayMode: preferences.displayMode ?? defaultPreferences.displayMode,
background: (preferences.background ?? defaultPreferences.background) as Prisma.InputJsonValue, background: (preferences.background ?? defaultPreferences.background) as unknown as Prisma.InputJsonValue,
}, },
}); });
@@ -75,7 +75,7 @@ export class PreferencesService {
showOnlyUnread: updatedPreferences.showOnlyUnread, showOnlyUnread: updatedPreferences.showOnlyUnread,
debug: updatedPreferences.debug, debug: updatedPreferences.debug,
displayMode: updatedPreferences.displayMode as UserPreferences["displayMode"], displayMode: updatedPreferences.displayMode as UserPreferences["displayMode"],
background: (updatedPreferences.background as Prisma.JsonValue) as BackgroundPreferences, background: updatedPreferences.background as unknown as BackgroundPreferences,
}; };
} catch (error) { } catch (error) {
if (error instanceof AppError) { if (error instanceof AppError) {