'use client'; import { useState, useEffect, useCallback, useRef } from 'react'; import { Card, MediaItem } from '@/types'; const isVideoUrl = (url: string) => /\.(mp4|webm|mov|m4v|ogv)(\?|$)/i.test(url); function MediaCarousel({ items, onMediaClick, }: { items: MediaItem[]; onMediaClick?: (index: number) => void; }) { const [current, setCurrent] = useState(0); const touchStartX = useRef(null); const videoRefs = useRef>({}); const prev = useCallback(() => setCurrent(i => (i - 1 + items.length) % items.length), [items.length]); const next = useCallback(() => setCurrent(i => (i + 1) % items.length), [items.length]); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === 'ArrowLeft') prev(); if (e.key === 'ArrowRight') next(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [prev, next]); useEffect(() => { setCurrent(0); }, [items]); // Pause non-current videos; autoplay current if flagged useEffect(() => { Object.entries(videoRefs.current).forEach(([key, vid]) => { if (!vid) return; const idx = parseInt(key, 10); if (idx !== current) { vid.pause(); return; } const item = items[idx]; if (item && isVideoUrl(item.url) && item.autoplay) { vid.muted = true; vid.play().catch(() => {}); } }); }, [current, items]); const onTouchStart = (e: React.TouchEvent) => { touchStartX.current = e.touches[0].clientX; }; const onTouchEnd = (e: React.TouchEvent) => { if (touchStartX.current === null) return; const delta = e.changedTouches[0].clientX - touchStartX.current; if (Math.abs(delta) > 50) delta < 0 ? next() : prev(); touchStartX.current = null; }; if (items.length === 0) { return
No Image
; } return (
{items.map((item, i) => { const isActive = i === current; const video = isVideoUrl(item.url); return (
{video ? ( <>
); })} {items.length > 1 && ( <>
{items.map((_, i) => (
{current + 1} / {items.length}
)}
); } function FullscreenViewer({ items, startIndex, onClose, }: { items: MediaItem[]; startIndex: number; onClose: () => void; }) { const [current, setCurrent] = useState(startIndex); const touchStartX = useRef(null); const prev = useCallback(() => setCurrent(i => (i - 1 + items.length) % items.length), [items.length]); const next = useCallback(() => setCurrent(i => (i + 1) % items.length), [items.length]); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); else if (e.key === 'ArrowLeft') prev(); else if (e.key === 'ArrowRight') next(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [prev, next, onClose]); const onTouchStart = (e: React.TouchEvent) => { touchStartX.current = e.touches[0].clientX; }; const onTouchEnd = (e: React.TouchEvent) => { if (touchStartX.current === null) return; const delta = e.changedTouches[0].clientX - touchStartX.current; if (Math.abs(delta) > 50) delta < 0 ? next() : prev(); touchStartX.current = null; }; return (
{/* Media — full resolution, contained */} {items.map((item, i) => { const isActive = i === current; const video = isVideoUrl(item.url); return (
{video ? (
); })} {/* Counter — top center */} {items.length > 1 && (
{current + 1} / {items.length}
)} {/* Side arrows */} {items.length > 1 && ( <> )} {/* Close button — bottom center, ABOVE the dots */} {/* Dots — at the very bottom */} {items.length > 1 && (
{items.map((_, i) => (
)}
); } export default function PublicGrid({ cards, maxCols = 5 }: { cards: Card[], maxCols?: number }) { const [activeCard, setActiveCard] = useState(null); const [fullscreenIndex, setFullscreenIndex] = useState(null); useEffect(() => { if (activeCard || fullscreenIndex !== null) document.body.style.overflow = 'hidden'; else document.body.style.overflow = 'unset'; return () => { document.body.style.overflow = 'unset'; }; }, [activeCard, fullscreenIndex]); const gridClasses: Record = { 3: 'xl:grid-cols-3 lg:grid-cols-3 md:grid-cols-2 grid-cols-1', 4: 'xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2 grid-cols-1', 5: 'xl:grid-cols-5 lg:grid-cols-4 md:grid-cols-3 grid-cols-1', 6: 'xl:grid-cols-6 lg:grid-cols-4 md:grid-cols-3 grid-cols-2', 7: 'xl:grid-cols-7 lg:grid-cols-5 md:grid-cols-4 grid-cols-2', 8: 'xl:grid-cols-8 lg:grid-cols-6 md:grid-cols-4 grid-cols-2', }; const activeGridClass = gridClasses[maxCols] || gridClasses[5]; const carouselItems: MediaItem[] = activeCard ? [ ...(activeCard.imageUrl ? [{ url: activeCard.imageUrl }] : []), ...(activeCard.extraMedia || []), ] : []; return ( <>
{cards.map((card) => { const galleryCount = (card.extraMedia?.length || 0) + (card.imageUrl ? 1 : 0); return (
setActiveCard(card)} className="group relative cursor-pointer overflow-hidden rounded-xl shadow-md aspect-square bg-gray-200 transition-all duration-300 hover:shadow-xl hover:-translate-y-1" > {card.imageUrl ? ( {card.title} ) : (
No Image
)} {galleryCount > 1 && (
{galleryCount}
)}

{card.title}

{card.shortDescription}

); })}
{activeCard && (
setActiveCard(null)} >
e.stopPropagation()} >
setFullscreenIndex(i)} />
{activeCard.cardType.replace('_', ' ')}

{activeCard.title}

{activeCard.fullContent ? (
) : (

{activeCard.shortDescription}

)}
)} {fullscreenIndex !== null && activeCard && ( setFullscreenIndex(null)} /> )} ); }