Files
nutriscan/components/OptimizedImage.tsx
T
Evandro Fonseca Junior bfb58ef16e feat(search): implement paginated API fetching for large page sizes
- Removed upper limit on page_size validation to support requests >100
- Added automatic pagination logic to fetch from Open Food Facts API in chunks of 1000
- Accumulates products across multiple API pages until desired count is reached
- Updated error messages and product data fields for consistency
2025-10-09 07:16:26 -03:00

161 lines
4.0 KiB
TypeScript

"use client";
import { useState, useRef, useEffect } from "react";
import Image from "next/image";
import { cn } from "@/lib/utils";
export interface OptimizedImageProps {
src: string;
alt: string;
priority?: number; // 0 = mais prioridade (topo da página)
onPageChange?: number; // Cancelar se página mudar
className?: string;
fill?: boolean;
width?: number;
height?: number;
sizes?: string;
placeholder?: "blur" | "empty";
blurDataURL?: string;
quality?: number;
loading?: "lazy" | "eager";
onLoad?: () => void;
onError?: (error: Error) => void;
}
/**
* Componente de imagem otimizada com carregamento inteligente
* - Carrega apenas quando visível no viewport
* - Cancela carregamento ao trocar de página
* - Suporte a priorização de carregamento
* - Placeholder durante carregamento
*/
export function OptimizedImage({
src,
alt,
priority = 999,
onPageChange,
className,
fill = false,
width,
height,
sizes,
placeholder = "empty",
blurDataURL,
quality,
loading = "lazy",
onLoad,
onError,
...props
}: OptimizedImageProps) {
const [shouldLoad, setShouldLoad] = useState(false);
const [hasError, setHasError] = useState(false);
// Usar priority para debug/logging se necessário
console.debug(`OptimizedImage: ${src} (priority: ${priority})`);
const imgRef = useRef<HTMLDivElement>(null);
const observerRef = useRef<IntersectionObserver | null>(null);
const currentPageRef = useRef(onPageChange);
// Atualizar referência da página atual
useEffect(() => {
currentPageRef.current = onPageChange;
}, [onPageChange]);
// Cancelar carregamento se página mudar
useEffect(() => {
if (onPageChange !== undefined && onPageChange !== currentPageRef.current) {
setShouldLoad(false);
setHasError(false);
}
}, [onPageChange]);
// Configurar Intersection Observer
useEffect(() => {
if (typeof window === "undefined" || !imgRef.current) return;
observerRef.current = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !shouldLoad) {
setShouldLoad(true);
}
});
},
{
rootMargin: "50px", // Pré-carregar 50px antes de ficar visível
threshold: 0.1,
}
);
observerRef.current.observe(imgRef.current);
return () => {
observerRef.current?.disconnect();
};
}, [shouldLoad]);
// Placeholder durante carregamento
const renderPlaceholder = () => (
<div
className={cn(
"bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-700 dark:to-gray-800",
"animate-pulse flex items-center justify-center",
className
)}
>
<div className="text-gray-400 dark:text-gray-500 text-sm">
Carregando...
</div>
</div>
);
// Imagem com erro
const renderError = () => (
<div
className={cn(
"bg-gray-100 dark:bg-gray-800 flex items-center justify-center",
"text-gray-400 dark:text-gray-500",
className
)}
>
<div className="text-center">
<div className="text-2xl mb-2">📷</div>
<div className="text-xs">Erro ao carregar</div>
</div>
</div>
);
return (
<div ref={imgRef} className="relative w-full h-full">
{hasError ? (
renderError()
) : shouldLoad ? (
<Image
src={src}
alt={alt}
fill={fill}
width={width}
height={height}
sizes={sizes}
placeholder={placeholder}
blurDataURL={blurDataURL}
quality={quality}
loading={loading}
className={cn("transition-opacity duration-300", className)}
onLoad={() => {
onLoad?.();
}}
onError={() => {
const errorObj = new Error("Falha ao carregar imagem");
setHasError(true);
onError?.(errorObj);
}}
{...props}
/>
) : (
renderPlaceholder()
)}
</div>
);
}