get a city card flow fix

This commit is contained in:
priyanshuvish
2025-11-05 18:57:24 +05:30
parent a55e473e3c
commit fec000e0b0
8 changed files with 249 additions and 165 deletions

View File

@@ -24,22 +24,24 @@ function App() {
const [offersSource, setOffersSource] = useState<'products' | 'passes'>('products');
const [stickyCardType, setStickyCardType] = useState<'unlimited' | 'selective'>('unlimited');
// Login state management
// ✅ Authentication state management
const [user, setUser] = useState<User | null>(null);
const [showLoginModal, setShowLoginModal] = useState(false);
const location = useLocation();
const navigate = useNavigate();
// Login handlers
// Login handlers
const handleLoginSuccess = (userData: User) => {
setUser(userData);
setShowLoginModal(false);
console.log('User logged in successfully:', userData);
};
const handleSignOut = () => {
setUser(null);
navigate('/');
console.log('User signed out');
};
const handleCloseLoginModal = () => {
@@ -50,6 +52,13 @@ function App() {
setShowLoginModal(true);
};
// ✅ Handle checkout (you can expand this later)
const handleCheckoutClick = () => {
console.log('Proceeding to checkout for user:', user);
// Add your checkout logic here
navigate('/checkout');
};
// Detect mobile for optimized animations
useEffect(() => {
const checkMobile = () => {
@@ -108,13 +117,14 @@ function App() {
animate={{ opacity: 1 }}
transition={{ duration: 0.3, ease: easeOutCubic }}
>
<AppRouter
<AppRouter
user={user}
showLoginModal={showLoginModal}
onSignInClick={handleSignInClick}
onSignOutClick={handleSignOut}
onLoginSuccess={handleLoginSuccess}
onCloseLoginModal={handleCloseLoginModal}
onCheckoutClick={handleCheckoutClick} // ✅ Pass checkout handler
offersSource={offersSource}
/>
</motion.div>

View File

@@ -45,6 +45,7 @@ interface AppRouterProps {
onSignOutClick: () => void;
onLoginSuccess: (userData: User) => void;
onCloseLoginModal: () => void;
onCheckoutClick?: () => void;
offersSource: 'products' | 'passes';
}
@@ -55,6 +56,7 @@ export function AppRouter({
onSignOutClick,
onLoginSuccess,
onCloseLoginModal,
onCheckoutClick,
offersSource
}: AppRouterProps) {
const location = useLocation();
@@ -88,9 +90,13 @@ export function AppRouter({
} />
{/* Passes Route */}
<Route path="/passes" element={
<Route path="/passes" element={
<motion.div key="passes" {...pageTransition}>
<PassesPage {...commonNavHandlers} />
<PassesPage
{...commonNavHandlers}
onCheckoutClick={onCheckoutClick} // ✅ Explicitly pass checkout handler
onLoginSuccess={onLoginSuccess} // ✅ Pass login success handler
/>
</motion.div>
} />

View File

@@ -18,32 +18,33 @@ import { ImageWithFallback } from './figma/ImageWithFallback';
import { Layout } from '../Layout';
interface CheckoutPageProps {
onBackClick: () => void;
onHomeClick: () => void;
onMelbourneClick: () => void;
onPassesClick: () => void;
onCheckoutClick: () => void;
onSignInClick: () => void;
onBackClick?: () => void;
onHomeClick?: () => void;
onMelbourneClick?: () => void;
onPassesClick?: () => void;
onCheckoutClick?: () => void;
onSignInClick?: () => void;
onSignOutClick?: () => void;
onAttractionsClick: () => void;
onBlogsClick: () => void;
onHowItWorksClick: () => void;
onFAQClick: () => void;
onPrivacyPolicyClick: () => void;
onAboutUsClick: () => void;
onProfileClick: () => void;
onCityCardsClick: () => void;
onMagicItineraryClick: () => void;
onPostCardsClick: () => void;
onOffersClick: () => void;
onAttractionsClick?: () => void;
onBlogsClick?: () => void;
onHowItWorksClick?: () => void;
onFAQClick?: () => void;
onPrivacyPolicyClick?: () => void;
onAboutUsClick?: () => void;
onProfileClick?: () => void;
onCityCardsClick?: () => void;
onMagicItineraryClick?: () => void;
onPostCardsClick?: () => void;
onOffersClick?: () => void;
onSecureCheckoutClick?: () => void;
onContactUsClick?: () => void;
onEsimsClick?: () => void;
onHotelDiscountsClick?: () => void;
currentPage: string;
currentPage?: string;
user?: { email: string; name: string } | null;
}
// Mock cart data
const mockCartItems = [
{

View File

@@ -1,5 +1,6 @@
// CitySelectionDialog.tsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; // ✅ import navigation hook
import { useNavigate } from 'react-router-dom';
import { Dialog, DialogContent, DialogTitle, DialogDescription } from './ui/dialog';
import { ArrowLeft, Search } from 'lucide-react';
import { Input } from './ui/input';
@@ -15,6 +16,7 @@ interface City {
interface CitySelectionDialogProps {
isOpen: boolean;
onClose: () => void;
onCitySelect?: (city: string) => void; // ✅ New prop for city selection callback
}
const cities: City[] = [
@@ -28,9 +30,13 @@ const cities: City[] = [
{ id: 'louisiana', name: 'Louisiana', imageUrl: 'https://images.unsplash.com/photo-1646508262200-455d62c22182?...' },
];
export function CitySelectionDialog({ isOpen, onClose }: CitySelectionDialogProps) {
export function CitySelectionDialog({
isOpen,
onClose,
onCitySelect
}: CitySelectionDialogProps) {
const [searchQuery, setSearchQuery] = useState('');
const navigate = useNavigate(); // ✅ navigation hook
const navigate = useNavigate();
const filteredCities = cities.filter(city =>
city.name.toLowerCase().includes(searchQuery.toLowerCase())
@@ -38,16 +44,22 @@ export function CitySelectionDialog({ isOpen, onClose }: CitySelectionDialogProp
const handleCityClick = (city: City) => {
console.log('Selected city:', city.name);
onClose();
// ✅ navigate to /passes with selected city info (optional query param)
navigate(`/passes?city=${encodeURIComponent(city.name)}`);
// ✅ Call the onCitySelect callback if provided
if (onCitySelect) {
onCitySelect(city.id);
} else {
// ✅ Default behavior: navigate to passes page
navigate(`/passes?city=${encodeURIComponent(city.name)}`);
}
onClose();
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-md w-full p-0 gap-0 font-poppins">
{/* Accessible Title */}
{/* ... rest of the component remains the same ... */}
<DialogTitle className="sr-only">Select a City</DialogTitle>
<DialogDescription className="sr-only">
Choose from our available cities to explore attractions and experiences
@@ -86,7 +98,7 @@ export function CitySelectionDialog({ isOpen, onClose }: CitySelectionDialogProp
{filteredCities.map((city, index) => (
<motion.button
key={city.id}
onClick={() => handleCityClick(city)} // ✅ navigate on click
onClick={() => handleCityClick(city)}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
@@ -111,7 +123,6 @@ export function CitySelectionDialog({ isOpen, onClose }: CitySelectionDialogProp
</div>
</AnimatePresence>
{/* No Results */}
{filteredCities.length === 0 && (
<div className="text-center py-8">
<p className="text-gray-500 font-poppins">

View File

@@ -242,18 +242,18 @@ function HeroBannerCarousel({ onCheckoutClick, onPassesClick, onEsimsClick, onHo
}
interface MelbournePageProps {
onBackClick: () => void;
onHomeClick: () => void;
onAttractionsClick: () => void;
onPassesClick: () => void;
onBackClick?: () => void;
onHomeClick?: () => void;
onAttractionsClick?: () => void;
onPassesClick?: () => void;
onCheckoutClick?: () => void;
onSignInClick: () => void;
onSignInClick?: () => void;
onSignOutClick?: () => void;
onBlogsClick: () => void;
onHowItWorksClick: () => void;
onFAQClick: () => void;
onPrivacyPolicyClick: () => void;
onAboutUsClick: () => void;
onBlogsClick?: () => void;
onHowItWorksClick?: () => void;
onFAQClick?: () => void;
onPrivacyPolicyClick?: () => void;
onAboutUsClick?: () => void;
onProfileClick?: () => void;
onCityCardsClick?: () => void;
onMagicItineraryClick?: () => void;
@@ -266,6 +266,7 @@ interface MelbournePageProps {
user?: User | null;
}
export function MelbournePage({
onBackClick,
onHomeClick,

View File

@@ -69,7 +69,7 @@ export default function Navbar({
const location = useLocation();
const navigate = useNavigate();
const handleOpenCityDialog = () => {
const handleOpenCityDialog = () => {
setIsCityDialogOpen(true);
};
@@ -77,6 +77,13 @@ export default function Navbar({
setIsCityDialogOpen(false);
};
// ✅ Handle city selection from dialog
const handleCitySelect = (cityId: string) => {
console.log('City selected in navbar:', cityId);
// Navigate to passes page when city is selected
navigate('/passes');
};
// Available cities
const cities = [
{ id: 'melbourne', label: 'Melbourne' },
@@ -85,6 +92,7 @@ export default function Navbar({
{ id: 'perth', label: 'Perth' }
];
// Check if we're on landing page
const isLandingPage = location.pathname === '/';
@@ -341,7 +349,6 @@ export default function Navbar({
}
className="h-10 w-auto"
/>
</Link>
</motion.div>
@@ -359,7 +366,6 @@ export default function Navbar({
}`}
>
{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"
@@ -374,7 +380,6 @@ export default function Navbar({
}}
transition={{ duration: 0.2 }}
/>
{/* Hover background */}
<motion.div
className="absolute inset-0 bg-gray-100/50 backdrop-blur-sm rounded-lg -z-10"
@@ -384,12 +389,10 @@ export default function Navbar({
/>
</Link>
))}
</div>
{/* Right Section */}
<div className="flex items-center gap-1">
{/* City Dropdown */}
<Dropdown
ref={cityRef}
@@ -421,6 +424,7 @@ export default function Navbar({
</div>
}
/>
{/* Language Dropdown */}
<Dropdown
ref={languageRef}
@@ -437,12 +441,12 @@ export default function Navbar({
}
/>
{/* Shopping Cart - UPDATED: using cartDropdownItems */}
{/* Shopping Cart */}
<Dropdown
ref={cartRef}
isOpen={activeCartDropdown}
onToggle={() => setActiveCartDropdown(prev => !prev)}
items={cartDropdownItems} // Using the updated array with navigation
items={cartDropdownItems}
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">
@@ -457,64 +461,86 @@ export default function Navbar({
</div>
}
/>
{/* City Card Button */}
{/* ✅ UPDATED: City Card Button with Proper Authentication Logic */}
<div className="flex items-center gap-3 pl-2">
<div className="relative">
<CTAButton
user={user ?? null}
// 👇 open the city selection dialog on click
onClick={handleOpenCityDialog}
className="hover:scale-105 transition-transform duration-200"
/>
<div className="relative">
{isUserSignedIn && user ? (
// ✅ When user is logged in - show user dropdown
<Dropdown
ref={userRef}
isOpen={activeUserDropdown}
onToggle={() => setActiveUserDropdown(prev => !prev)}
items={[
{
id: 'profile',
label: 'My Profile',
icon: <User className="w-4 h-4" />,
action: () => {
navigate('/profile');
setActiveUserDropdown(false);
}
},
{
id: 'settings',
label: 'Settings',
icon: <Settings className="w-4 h-4" />,
action: () => {
navigate('/comming-soon');
setActiveUserDropdown(false);
}
},
{
id: 'logout',
label: 'Sign Out',
icon: <LogOut className="w-4 h-4" />,
action: () => {
if (onSignOutClick) {
onSignOutClick();
}
setActiveUserDropdown(false);
}
}
]}
title="Account"
trigger={
<div
className="cursor-pointer"
onClick={(e) => {
e.stopPropagation();
setActiveUserDropdown(prev => !prev);
}}
>
<CTAButton
user={user}
onClick={() => { }} // Empty function since we handle click above
className="hover:scale-105 transition-transform duration-200"
/>
</div>
}
/>
) : (
// ✅ When user is NOT logged in - show city selection dialog
<>
<div
className="cursor-pointer"
onClick={handleOpenCityDialog}
>
<CTAButton
user={null}
onClick={() => { }} // Empty function since we handle click above
className="hover:scale-105 transition-transform duration-200"
/>
</div>
{/* ✅ City Selection Dialog */}
<CitySelectionDialog
isOpen={isCityDialogOpen}
onClose={handleCloseCityDialog}
/>
{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: () => {
navigate('/profile');
setActiveUserDropdown(false);
},
},
{
id: 'settings',
label: 'Settings',
icon: <Settings className="w-4 h-4" />,
action: () => {
navigate('/settings');
setActiveUserDropdown(false);
},
},
{
id: 'logout',
label: 'Sign Out',
icon: <LogOut className="w-4 h-4" />,
action: () => {
if (onSignOutClick) {
onSignOutClick();
}
setActiveUserDropdown(false);
},
},
]}
title="Account"
trigger={null}
/>
)}
</div>
{/* ✅ City Selection Dialog with navigation to passes */}
<CitySelectionDialog
isOpen={isCityDialogOpen}
onClose={handleCloseCityDialog}
onCitySelect={handleCitySelect}
/>
</>
)}
</div>
</div>
</div>
</div>
@@ -522,6 +548,7 @@ export default function Navbar({
</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

View File

@@ -5,17 +5,18 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/
import { RadioGroup, RadioGroupItem } from './ui/radio-group';
import { Badge } from './ui/badge';
import Navbar from './Navbar';
import { CitySubmenu } from './CitySubmenu';
import { EnhancedTestimonials } from './EnhancedTestimonials';
import { Footer } from './Footer';
import { ReviewsSection } from './ReviewsSection';
import { Layout } from '../Layout';
import { LoginModal } from './LoginModal';
interface PassesPageProps {
onCheckoutClick?: () => void;
onSignInClick: () => void;
onSignOutClick?: () => void;
user?: { email: string; name: string; } | null;
onLoginSuccess?: (userData: { email: string; name: string }) => void;
}
interface PassType {
@@ -45,7 +46,7 @@ const passTypes: PassType[] = [
{
id: 'selective',
name: 'Selective Pass',
title: 'SELECTIVE PASS',
title: 'Flexi Card',
description: 'Perfect for travelers who want to explore selected attractions at their own pace with essential features.',
price: '$59.99',
originalPrice: '$89.99',
@@ -147,9 +148,35 @@ export function PassesPage({
onCheckoutClick,
onSignInClick,
onSignOutClick,
user
user,
onLoginSuccess
}: PassesPageProps) {
const [selectedPass, setSelectedPass] = useState<string>('unlimited');
const [isLoginOpen, setIsLoginOpen] = useState(false);
const [userData, setUserData] = useState<{ email: string; name: string } | null>(user || null);
// ✅ Handle purchase button click
const handlePurchaseClick = () => {
if (!userData) {
// User not logged in - show login modal
setIsLoginOpen(true);
} else {
// User is logged in - proceed to checkout
onCheckoutClick?.();
}
};
// ✅ Handle successful login
const handleLoginSuccess = (data: { email: string; name: string }) => {
setUserData(data);
setIsLoginOpen(false);
console.log('Logged in user:', data);
// Call parent's onLoginSuccess if provided
if (onLoginSuccess) {
onLoginSuccess(data);
}
};
const renderFeatureValue = (value: boolean | string) => {
if (typeof value === 'boolean') {
@@ -164,18 +191,15 @@ export function PassesPage({
return (
<Layout
activeCity="Melbourne"
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
user={user}
showCitySubmenu={true}
>
activeCity="Melbourne"
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
user={userData} // ✅ Pass the updated user data
>
<div className="container mx-auto px-4 pt-52 pb-12 relative z-10">
{/* Page Header */}
<div className="text-center mb-16">
<div className="mb-6">
<h1 className="font-merchant font-light text-4xl md:text-5xl lg:text-6xl mb-4">
Buy <span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Passes</span>
</h1>
@@ -194,13 +218,12 @@ export function PassesPage({
>
{passTypes.map((pass) => (
<div key={pass.id} className="relative h-full">
<Card className={`relative h-full flex flex-col transition-all duration-300 cursor-pointer ${
pass.popular
? 'ring-2 ring-primary shadow-xl'
: selectedPass === pass.id
? 'ring-2 ring-primary/50 shadow-lg'
: 'border-gray-200 shadow-md hover:shadow-lg hover:border-primary/30'
}`}>
<Card className={`relative h-full flex flex-col transition-all duration-300 cursor-pointer ${pass.popular
? 'ring-2 ring-primary shadow-xl'
: selectedPass === pass.id
? 'ring-2 ring-primary/50 shadow-lg'
: 'border-gray-200 shadow-md hover:shadow-lg hover:border-primary/30'
}`}>
{/* Popular Badge */}
{pass.popular && (
@@ -256,15 +279,14 @@ export function PassesPage({
</div>
</div>
{/* CTA Button - Pushed to bottom */}
{/* ✅ UPDATED: CTA Button with login logic */}
<div className="flex-shrink-0 space-y-3">
<Button
className={`w-full h-12 rounded-lg font-semibold transition-all duration-300 font-poppins ${
pass.popular
? 'bg-primary hover:bg-primary/90 text-white shadow-md hover:shadow-lg'
: 'bg-gray-900 hover:bg-gray-800 text-white hover:shadow-md'
}`}
onClick={onCheckoutClick}
className={`w-full h-12 rounded-lg font-semibold transition-all duration-300 font-poppins ${pass.popular
? 'bg-primary hover:bg-primary/90 text-white shadow-md hover:shadow-lg'
: 'bg-gray-900 hover:bg-gray-800 text-white hover:shadow-md'
}`}
onClick={handlePurchaseClick} // ✅ Use the new handler
>
PURCHASE NOW
</Button>
@@ -570,6 +592,12 @@ export function PassesPage({
</div>
</div>
<LoginModal
isOpen={isLoginOpen}
onClose={() => setIsLoginOpen(false)}
onLoginSuccess={handleLoginSuccess}
/>
</Layout>
);
}

View File

@@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog@1.1.6";
import { XIcon } from "lucide-react@0.487.0";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import { cn } from "./utils";