"use client"; import Image from "next/image"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { CSSProperties } from "react"; import type { SliderPhoto } from "@/lib/slider-photos"; const ROTATE_MS = 5500; function PromoStripSingleSlide({ photos, activeIndex, }: { photos: SliderPhoto[]; activeIndex: number; }) { const [ratioBySrc, setRatioBySrc] = useState>({}); const lastKnownRatioRef = useRef(16 / 9); const active = photos[activeIndex]; const activeRatio = active ? ratioBySrc[active.src] : undefined; const displayRatio = activeRatio != null && activeRatio > 0 ? activeRatio : photos.length > 0 ? lastKnownRatioRef.current : undefined; useEffect(() => { if (activeRatio != null && activeRatio > 0) { lastKnownRatioRef.current = activeRatio; } }, [activeRatio]); const containerStyle = useMemo((): CSSProperties => { if (photos.length === 0) { return { minHeight: "10rem" }; } if (displayRatio != null) { return { aspectRatio: displayRatio }; } return { minHeight: "10rem" }; }, [photos.length, displayRatio]); const handleImageLoad = useCallback((src: string, naturalWidth: number, naturalHeight: number) => { if (naturalHeight <= 0) return; setRatioBySrc((prev) => { const next = naturalWidth / naturalHeight; if (prev[src] === next) return prev; return { ...prev, [src]: next }; }); }, []); const n = photos.length; return (
{n > 0 ? ( photos.map((photo, i) => ( {photo.alt} handleImageLoad(photo.src, e.currentTarget.naturalWidth, e.currentTarget.naturalHeight)} /> )) ) : (
)}
); } function useSliderPhotos(): { photos: SliderPhoto[]; loading: boolean } { const [photos, setPhotos] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { let cancelled = false; fetch("/api/slider-photos", { cache: "no-store" }) .then((r) => (r.ok ? r.json() : [])) .then((data: unknown) => { if (cancelled) return; if (Array.isArray(data)) { setPhotos( data.filter( (x): x is SliderPhoto => typeof x === "object" && x !== null && "src" in x && typeof (x as SliderPhoto).src === "string" && "alt" in x && typeof (x as SliderPhoto).alt === "string", ), ); } else { setPhotos([]); } }) .catch(() => { if (!cancelled) setPhotos([]); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, []); return { photos, loading }; } /** Slider: one image at a time; container height follows each image aspect ratio (no fixed empty band). */ export function TvonePromoStrip() { const { photos, loading } = useSliderPhotos(); const [index, setIndex] = useState(0); const [reduceMotion, setReduceMotion] = useState(false); useEffect(() => { const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); const sync = () => setReduceMotion(mq.matches); sync(); mq.addEventListener("change", sync); return () => mq.removeEventListener("change", sync); }, []); const advance = useCallback(() => { if (photos.length <= 1) return; setIndex((i) => (i + 1) % photos.length); }, [photos.length]); useEffect(() => { if (photos.length <= 1 || reduceMotion) return; const id = window.setInterval(advance, ROTATE_MS); return () => window.clearInterval(id); }, [photos.length, reduceMotion, advance]); useEffect(() => { setIndex((prev) => (photos.length === 0 ? 0 : Math.min(prev, photos.length - 1))); }, [photos.length]); const current = photos.length > 0 ? photos[index] : null; const regionLabel = loading ? "A carregar galeria" : current?.alt ?? "Galeria"; return (
{current ? current.alt : loading ? "A carregar" : "Sem imagens"}
); }