163 lines
7.8 KiB
TypeScript
163 lines
7.8 KiB
TypeScript
|
|
import React, { useMemo, useState } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import { Event } from '../types';
|
|
import { CLUBS } from '../constants';
|
|
|
|
const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
return createPortal(children, document.body);
|
|
};
|
|
|
|
interface UpcomingEventsSliderProps {
|
|
events: Event[];
|
|
}
|
|
|
|
const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({ events }) => {
|
|
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
|
const clubInfo = useMemo(() => selectedEvent ? CLUBS.find(c => c.name === selectedEvent.club) : null, [selectedEvent]);
|
|
|
|
const sortedEvents = useMemo(() => {
|
|
return [...events]
|
|
.filter(e => e.status !== 'Completed')
|
|
.sort((a, b) => {
|
|
return new Date(a.date).getTime() - new Date(b.date).getTime();
|
|
});
|
|
}, [events]);
|
|
|
|
// Duplicate events for seamless loop only if we have enough to fill the track
|
|
const loopEvents = sortedEvents.length > 2 ? [...sortedEvents, ...sortedEvents] : sortedEvents;
|
|
|
|
if (sortedEvents.length === 0) return null;
|
|
|
|
return (
|
|
<div className="py-24 px-6 md:px-12 lg:px-24 bg-white overflow-hidden">
|
|
<style>
|
|
{`
|
|
@keyframes merryGoRound {
|
|
0% { transform: translateX(0); }
|
|
100% { transform: translateX(-50%); }
|
|
}
|
|
.animate-merry-go-round {
|
|
animation: merryGoRound 30s linear infinite;
|
|
display: flex;
|
|
width: max-content;
|
|
}
|
|
.animate-merry-go-round:hover {
|
|
animation-play-state: paused;
|
|
}
|
|
`}
|
|
</style>
|
|
<div className="flex flex-col lg:flex-row items-start lg:items-end justify-between mb-16 gap-8">
|
|
<div>
|
|
<h3 className="text-4xl md:text-5xl font-serif text-[#1A202C] tracking-tight">Upcoming Experiences</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative overflow-hidden w-full">
|
|
{/* Gradient masks removed to eliminate fogginess */}
|
|
|
|
<div className="animate-merry-go-round gap-6 md:gap-8">
|
|
{loopEvents.map((event, idx) => (
|
|
<div
|
|
key={`${event.id}-${idx}`}
|
|
className="w-[280px] md:w-[350px] lg:w-[400px] flex-shrink-0 bg-white rounded-[2.5rem] p-5 group transition-all shadow-xl shadow-gray-900/5 hover:-translate-y-1 border border-gray-200"
|
|
>
|
|
<div className="h-48 md:h-56 w-full rounded-[2rem] overflow-hidden mb-6 md:mb-8 relative">
|
|
<img
|
|
src={event.image}
|
|
alt={event.title}
|
|
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
|
|
/>
|
|
<div className="absolute top-4 right-4 bg-white px-4 py-2 rounded-full border border-gray-200">
|
|
<span className="text-[9px] font-black text-[#1A202C] uppercase tracking-widest">{event.category}</span>
|
|
</div>
|
|
</div>
|
|
<div className="px-2 pb-2 md:pb-4">
|
|
<h4 className="text-xl md:text-2xl font-serif text-[#1A202C] mb-4 md:mb-6 group-hover:text-[#f97316] transition-colors line-clamp-1">
|
|
{event.title}
|
|
</h4>
|
|
<div className="flex flex-col gap-3 text-gray-500 text-[9px] md:text-[10px] font-bold uppercase tracking-[0.2em]">
|
|
<div className="flex items-center gap-3">
|
|
<i className="far fa-calendar-alt text-[#f97316]"></i>
|
|
<span className="flex items-center gap-2">
|
|
{event.date}
|
|
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
|
<span className="text-[#f97316]">{event.schedule?.[0]?.start_time || ''}</span>
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<i className="fas fa-location-dot text-[#f97316]"></i>
|
|
<span className="truncate">{event.location}</span>
|
|
</div>
|
|
</div>
|
|
<div className="mt-6 md:mt-8 pt-4 md:pt-6 border-t border-gray-200/30 flex justify-between items-center">
|
|
<span className="text-[8px] md:text-[9px] text-gray-400 font-black uppercase tracking-widest">Limited Access</span>
|
|
<button
|
|
onClick={() => setSelectedEvent(event)}
|
|
className="text-[#1A202C] text-[9px] md:text-[10px] font-black uppercase tracking-[0.3em] hover:text-[#f97316] transition-colors flex items-center gap-2"
|
|
>
|
|
Details <i className="fas fa-arrow-right text-[8px]"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{selectedEvent && (
|
|
<Portal>
|
|
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-4 sm:p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300">
|
|
<div className="bg-white rounded-[2rem] w-full max-w-lg shadow-2xl animate-in zoom-in-95 duration-500 max-h-[85vh] flex flex-col relative">
|
|
<button
|
|
onClick={() => setSelectedEvent(null)}
|
|
className="absolute top-6 right-6 w-8 h-8 rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-all flex items-center justify-center z-10"
|
|
>
|
|
<i className="fas fa-times text-sm"></i>
|
|
</button>
|
|
|
|
<div className="p-8 overflow-y-auto flex-1 custom-scrollbar">
|
|
<div className="flex items-center gap-4 mb-8 pb-6 border-b border-gray-100 pr-8">
|
|
{clubInfo && (
|
|
<img src={clubInfo.image} className="w-12 h-12 rounded-full object-cover border border-slate-200 shadow-sm" alt="" />
|
|
)}
|
|
<div>
|
|
<span className="block text-[10px] font-black text-[#f97316] uppercase tracking-[0.2em] mb-1">{selectedEvent.club || 'Organized by'}</span>
|
|
<span className="text-[10px] font-bold text-gray-500 uppercase tracking-widest flex items-center gap-2">
|
|
<i className="fas fa-user text-[9px]"></i> {selectedEvent.coordinator}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-6 pb-6 mt-6 border-t border-gray-100 pt-6">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div className="w-1 h-3 bg-[#f97316] rounded-full"></div>
|
|
<h4 className="text-[10px] font-black text-slate-800 uppercase tracking-[0.2em]">Summary</h4>
|
|
</div>
|
|
<div className="relative pt-2">
|
|
<i className="fas fa-quote-right absolute top-0 right-0 text-5xl text-slate-50 pointer-events-none -z-10"></i>
|
|
<p className="text-[14px] text-slate-600 leading-relaxed whitespace-pre-wrap font-medium">
|
|
{selectedEvent.event_summary || "Details for this session will be provided soon."}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 shrink-0 border-t border-gray-100 pt-6">
|
|
<button
|
|
onClick={() => setSelectedEvent(null)}
|
|
className="w-full py-4 bg-slate-900 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-black transition-all shadow-xl shadow-slate-200 focus:outline-none focus:ring-4 focus:ring-slate-100"
|
|
>
|
|
Close Summary
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Portal>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default UpcomingEventsSlider;
|