198 lines
8.6 KiB
TypeScript
198 lines
8.6 KiB
TypeScript
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
import type { Event } from '../../types';
|
|
|
|
interface EventsHeroSliderProps {
|
|
events: Event[];
|
|
}
|
|
|
|
const EventsHeroSlider: React.FC<EventsHeroSliderProps> = ({ events }) => {
|
|
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]);
|
|
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
const [timeLeft, setTimeLeft] = useState({ d: '00', h: '00', m: '00', s: '00' });
|
|
|
|
const nextSlide = useCallback(() => {
|
|
setCurrentIndex((prev) => (prev + 1) % sortedEvents.length);
|
|
}, [sortedEvents.length]);
|
|
|
|
const prevSlide = useCallback(() => {
|
|
setCurrentIndex((prev) => (prev - 1 + sortedEvents.length) % sortedEvents.length);
|
|
}, [sortedEvents.length]);
|
|
|
|
useEffect(() => {
|
|
if (sortedEvents.length === 0) return;
|
|
const interval = setInterval(nextSlide, 5000);
|
|
return () => clearInterval(interval);
|
|
}, [sortedEvents.length, nextSlide]);
|
|
|
|
const updateTimer = useCallback(() => {
|
|
const activeEvent = sortedEvents[currentIndex];
|
|
if (!activeEvent) {
|
|
setTimeLeft({ d: '00', h: '00', m: '00', s: '00' });
|
|
return;
|
|
}
|
|
|
|
const targetDate = new Date(activeEvent.date).getTime();
|
|
const now = new Date().getTime();
|
|
const distance = targetDate - now;
|
|
|
|
if (isNaN(targetDate) || distance < 0) {
|
|
setTimeLeft({ d: '00', h: '00', m: '00', s: '00' });
|
|
return;
|
|
}
|
|
|
|
setTimeLeft(prev => {
|
|
const newD = Math.floor(distance / (1000 * 60 * 60 * 24)).toString().padStart(2, '0');
|
|
const newH = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)).toString().padStart(2, '0');
|
|
const newM = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)).toString().padStart(2, '0');
|
|
const newS = Math.floor((distance % (1000 * 60)) / 1000).toString().padStart(2, '0');
|
|
|
|
if (prev.d === newD && prev.h === newH && prev.m === newM && prev.s === newS) {
|
|
return prev;
|
|
}
|
|
return { d: newD, h: newH, m: newM, s: newS };
|
|
});
|
|
}, [sortedEvents, currentIndex]);
|
|
|
|
useEffect(() => {
|
|
updateTimer();
|
|
const timerId = setInterval(updateTimer, 1000);
|
|
return () => clearInterval(timerId);
|
|
}, [updateTimer]);
|
|
|
|
if (sortedEvents.length === 0) return null;
|
|
|
|
const activeEvent = sortedEvents[currentIndex];
|
|
|
|
return (
|
|
<div className="relative w-full h-screen bg-[#1A202C] overflow-hidden group">
|
|
{/* Background Images & Content */}
|
|
{sortedEvents.map((event, idx) => {
|
|
const isActive = idx === currentIndex;
|
|
return (
|
|
<div
|
|
key={event.id}
|
|
className={`absolute inset-0 transition-opacity duration-1000 ease-in-out ${isActive ? 'opacity-100 z-10' : 'opacity-0 z-0'}`}
|
|
>
|
|
{/* Background Image */}
|
|
<div className="absolute inset-0 bg-black">
|
|
<img
|
|
src={event.image}
|
|
alt={event.title}
|
|
className={`w-full h-full object-cover opacity-50 transition-transform duration-[5000ms] ${isActive ? 'scale-100' : 'scale-105'}`}
|
|
/>
|
|
</div>
|
|
|
|
{/* Top Right Location Button */}
|
|
<div className="absolute top-24 right-12 z-20">
|
|
<div className="bg-white px-6 py-3 rounded-full flex items-center gap-3 shadow-2xl shadow-gray-200/50">
|
|
<i className="fas fa-location-dot text-[#f97316]"></i>
|
|
<span className="text-[#1A202C] text-xs font-bold tracking-widest uppercase">{event.location}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<div className="absolute inset-0 z-10 flex flex-col justify-center pb-[15vh] px-12 md:px-24">
|
|
<div className={`max-w-5xl transition-all duration-1000 transform ${isActive ? 'translate-y-0 opacity-100' : 'translate-y-8 opacity-0'}`}>
|
|
<div className="flex items-center gap-4 mb-6">
|
|
<div className="w-12 h-[2px] bg-[#f97316]"></div>
|
|
<span className="text-white font-bold tracking-[0.3em] uppercase text-sm">
|
|
JOIN US <span className="text-[#f97316] ml-2">{event.date}</span>
|
|
</span>
|
|
</div>
|
|
|
|
<h1 className="text-white text-5xl md:text-[6rem] lg:text-[8rem] font-black leading-[0.9] tracking-tighter uppercase break-words drop-shadow-lg">
|
|
{event.title}
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{/* Countdown Timer Block (Slanted Orange Design) */}
|
|
<div
|
|
className="absolute bottom-0 left-0 z-20 bg-[#f97316] h-[140px] md:h-[160px] flex items-center px-8 md:px-16"
|
|
style={{
|
|
clipPath: 'polygon(0 0, 85% 0, 100% 100%, 0% 100%)',
|
|
width: '100%',
|
|
maxWidth: '800px'
|
|
}}
|
|
>
|
|
<div className="flex items-center gap-6 md:gap-12 relative z-10 w-full pr-12 md:pr-24">
|
|
<div className="flex flex-col items-center flex-1">
|
|
<span className="text-5xl md:text-6xl font-black leading-none text-white tracking-tighter">{timeLeft.d}</span>
|
|
<span className="text-[10px] md:text-xs font-bold uppercase tracking-widest mt-2 text-white">Days</span>
|
|
</div>
|
|
<div className="w-px h-16 bg-white/30"></div>
|
|
<div className="flex flex-col items-center flex-1">
|
|
<span className="text-5xl md:text-6xl font-black leading-none text-white tracking-tighter">{timeLeft.h}</span>
|
|
<span className="text-[10px] md:text-xs font-bold uppercase tracking-widest mt-2 text-white">Hrs</span>
|
|
</div>
|
|
<div className="w-px h-16 bg-white/30"></div>
|
|
<div className="flex flex-col items-center flex-1">
|
|
<span className="text-5xl md:text-6xl font-black leading-none text-white tracking-tighter">{timeLeft.m}</span>
|
|
<span className="text-[10px] md:text-xs font-bold uppercase tracking-widest mt-2 text-white">Min</span>
|
|
</div>
|
|
<div className="w-px h-16 bg-white/30"></div>
|
|
<div className="flex flex-col items-center flex-1">
|
|
<span className="text-5xl md:text-6xl font-black leading-none text-white tracking-tighter">{timeLeft.s}</span>
|
|
<span className="text-[10px] md:text-xs font-bold uppercase tracking-widest mt-2 text-white">Sec</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Navigation Arrows */}
|
|
<button
|
|
onClick={prevSlide}
|
|
className="absolute left-8 top-1/2 -translate-y-1/2 w-12 h-12 rounded-full bg-white/10 backdrop-blur-md flex items-center justify-center text-white/50 hover:text-white hover:bg-white/20 transition-all duration-300 z-20 opacity-0 group-hover:opacity-100 hover:scale-110 border border-white/10"
|
|
>
|
|
<i className="fas fa-chevron-left text-lg"></i>
|
|
</button>
|
|
<button
|
|
onClick={nextSlide}
|
|
className="absolute right-8 top-1/2 -translate-y-1/2 w-12 h-12 rounded-full bg-white/10 backdrop-blur-md flex items-center justify-center text-white/50 hover:text-white hover:bg-white/20 transition-all duration-300 z-20 opacity-0 group-hover:opacity-100 hover:scale-110 border border-white/10"
|
|
>
|
|
<i className="fas fa-chevron-right text-lg"></i>
|
|
</button>
|
|
|
|
{/* Slider Dots */}
|
|
<div className="absolute bottom-12 right-12 z-20 flex gap-3">
|
|
{sortedEvents.map((_, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => setCurrentIndex(idx)}
|
|
className={`h-1 transition-all duration-500 ${currentIndex === idx ? 'w-12 bg-[#f97316]' : 'w-6 bg-gray-200 hover:bg-gray-400'}`}
|
|
></button>
|
|
))}
|
|
</div>
|
|
{/* Scroll Down Indicator */}
|
|
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 md:left-[60%] lg:left-1/2 z-30 flex flex-col items-center gap-1.5 text-white/70 hover:text-white transition-colors duration-300 pointer-events-none">
|
|
<span className="text-[10px] md:text-xs font-black uppercase tracking-[0.25em] whitespace-nowrap drop-shadow-md animate-pulse">
|
|
Scroll down to explore the events
|
|
</span>
|
|
<svg
|
|
className="w-5 h-5 animate-bounce text-[#f97316] drop-shadow-md"
|
|
fill="none"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="3"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default EventsHeroSlider;
|
|
|