"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { Cursor, PortfolioArtworkItem, PortfolioFilters, } from "@/actions/portfolio/getPortfolioArtworksPage"; import { getPortfolioArtworksPage } from "@/actions/portfolio/getPortfolioArtworksPage"; import JustifiedGallery, { type JustifiedGalleryItem, } from "@/components/gallery/JustifiedGallery"; export default function PortfolioGallery({ filters, }: { filters: PortfolioFilters; }) { const { year, albumId, q } = filters; const queryFilters = useMemo( () => ({ year, albumId, q }), [year, albumId, q] ); const resetKey = useMemo( () => `${year ?? ""}|${albumId ?? ""}|${q ?? ""}`, [year, albumId, q] ); const [items, setItems] = useState([]); const [done, setDone] = useState(false); const [loading, setLoading] = useState(false); const [screen, setScreen] = useState<{ width: number; height: number; dpr: number; } | null>(null); const inFlight = useRef(false); const doneRef = useRef(false); doneRef.current = done; const cursorRef = useRef(null); useEffect(() => { if (resetKey == null) return; setItems([]); setDone(false); doneRef.current = false; inFlight.current = false; cursorRef.current = null; }, [resetKey]); const loadMore = useCallback(async () => { if (inFlight.current || doneRef.current) return 0; inFlight.current = true; setLoading(true); try { const data = await getPortfolioArtworksPage({ take: 60, cursor: cursorRef.current, filters: queryFilters, onlyPublished: true, }); // Defensive dedupe setItems((prev) => { const seen = new Set(prev.map((x) => x.id)); const next = data.items.filter((x) => !seen.has(x.id)); return prev.concat(next); }); cursorRef.current = data.nextCursor; if (!data.nextCursor) setDone(true); return data.items.length; } finally { setLoading(false); inFlight.current = false; } }, [queryFilters]); useEffect(() => { void loadMore(); }, [loadMore]); useEffect(() => { const update = () => { setScreen({ width: window.innerWidth, height: window.innerHeight, dpr: window.devicePixelRatio || 1, }); }; update(); window.addEventListener("resize", update); window.addEventListener("orientationchange", update); return () => { window.removeEventListener("resize", update); window.removeEventListener("orientationchange", update); }; }, []); const galleryItems: JustifiedGalleryItem[] = items.map((it) => ({ id: it.id, name: it.name, altText: it.altText, fileKey: it.fileKey, width: it.thumbW, height: it.thumbH, dominantHex: it.dominantHex, })); // useEffect(() => { // if (items.length === 0) return; // // Debug: inspect dominantHex values coming from the server. // console.log( // "[PortfolioGallery] dominantHex sample", // items.slice(0, 5).map((it) => ({ // id: it.id, // dominantHex: it.dominantHex, // })) // ); // }, [items]); if (!loading && done && galleryItems.length === 0) { return (

No artworks to display

); } return (
{screen ? (
Screen {screen.width} × {screen.height} · {screen.dpr}x
) : null} void loadMore()} hasMore={!done} isLoadingMore={loading} />
); }