340 lines
13 KiB
TypeScript
340 lines
13 KiB
TypeScript
import { ArrowRight } from 'lucide-react';
|
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
|
import { Button } from './ui/button';
|
|
import { useRef, useState, useEffect } from 'react';
|
|
import Image592Traced from '../imports/Image592Traced-5025-559';
|
|
import { useGetUpcomingCitiesQuery } from '../Redux/services/cities.service';
|
|
import LoadingSpinner from './LoadingSpinner';
|
|
|
|
// const upcomingCities = [
|
|
// {
|
|
// id: 1,
|
|
// name: 'Boston',
|
|
// country: 'USA',
|
|
// launchDate: 'Spring 2025',
|
|
// attractions: 65,
|
|
// description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.',
|
|
// image: 'https://images.unsplash.com/photo-1568271667303-14b2a1a36da1?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: true
|
|
// },
|
|
// {
|
|
// id: 2,
|
|
// name: 'Rome',
|
|
// country: 'Italy',
|
|
// launchDate: 'Summer 2025',
|
|
// attractions: 80,
|
|
// image: 'https://images.unsplash.com/photo-1552832230-c0197dd311b5?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false
|
|
// },
|
|
// {
|
|
// id: 3,
|
|
// name: 'Paris',
|
|
// country: 'France',
|
|
// launchDate: 'Fall 2025',
|
|
// attractions: 95,
|
|
// image: 'https://images.unsplash.com/photo-1502602898536-47ad22581b52?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false
|
|
// },
|
|
// {
|
|
// id: 4,
|
|
// name: 'Dubai',
|
|
// country: 'UAE',
|
|
// launchDate: 'Winter 2025',
|
|
// attractions: 70,
|
|
// image: 'https://images.unsplash.com/photo-1512453979798-5ea266f8880c?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false,
|
|
// badge: 'New'
|
|
// },
|
|
// {
|
|
// id: 5,
|
|
// name: 'Tokyo',
|
|
// country: 'Japan',
|
|
// launchDate: 'Early 2026',
|
|
// attractions: 120,
|
|
// image: 'https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false
|
|
// },
|
|
// {
|
|
// id: 6,
|
|
// name: 'Sydney',
|
|
// country: 'Australia',
|
|
// launchDate: 'Spring 2026',
|
|
// attractions: 85,
|
|
// image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false
|
|
// },
|
|
// {
|
|
// id: 7,
|
|
// name: 'New York',
|
|
// country: 'USA',
|
|
// launchDate: 'Summer 2026',
|
|
// attractions: 150,
|
|
// image: 'https://images.unsplash.com/photo-1496442226666-8d4d0e62e6e9?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false,
|
|
// badge: 'Most Requested'
|
|
// },
|
|
// {
|
|
// id: 8,
|
|
// name: 'Singapore',
|
|
// country: 'Singapore',
|
|
// launchDate: 'Fall 2026',
|
|
// attractions: 75,
|
|
// image: 'https://images.unsplash.com/photo-1525625293386-3f8f99389edd?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false
|
|
// },
|
|
// {
|
|
// id: 9,
|
|
// name: 'Amsterdam',
|
|
// country: 'Netherlands',
|
|
// launchDate: 'Winter 2026',
|
|
// attractions: 90,
|
|
// image: 'https://images.unsplash.com/photo-1534351590666-13e3e96b5017?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false
|
|
// },
|
|
// {
|
|
// id: 10,
|
|
// name: 'Barcelona',
|
|
// country: 'Spain',
|
|
// launchDate: 'Early 2027',
|
|
// attractions: 110,
|
|
// image: 'https://images.unsplash.com/photo-1583422409516-2895a77efded?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
|
// showHoverState: false
|
|
// }
|
|
// ];
|
|
|
|
export function LandingUpcomingCities() {
|
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
const [startX, setStartX] = useState(0);
|
|
const [scrollLeft, setScrollLeft] = useState(0);
|
|
const [showDragHint, setShowDragHint] = useState(false);
|
|
|
|
const listType = "upcomingCity"
|
|
// const[listType,setListType]=useState("upcomingCity")
|
|
|
|
const { data, isLoading } = useGetUpcomingCitiesQuery(listType)
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<LoadingSpinner/>
|
|
);
|
|
}
|
|
|
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
if (!scrollContainerRef.current) return;
|
|
// Only start dragging if not clicking on a button or interactive element
|
|
const target = e.target as HTMLElement;
|
|
if (target.closest('button') || target.closest('[role="button"]')) {
|
|
return;
|
|
}
|
|
setIsDragging(true);
|
|
setStartX(e.pageX - scrollContainerRef.current.offsetLeft);
|
|
setScrollLeft(scrollContainerRef.current.scrollLeft);
|
|
setShowDragHint(false);
|
|
};
|
|
|
|
const handleMouseLeave = () => {
|
|
setIsDragging(false);
|
|
setShowDragHint(false);
|
|
};
|
|
|
|
const handleMouseUp = () => {
|
|
setIsDragging(false);
|
|
};
|
|
|
|
const handleMouseMove = (e: React.MouseEvent) => {
|
|
if (!isDragging || !scrollContainerRef.current) return;
|
|
e.preventDefault();
|
|
const x = e.pageX - scrollContainerRef.current.offsetLeft;
|
|
const walk = (x - startX) * 1.5; // Reduced multiplier for smoother movement
|
|
scrollContainerRef.current.scrollLeft = scrollLeft - walk;
|
|
};
|
|
|
|
const handleMouseEnter = () => {
|
|
if (!isDragging) {
|
|
setShowDragHint(true);
|
|
}
|
|
};
|
|
|
|
// useEffect(() => {
|
|
// const handleGlobalMouseUp = () => setIsDragging(false);
|
|
// document.addEventListener('mouseup', handleGlobalMouseUp);
|
|
// return () => document.removeEventListener('mouseup', handleGlobalMouseUp);
|
|
// }, []);
|
|
|
|
return (
|
|
<section className="py-20 bg-gray-50">
|
|
{/* Header - Contained and aligned */}
|
|
<div className="container mx-auto px-4">
|
|
<div className="mb-16">
|
|
<h2 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
|
|
Upcoming Cities
|
|
</h2>
|
|
<p className="text-lg text-gray-600 max-w-2xl">
|
|
Here are lots of interesting destinations to visit, but don't be confused—they're already grouped by category.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Cities Carousel - Aligned with header, extending to screen edge */}
|
|
<div className="relative">
|
|
{/* Drag Hint Pill */}
|
|
{showDragHint && (
|
|
<div className="absolute top-4 left-1/2 transform -translate-x-1/2 z-20 bg-black/80 text-white px-4 py-2 rounded-full text-sm font-medium backdrop-blur-sm transition-all duration-300 pointer-events-none">
|
|
Drag to scroll
|
|
</div>
|
|
)}
|
|
|
|
<div
|
|
ref={scrollContainerRef}
|
|
className={`flex gap-6 overflow-x-auto scrollbar-hide pb-2 ${isDragging ? 'cursor-grabbing dragging select-none' : 'cursor-grab'}`}
|
|
style={{
|
|
scrollbarWidth: 'none',
|
|
msOverflowStyle: 'none',
|
|
scrollBehavior: isDragging ? 'auto' : 'smooth',
|
|
paddingLeft: 'max(1rem, calc((100vw - 1280px) / 2 + 1rem))',
|
|
paddingRight: '1rem'
|
|
}}
|
|
onMouseDown={handleMouseDown}
|
|
onMouseLeave={handleMouseLeave}
|
|
onMouseUp={handleMouseUp}
|
|
onMouseMove={handleMouseMove}
|
|
onMouseEnter={handleMouseEnter}
|
|
>
|
|
{data && data?.upcomingCities?.map((city: any) => (
|
|
<div
|
|
key={city.id}
|
|
className="flex-shrink-0 w-72 md:w-80 group relative h-[420px] rounded-3xl overflow-hidden shadow-lg hover:shadow-xl transition-all duration-500"
|
|
>
|
|
{/* Background - Either solid color or image */}
|
|
{true ? (
|
|
// Boston card with image background and same layout as other cards
|
|
<>
|
|
<ImageWithFallback
|
|
src={city.imgPathName!}
|
|
alt={city.cityName}
|
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
|
|
/>
|
|
|
|
{/* Dark overlay */}
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent group-hover:from-black/80 transition-all duration-500" />
|
|
|
|
{/* City name overlay - matching Rome card layout */}
|
|
<div className="absolute bottom-6 left-6 right-6 text-white">
|
|
<h3 className="text-2xl font-bold mb-2">{city.cityName}</h3>
|
|
<div className="flex items-center justify-between text-sm text-white/80">
|
|
{/* <span>{city.country}</span>
|
|
<span>{city.launchDate}</span> */}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Hover state overlay - same as other cards */}
|
|
<div className="absolute inset-0 bg-warm-coral/90 opacity-0 group-hover:opacity-100 transition-all duration-500 flex items-center justify-center">
|
|
<div className="text-center text-white">
|
|
<h3 className="text-2xl font-bold mb-2">{city.cityName}</h3>
|
|
{/* <p className="text-white/90 mb-4">{city.attractions}+ attractions</p>
|
|
<p className="text-sm text-white/80 mb-6">Coming {city.launchDate}</p> */}
|
|
<Button
|
|
variant="secondary"
|
|
className="bg-white/20 hover:bg-white/30 text-white border-white/30 hover:border-white/50 backdrop-blur-sm"
|
|
onMouseDown={(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
e.stopPropagation();
|
|
setIsDragging(false);
|
|
}}
|
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
e.stopPropagation();
|
|
console.log('Notify Me button clicked');
|
|
}}
|
|
>
|
|
Notify Me
|
|
<ArrowRight className="w-4 h-4 ml-2" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
// Image background for other cards
|
|
<>
|
|
<ImageWithFallback
|
|
src={city.image!}
|
|
alt={city.name}
|
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
|
|
/>
|
|
|
|
{/* Dark overlay */}
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent group-hover:from-black/80 transition-all duration-500" />
|
|
|
|
{/* Badge (if present) */}
|
|
{/* {city.badge && (
|
|
<div className="absolute top-4 right-4 bg-white text-gray-900 px-3 py-1 rounded-full text-sm font-medium shadow-lg">
|
|
{city.badge}
|
|
</div>
|
|
)} */}
|
|
|
|
{/* City name overlay */}
|
|
{/* <div className="absolute bottom-6 left-6 right-6 text-white">
|
|
<h3 className="text-2xl font-bold mb-2">{city.name}</h3>
|
|
<div className="flex items-center justify-between text-sm text-white/80">
|
|
<span>{city.country}</span>
|
|
<span>{city.launchDate}</span>
|
|
</div>
|
|
</div> */}
|
|
|
|
{/* Hover state overlay */}
|
|
<div className="absolute inset-0 bg-gradient-to-br from-primary/90 to-secondary/90 opacity-0 group-hover:opacity-100 transition-all duration-500 flex items-center justify-center">
|
|
<div className="text-center text-white">
|
|
<h3 className="text-2xl font-bold mb-2">{city.cityName}</h3>
|
|
{/* <p className="text-white/90 mb-4">{city.attractions}+ attractions</p> */}
|
|
{/* <p className="text-sm text-white/80 mb-6">Coming {city.launchDate}</p> */}
|
|
<Button
|
|
variant="secondary"
|
|
className="bg-white/20 hover:bg-white/30 text-white border-white/30 hover:border-white/50 backdrop-blur-sm"
|
|
onMouseDown={(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
e.stopPropagation();
|
|
setIsDragging(false);
|
|
}}
|
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
e.stopPropagation();
|
|
console.log('Notify Me button clicked');
|
|
}}
|
|
>
|
|
Notify Me
|
|
<ArrowRight className="w-4 h-4 ml-2" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bottom CTA - Contained */}
|
|
<div className="container mx-auto px-4">
|
|
|
|
</div>
|
|
|
|
{/* Global Offices Section */}
|
|
<div className="container mx-auto px-4 mt-20">
|
|
{/* Global Presence Section - Exact Screenshot Recreation */}
|
|
|
|
</div>
|
|
|
|
<style dangerouslySetInnerHTML={{
|
|
__html: `
|
|
.scrollbar-hide::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
.scrollbar-hide {
|
|
scroll-behavior: smooth;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
.scrollbar-hide.dragging {
|
|
scroll-behavior: auto;
|
|
}
|
|
`
|
|
}} />
|
|
</section>
|
|
);
|
|
} |