import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { Page } from './_util'; import { useParams, useSearchParams } from 'react-router-dom'; import { usePollGalleryDelta } from '../polling/usePollGalleryDelta'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import FiltersBar, { type GalleryFilter } from '../components/FiltersBar'; import { Heart, Users, Image as ImageIcon, Camera, Package as PackageIcon } from 'lucide-react'; import { likePhoto } from '../services/photosApi'; import PhotoLightbox from './PhotoLightbox'; import { fetchEvent, getEventPackage, fetchStats, type EventData, type EventPackage, type EventStats } from '../services/eventApi'; export default function GalleryPage() { const { slug } = useParams(); const { photos, loading, newCount, acknowledgeNew } = usePollGalleryDelta(slug!); const [filter, setFilter] = React.useState('latest'); const [currentPhotoIndex, setCurrentPhotoIndex] = React.useState(null); const [hasOpenedPhoto, setHasOpenedPhoto] = useState(false); const [event, setEvent] = useState(null); const [eventPackage, setEventPackage] = useState(null); const [stats, setStats] = useState(null); const [eventLoading, setEventLoading] = useState(true); const [searchParams] = useSearchParams(); const photoIdParam = searchParams.get('photoId'); // Auto-open lightbox if photoId in query params useEffect(() => { if (photoIdParam && photos.length > 0 && currentPhotoIndex === null && !hasOpenedPhoto) { const index = photos.findIndex((photo: any) => photo.id === parseInt(photoIdParam, 10)); if (index !== -1) { setCurrentPhotoIndex(index); setHasOpenedPhoto(true); } } }, [photos, photoIdParam, currentPhotoIndex, hasOpenedPhoto]); // Load event and package info useEffect(() => { if (!slug) return; const loadEventData = async () => { try { setEventLoading(true); const [eventData, packageData, statsData] = await Promise.all([ fetchEvent(slug), getEventPackage(slug), fetchStats(slug), ]); setEvent(eventData); setEventPackage(packageData); setStats(statsData); } catch (err) { console.error('Failed to load event data', err); } finally { setEventLoading(false); } }; loadEventData(); }, [slug]); const myPhotoIds = React.useMemo(() => { try { const raw = localStorage.getItem('my-photo-ids'); return new Set(raw ? JSON.parse(raw) : []); } catch { return new Set(); } }, []); const list = React.useMemo(() => { let arr = photos.slice(); if (filter === 'popular') { arr.sort((a: any, b: any) => (b.likes_count ?? 0) - (a.likes_count ?? 0)); } else if (filter === 'mine') { arr = arr.filter((p: any) => myPhotoIds.has(p.id)); } else { arr.sort((a: any, b: any) => new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime()); } return arr; }, [photos, filter, myPhotoIds]); const [liked, setLiked] = React.useState>(new Set()); const [counts, setCounts] = React.useState>({}); async function onLike(id: number) { if (liked.has(id)) return; setLiked(new Set(liked).add(id)); try { const c = await likePhoto(id); setCounts((m) => ({ ...m, [id]: c })); // keep a simple record of liked items try { const raw = localStorage.getItem('liked-photo-ids'); const arr: number[] = raw ? JSON.parse(raw) : []; if (!arr.includes(id)) localStorage.setItem('liked-photo-ids', JSON.stringify([...arr, id])); } catch {} } catch { const s = new Set(liked); s.delete(id); setLiked(s); } } if (eventLoading) { return

Lade Event-Info...

; } return ( Galerie: {event?.name || 'Event'}

Online Gäste

{stats?.onlineGuests || 0}

Gesamt Likes

{photos.reduce((sum, p) => sum + ((p as any).likes_count || 0), 0)}

Gesamt Fotos

{photos.length}

{eventPackage && (

Package

{eventPackage.package.name}

{eventPackage.used_photos} / {eventPackage.package.max_photos} Fotos

{new Date(eventPackage.expires_at) < new Date() && (

Abgelaufen: {new Date(eventPackage.expires_at).toLocaleDateString()}

)}
)}
{newCount > 0 && ( {newCount} neue Fotos verfügbar.{' '} )} {loading &&

Lade…

}
{list.map((p: any) => { // Debug: Log image URLs const imgSrc = p.thumbnail_path || p.file_path; // Normalize image URL let imageUrl = imgSrc; let cleanPath = ''; if (imageUrl) { // Remove leading/trailing slashes for processing cleanPath = imageUrl.replace(/^\/+|\/+$/g, ''); // Check if path already contains storage prefix if (cleanPath.startsWith('storage/')) { // Already has storage prefix, just ensure it starts with / imageUrl = `/${cleanPath}`; } else { // Add storage prefix imageUrl = `/storage/${cleanPath}`; } // Remove double slashes imageUrl = imageUrl.replace(/\/+/g, '/'); } // Production: avoid heavy console logging for each image return (
{ const index = list.findIndex(photo => photo.id === p.id); setCurrentPhotoIndex(index >= 0 ? index : null); }} className="cursor-pointer" > {`Foto { (e.target as HTMLImageElement).src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjRjNGNEY2Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPk5vIEltYWdlPC90ZXh0Pjwvc3ZnPg=='; }} loading="lazy" />
{p.task_title && (

{p.task_title}

)}
{counts[p.id] ?? (p.likes_count || 0)}
); })}
{currentPhotoIndex !== null && list.length > 0 && ( setCurrentPhotoIndex(null)} onIndexChange={(index: number) => setCurrentPhotoIndex(index)} slug={slug} /> )}
); }