import { useState, useEffect, useMemo, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import Layout from '../components/layout/Layout'; import { eventsAPI } from '../services/api'; import { Calendar, MapPin, Users, Loader2, CheckCircle, QrCode } from 'lucide-react'; const EventsPage = () => { const navigate = useNavigate(); const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); // Pagination controls for revealing older/newer events around the nearest 5 const [pastShownCount, setPastShownCount] = useState(0); const [futureShownCount, setFutureShownCount] = useState(0); useEffect(() => { const fetchEvents = async () => { try { setLoading(true); const data = await eventsAPI.getAll(); setEvents(data); } catch (err) { setError('Failed to load events'); console.error('Error loading events:', err); } finally { setLoading(false); } }; fetchEvents(); }, []); // Build visible list and animation guards BEFORE any conditional returns const threshold = new Date(); threshold.setDate(threshold.getDate() - 3); const { visibleEvents, pastPool, futurePool, canLoadMorePast, canLoadMoreFuture, initialFutureIds } = useMemo(() => { const sorted = [...events].sort((a, b) => new Date(a.startDate) - new Date(b.startDate)); const idxStart = sorted.findIndex((e) => new Date(e.startDate) >= threshold); const startIdx = idxStart === -1 ? sorted.length : idxStart; const pastPool = sorted.slice(0, startIdx); const initialFuture = sorted.slice(startIdx, startIdx + 5); const futurePool = sorted.slice(startIdx + 5); const showPast = pastPool.slice(Math.max(0, pastPool.length - pastShownCount)); const showFuture = futurePool.slice(0, futureShownCount); const visibleEvents = [...showPast, ...initialFuture, ...showFuture]; return { visibleEvents, pastPool, futurePool, canLoadMorePast: pastShownCount < pastPool.length, canLoadMoreFuture: futureShownCount < futurePool.length, initialFutureIds: new Set(initialFuture.map((e) => e.id)), }; }, [events, pastShownCount, futureShownCount]); // Track previously visible IDs to animate only newly added items const prevVisibleIdsRef = useRef(new Set()); const listTopRef = useRef(null); const isNewById = useMemo(() => { const map = new Map(); for (const ev of visibleEvents) { map.set(ev.id, !prevVisibleIdsRef.current.has(ev.id)); } return map; }, [visibleEvents]); useEffect(() => { prevVisibleIdsRef.current = new Set(visibleEvents.map((e) => e.id)); }, [visibleEvents]); // Preserve scroll position when prepending past items const handleLoadPrevious = () => { const containerTop = listTopRef.current?.getBoundingClientRect().top || 0; setPastShownCount((c) => Math.min(c + 10, pastPool.length)); requestAnimationFrame(() => { const newTop = listTopRef.current?.getBoundingClientRect().top || 0; const delta = newTop - containerTop; if (delta !== 0) window.scrollBy({ top: delta, left: 0, behavior: 'instant' }); }); }; const handleLoadLater = () => { setFutureShownCount((c) => Math.min(c + 10, futurePool.length)); }; const handleJoinEvent = (slug) => { navigate(`/events/${slug}/chat`); }; if (loading) { return (

Loading events...

); } if (error) { return (
{error}
); } // Animated card for smoother reveal of newly mounted items (CSS keyframes) const EventCard = ({ event, delay = 0, isNew = false, showCheckin = false }) => { const handleJoinEvent = () => navigate(`/events/${event.slug}/chat`); return (

{event.name}

{event.isJoined && ( Joined )}
{event.location}
{new Date(event.startDate).toLocaleDateString('en-US')} - {new Date(event.endDate).toLocaleDateString('en-US')}
{event.participantsCount} participants
{event.description && (

{event.description}

)}
{event.isJoined ? ( ) : showCheckin ? (
Check-in required

Scan the QR code at the event venue to join the chat

) : null} {/* Development mode: Show details link */} {import.meta.env.DEV && ( )}
); }; return (

Choose an event

Join an event and start connecting with other dancers

{canLoadMorePast && (
)}
{visibleEvents.map((event, idx) => ( ))}
{canLoadMoreFuture && (
)}
); }; export default EventsPage;