Files
CityCards-Website/src/components/LandingUpcomingCities.tsx

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>
);
}