666 lines
25 KiB
TypeScript
666 lines
25 KiB
TypeScript
import { useState, useEffect, useRef, forwardRef } from 'react';
|
|
import { Menu, X, ShoppingBag, ChevronDown, Globe, User, Settings, LogOut } from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'motion/react';
|
|
import Frame1597884853 from '../imports/Frame1597884853';
|
|
import { Button } from './ui/button';
|
|
import { ImageWithFallback } from './figma/ImageWithFallback';
|
|
import { CTAButton } from './CTAButton';
|
|
import logoImage from '../assets/cit-logo.png';
|
|
|
|
interface NavbarProps {
|
|
activeCity: string;
|
|
onCityChange: (city: string) => void;
|
|
onSignInClick: () => void;
|
|
onSignOutClick?: () => void;
|
|
onPassesClick: () => void;
|
|
onCheckoutClick?: () => void;
|
|
onHomeClick?: () => void;
|
|
onAttractionsClick?: () => void;
|
|
onBlogsClick?: () => void;
|
|
onHowItWorksClick?: () => void;
|
|
onFAQClick?: () => void;
|
|
onPrivacyPolicyClick?: () => void;
|
|
onAboutUsClick?: () => void;
|
|
onProfileClick?: () => void;
|
|
onCityCardsClick?: () => void;
|
|
onMagicItineraryClick?: () => void;
|
|
onPostCardsClick?: () => void;
|
|
onOffersClick?: () => void;
|
|
onEsimsClick?: () => void;
|
|
onHotelDiscountsClick?: () => void;
|
|
currentPage?: 'home' | 'signin' | 'passes' | 'attractions' | 'checkout' | 'blogs' | 'how-it-works' | 'faq' | 'privacy-policy' | 'about-us' | 'melbourne' | 'profile' | 'citycards' | 'magic-itinerary' | 'postcards' | 'offers' | 'esims' | 'hotel-discounts';
|
|
isUserSignedIn?: boolean;
|
|
user?: { email: string; name: string } | null;
|
|
}
|
|
|
|
interface DropdownItem {
|
|
id: string;
|
|
label: string;
|
|
icon?: React.ReactNode;
|
|
action?: () => void;
|
|
badge?: string | number;
|
|
}
|
|
|
|
interface CartItem {
|
|
id: string;
|
|
name: string;
|
|
price: string;
|
|
image?: string;
|
|
quantity: number;
|
|
}
|
|
|
|
interface DropdownProps {
|
|
isOpen: boolean;
|
|
onToggle: () => void;
|
|
items: DropdownItem[];
|
|
trigger: React.ReactNode;
|
|
title?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export default function Navbar({
|
|
activeCity,
|
|
onCityChange,
|
|
onSignInClick,
|
|
onSignOutClick,
|
|
onPassesClick,
|
|
onCheckoutClick,
|
|
onHomeClick,
|
|
onAttractionsClick,
|
|
onBlogsClick,
|
|
onHowItWorksClick,
|
|
onFAQClick,
|
|
onPrivacyPolicyClick,
|
|
onAboutUsClick,
|
|
onProfileClick,
|
|
onCityCardsClick,
|
|
onMagicItineraryClick,
|
|
onPostCardsClick,
|
|
onOffersClick,
|
|
onEsimsClick,
|
|
onHotelDiscountsClick,
|
|
currentPage = 'home',
|
|
isUserSignedIn = false,
|
|
user
|
|
}: NavbarProps) {
|
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
const [activeLanguageDropdown, setActiveLanguageDropdown] = useState(false);
|
|
const [activeCartDropdown, setActiveCartDropdown] = useState(false);
|
|
const [activeUserDropdown, setActiveUserDropdown] = useState(false);
|
|
const [activeProductsDropdown, setActiveProductsDropdown] = useState(false);
|
|
|
|
const languageRef = useRef<HTMLDivElement>(null);
|
|
const cartRef = useRef<HTMLDivElement>(null);
|
|
const userRef = useRef<HTMLDivElement>(null);
|
|
const productsRef = useRef<HTMLDivElement>(null);
|
|
|
|
// Languages available
|
|
const languages: DropdownItem[] = [
|
|
{ id: 'en', label: 'English', icon: <span className="text-base">🇺🇸</span> },
|
|
{ id: 'es', label: 'Español', icon: <span className="text-base">🇪🇸</span> },
|
|
{ id: 'fr', label: 'Français', icon: <span className="text-base">🇫🇷</span> },
|
|
{ id: 'it', label: 'Italiano', icon: <span className="text-base">🇮🇹</span> },
|
|
];
|
|
|
|
// Products dropdown items
|
|
const productsItems: DropdownItem[] = [
|
|
{
|
|
id: 'citycards',
|
|
label: 'CityCards',
|
|
action: onCityCardsClick
|
|
},
|
|
{
|
|
id: 'magic-itinerary',
|
|
label: 'Magic Itinerary',
|
|
action: onMagicItineraryClick
|
|
},
|
|
{
|
|
id: 'postcards',
|
|
label: 'Post Cards',
|
|
action: onPostCardsClick
|
|
},
|
|
{
|
|
id: 'offers',
|
|
label: 'Offers',
|
|
action: onOffersClick
|
|
},
|
|
{
|
|
id: 'esims',
|
|
label: 'eSIMs',
|
|
action: onEsimsClick
|
|
}
|
|
];
|
|
|
|
// Mock cart items
|
|
const cartItems: CartItem[] = [
|
|
{ id: '1', name: 'Sydney 2-Day Pass', price: '$89', quantity: 1 },
|
|
{ id: '2', name: 'Melbourne Premium Pass', price: '$129', quantity: 1 },
|
|
];
|
|
|
|
// Section IDs for navigation
|
|
const sectionIds = [
|
|
'hero-section',
|
|
'why-choose-section',
|
|
'variety-adventures-section',
|
|
'how-it-works-section',
|
|
'magic-itinerary-section',
|
|
'book-attraction-section',
|
|
'custom-postcards-section',
|
|
'upcoming-cities-section',
|
|
'trust-section',
|
|
'mobile-app-section'
|
|
];
|
|
|
|
const scrollToSection = (index: number) => {
|
|
const sectionId = sectionIds[index];
|
|
const element = document.getElementById(sectionId);
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
setIsMobileMenuOpen(false);
|
|
};
|
|
|
|
const closeMobileMenu = () => {
|
|
setIsMobileMenuOpen(false);
|
|
};
|
|
|
|
// Detect scroll for navbar styling
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
const scrolled = window.scrollY > 20;
|
|
setIsScrolled(scrolled);
|
|
};
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
// Close dropdowns when clicking outside
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
// Add a small delay to prevent immediate closure when toggle is clicked
|
|
setTimeout(() => {
|
|
if (languageRef.current && !languageRef.current.contains(event.target as Node)) {
|
|
setActiveLanguageDropdown(false);
|
|
}
|
|
if (cartRef.current && !cartRef.current.contains(event.target as Node)) {
|
|
setActiveCartDropdown(false);
|
|
}
|
|
if (userRef.current && !userRef.current.contains(event.target as Node)) {
|
|
setActiveUserDropdown(false);
|
|
}
|
|
if (productsRef.current && !productsRef.current.contains(event.target as Node)) {
|
|
setActiveProductsDropdown(false);
|
|
}
|
|
}, 10);
|
|
};
|
|
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
const handleNavClick = (action: string) => {
|
|
switch (action) {
|
|
case 'about':
|
|
onAboutUsClick?.();
|
|
break;
|
|
case 'attractions':
|
|
onAttractionsClick?.();
|
|
break;
|
|
case 'card':
|
|
onPassesClick?.();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
closeMobileMenu();
|
|
};
|
|
|
|
const isNavItemActive = (action: string) => {
|
|
if (action === 'about') {
|
|
return currentPage === 'about-us';
|
|
}
|
|
return currentPage === action;
|
|
};
|
|
|
|
// Calculate cart total
|
|
const cartTotal = cartItems.reduce((total, item) => {
|
|
const price = parseFloat(item.price.replace('$', ''));
|
|
return total + (price * item.quantity);
|
|
}, 0);
|
|
|
|
// Dropdown component with proper ref forwarding and glassmorphism
|
|
const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(({
|
|
isOpen,
|
|
onToggle,
|
|
items,
|
|
trigger,
|
|
title,
|
|
className = ""
|
|
}, ref) => (
|
|
<div ref={ref} className={`relative ${className}`} style={{ height: 'auto', minHeight: 'auto' }}>
|
|
<motion.button
|
|
onClick={onToggle}
|
|
className="relative"
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
{trigger}
|
|
</motion.button>
|
|
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
|
transition={{ duration: 0.2, ease: [0.25, 0.1, 0.25, 1] }}
|
|
className="absolute top-full right-0 mt-2 bg-white/95 backdrop-blur-xl rounded-2xl shadow-xl border border-white/20 min-w-[280px] max-w-[320px] overflow-hidden z-50"
|
|
style={{
|
|
position: 'absolute',
|
|
top: '100%',
|
|
right: '0',
|
|
marginTop: '0.5rem'
|
|
}}
|
|
>
|
|
{title && (
|
|
<div className="px-5 py-4 border-b border-gray-100/50">
|
|
<h3 className="font-merchant font-semibold text-gray-900 text-base">{title}</h3>
|
|
</div>
|
|
)}
|
|
|
|
<div className="py-3 px-2">
|
|
{items.map((item, index) => (
|
|
<motion.button
|
|
key={item.id}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
// Handle action first
|
|
if (item.action && item.id !== 'total') {
|
|
item.action();
|
|
}
|
|
|
|
// Only close dropdown for actionable items (checkout and regular cart items)
|
|
if (item.id === 'checkout' || (item.id !== 'total' && !item.action)) {
|
|
setTimeout(() => onToggle(), 100);
|
|
}
|
|
}}
|
|
className={`w-full flex items-center justify-between text-left transition-colors duration-200 ${
|
|
item.id === 'checkout'
|
|
? 'bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-medium rounded-lg px-4 py-2.5 mb-1 mt-2'
|
|
: item.id === 'total'
|
|
? 'cursor-default font-semibold border-t border-gray-100/50 pt-4 px-3 py-2'
|
|
: 'hover:bg-gray-50/80 px-3 py-2.5 rounded-md'
|
|
}`}
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ delay: index * 0.05 }}
|
|
disabled={item.id === 'total'}
|
|
>
|
|
<div className="flex items-center space-x-3">
|
|
{item.icon}
|
|
<span className={`text-sm font-medium ${
|
|
item.id === 'checkout' ? 'text-white' :
|
|
item.id === 'total' ? 'text-gray-900' : 'text-gray-700'
|
|
}`}>{item.label}</span>
|
|
</div>
|
|
{item.badge && (
|
|
<span className="bg-primary text-primary-foreground text-xs px-2 py-1 rounded-full">
|
|
{item.badge}
|
|
</span>
|
|
)}
|
|
</motion.button>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
));
|
|
|
|
// Set display name for debugging
|
|
Dropdown.displayName = 'Dropdown';
|
|
|
|
return (
|
|
<>
|
|
{/* Desktop Navbar - Enhanced Glassmorphism */}
|
|
<motion.nav
|
|
className="fixed top-6 left-0 right-0 z-50 hidden lg:block"
|
|
initial={{ y: -100, opacity: 0 }}
|
|
animate={{ y: 0, opacity: 1 }}
|
|
transition={{ duration: 0.6, ease: [0.25, 0.1, 0.25, 1], delay: 0.2 }}
|
|
>
|
|
<div className="container mx-auto px-4">
|
|
<motion.div
|
|
className={`w-full transition-all duration-500 ease-out rounded-full px-8 py-4 bg-white backdrop-blur-[20px] border border-white/20 ${
|
|
isScrolled
|
|
? 'shadow-[0_10px_15px_-3px_rgba(0,0,0,0.08),0_4px_6px_-2px_rgba(0,0,0,0.05)]'
|
|
: 'shadow-lg shadow-black/5'
|
|
}`}
|
|
initial={{ scale: 0.95, opacity: 0, y: 0 }}
|
|
animate={{
|
|
scale: isScrolled ? 0.98 : 1,
|
|
opacity: 1,
|
|
y: isScrolled ? 2 : 0
|
|
}}
|
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
{/* Logo */}
|
|
<motion.div
|
|
className="flex items-center cursor-pointer flex-shrink-0"
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
onClick={() => onHomeClick?.()}
|
|
>
|
|
<ImageWithFallback
|
|
src={logoImage}
|
|
alt="CityCards Logo"
|
|
className="h-10 w-auto"
|
|
/>
|
|
</motion.div>
|
|
|
|
{/* Navigation Links - Centered */}
|
|
<div className="absolute left-1/2 -translate-x-1/2 flex items-center gap-[51px]">
|
|
{[
|
|
{ label: 'About Us', action: 'about' },
|
|
{ label: 'Your Card', action: 'card' }
|
|
].map((item) => (
|
|
<motion.button
|
|
key={item.action}
|
|
onClick={() => handleNavClick(item.action)}
|
|
className={`relative px-0 py-2 text-base font-medium transition-all duration-200 whitespace-nowrap group capitalize ${
|
|
isNavItemActive(item.action)
|
|
? 'text-primary'
|
|
: 'text-gray-700 hover:text-gray-900'
|
|
}`}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
{item.label}
|
|
|
|
{/* Active indicator */}
|
|
<motion.div
|
|
className="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-primary to-secondary rounded-full"
|
|
initial={{ width: 0 }}
|
|
animate={{
|
|
width: isNavItemActive(item.action) ? "100%" : 0,
|
|
opacity: isNavItemActive(item.action) ? 1 : 0
|
|
}}
|
|
whileHover={{
|
|
width: "100%",
|
|
opacity: 1
|
|
}}
|
|
transition={{ duration: 0.2 }}
|
|
/>
|
|
|
|
{/* Hover background */}
|
|
<motion.div
|
|
className="absolute inset-0 bg-gray-100/50 backdrop-blur-sm rounded-lg -z-10"
|
|
initial={{ scale: 0, opacity: 0 }}
|
|
whileHover={{ scale: 1, opacity: 0.5 }}
|
|
transition={{ duration: 0.2 }}
|
|
/>
|
|
</motion.button>
|
|
))}
|
|
|
|
{/* Our Products Dropdown */}
|
|
<button
|
|
onClick={onCityCardsClick}
|
|
className="flex items-center text-gray-700 hover:text-gray-900 text-base font-medium transition-colors duration-200 cursor-pointer rounded-lg hover:bg-gray-50/50 px-2 py-1"
|
|
>
|
|
<span>Our Products</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Right Section */}
|
|
<div className="flex items-center gap-5">
|
|
{/* Language Dropdown */}
|
|
<Dropdown
|
|
ref={languageRef}
|
|
isOpen={activeLanguageDropdown}
|
|
onToggle={() => setActiveLanguageDropdown(!activeLanguageDropdown)}
|
|
items={languages}
|
|
title="Select Language"
|
|
trigger={
|
|
<div className="flex items-center space-x-1.5 text-gray-700 hover:text-gray-900 text-base font-medium transition-colors duration-200 cursor-pointer rounded-lg hover:bg-gray-50/50 uppercase px-2 py-1">
|
|
<Globe className="w-4 h-4" />
|
|
<span>ENG</span>
|
|
<ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${activeLanguageDropdown ? 'rotate-180' : ''}`} />
|
|
</div>
|
|
}
|
|
/>
|
|
|
|
{/* Shopping Cart */}
|
|
<Dropdown
|
|
ref={cartRef}
|
|
isOpen={activeCartDropdown}
|
|
onToggle={() => setActiveCartDropdown(prev => !prev)}
|
|
items={[
|
|
...cartItems.map(item => ({
|
|
id: item.id,
|
|
label: `${item.name} - ${item.price}`,
|
|
badge: `${item.quantity}x`
|
|
})),
|
|
{
|
|
id: 'total',
|
|
label: `Total: ${cartTotal.toFixed(2)}`,
|
|
icon: <ShoppingBag className="w-4 h-4" />
|
|
},
|
|
{
|
|
id: 'checkout',
|
|
label: 'Proceed to Checkout',
|
|
action: onCheckoutClick
|
|
}
|
|
]}
|
|
title="Shopping Cart"
|
|
trigger={
|
|
<div className="relative text-gray-700 hover:text-gray-900 transition-colors duration-200 rounded-lg hover:bg-gray-50/50 cursor-pointer p-2">
|
|
<ShoppingBag className="w-6 h-6" />
|
|
<motion.div
|
|
className="absolute -top-1 -right-1 w-5 h-5 bg-primary rounded-full flex items-center justify-center"
|
|
animate={{ scale: [1, 1.1, 1] }}
|
|
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
|
|
>
|
|
<span className="text-xs text-primary-foreground font-bold">{cartItems.length}</span>
|
|
</motion.div>
|
|
</div>
|
|
}
|
|
/>
|
|
|
|
{/* City Card Button */}
|
|
<div className="flex items-center gap-3">
|
|
<div className="relative">
|
|
<CTAButton
|
|
user={user}
|
|
onClick={user ? () => setActiveUserDropdown(prev => !prev) : (onSignInClick || (() => {}))}
|
|
className="hover:scale-105 transition-transform duration-200"
|
|
/>
|
|
|
|
{/* User Profile Dropdown attached to CTA Button */}
|
|
{isUserSignedIn && user && (
|
|
<Dropdown
|
|
ref={userRef}
|
|
isOpen={activeUserDropdown}
|
|
onToggle={() => setActiveUserDropdown(prev => !prev)}
|
|
items={[
|
|
{
|
|
id: 'profile',
|
|
label: 'My Profile',
|
|
icon: <User className="w-4 h-4" />,
|
|
action: onProfileClick
|
|
},
|
|
{
|
|
id: 'settings',
|
|
label: 'Settings',
|
|
icon: <Settings className="w-4 h-4" />
|
|
},
|
|
{
|
|
id: 'logout',
|
|
label: 'Sign Out',
|
|
icon: <LogOut className="w-4 h-4" />,
|
|
action: onSignOutClick
|
|
}
|
|
]}
|
|
title="Account"
|
|
trigger={null}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</motion.nav>
|
|
|
|
{/* Mobile Navbar - Enhanced Glassmorphism */}
|
|
<nav className="fixed top-0 w-full z-50 lg:hidden">
|
|
<div className={`transition-all duration-500 ease-out border-b shadow-sm ${
|
|
isScrolled
|
|
? 'bg-white/85 backdrop-blur-2xl border-white/40 shadow-black/5'
|
|
: 'bg-white/70 backdrop-blur-3xl border-white/50 shadow-black/10'
|
|
}`}>
|
|
<div className="container mx-auto px-4">
|
|
<div className="flex justify-between items-center h-16">
|
|
{/* Mobile Logo */}
|
|
<motion.div
|
|
className="flex items-center cursor-pointer"
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
onClick={() => onHomeClick?.()}
|
|
>
|
|
<ImageWithFallback
|
|
src={logoImage}
|
|
alt="CityCards Logo"
|
|
className="h-10 w-auto"
|
|
/>
|
|
</motion.div>
|
|
|
|
{/* Mobile Actions */}
|
|
<div className="flex items-center space-x-2">
|
|
{/* Mobile Cart */}
|
|
<motion.button
|
|
className="relative text-gray-700 hover:text-gray-900 p-2 transition-colors duration-200 rounded-lg hover:bg-gray-50/50"
|
|
whileHover={{ scale: 1.1 }}
|
|
whileTap={{ scale: 0.9 }}
|
|
onClick={() => onCheckoutClick?.()}
|
|
>
|
|
<ShoppingBag className="w-6 h-6" />
|
|
<div className="absolute -top-1 -right-1 w-5 h-5 bg-primary rounded-full flex items-center justify-center">
|
|
<span className="text-xs text-primary-foreground font-bold">{cartItems.length}</span>
|
|
</div>
|
|
</motion.button>
|
|
|
|
{/* Mobile menu button */}
|
|
<motion.button
|
|
onClick={() => setIsMobileMenuOpen(true)}
|
|
className="inline-flex items-center justify-center p-2 rounded-lg text-gray-700 hover:text-gray-900 hover:bg-gray-100/50 transition-colors duration-200"
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
>
|
|
<Menu className="h-6 w-6" />
|
|
</motion.button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Mobile Menu Overlay - Enhanced Glassmorphism */}
|
|
<AnimatePresence>
|
|
{isMobileMenuOpen && (
|
|
<>
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
className="fixed inset-0 bg-black/70 backdrop-blur-lg z-50 lg:hidden"
|
|
onClick={closeMobileMenu}
|
|
/>
|
|
<motion.div
|
|
initial={{ x: '100%' }}
|
|
animate={{ x: 0 }}
|
|
exit={{ x: '100%' }}
|
|
transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
|
|
className="fixed top-0 right-0 h-full w-80 bg-white/95 backdrop-blur-2xl shadow-2xl z-50 lg:hidden overflow-y-auto"
|
|
>
|
|
<div className="flex items-center justify-between p-6 border-b border-gray-200/50">
|
|
<h2 className="text-xl font-semibold text-gray-900">Menu</h2>
|
|
<motion.button
|
|
onClick={closeMobileMenu}
|
|
className="p-2 rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-100/50 transition-colors duration-200"
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
>
|
|
<X className="h-6 w-6" />
|
|
</motion.button>
|
|
</div>
|
|
|
|
<div className="p-6 space-y-6">
|
|
{/* Mobile Navigation Links */}
|
|
<div className="space-y-4">
|
|
{[
|
|
{ label: 'About Us', action: 'about' },
|
|
{ label: 'Cities', action: 'cities' },
|
|
{ label: 'Attractions', action: 'attractions' },
|
|
{ label: 'Your Card', action: 'card' },
|
|
{ label: 'Deals', action: 'offer' }
|
|
].map((item) => (
|
|
<motion.button
|
|
key={item.action}
|
|
onClick={() => handleNavClick(item.action)}
|
|
className={`w-full flex items-center justify-between py-3 px-4 rounded-lg text-left transition-colors duration-200 ${
|
|
isNavItemActive(item.action)
|
|
? 'bg-primary/10 text-primary font-medium'
|
|
: 'text-gray-700 hover:bg-gray-100/70 hover:text-gray-900'
|
|
}`}
|
|
whileHover={{ x: 4 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<span className="text-base">{item.label}</span>
|
|
</motion.button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Mobile Our Products Section */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-semibold text-gray-900 px-4">Our Products</h3>
|
|
{productsItems.map((item) => (
|
|
<motion.button
|
|
key={item.id}
|
|
onClick={() => {
|
|
item.action?.();
|
|
closeMobileMenu();
|
|
}}
|
|
className="w-full flex items-center justify-between py-3 px-4 rounded-lg text-left transition-colors duration-200 text-gray-700 hover:bg-gray-100/70 hover:text-gray-900"
|
|
whileHover={{ x: 4 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<span className="text-base">{item.label}</span>
|
|
</motion.button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Mobile CTA Button */}
|
|
<Button
|
|
onClick={() => {
|
|
onSignInClick();
|
|
closeMobileMenu();
|
|
}}
|
|
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-medium py-3"
|
|
>
|
|
GET A CITY CARD
|
|
</Button>
|
|
</div>
|
|
</motion.div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
} |