diff --git a/src/Layout.tsx b/src/Layout.tsx index a48ac14..ab2a28f 100644 --- a/src/Layout.tsx +++ b/src/Layout.tsx @@ -1,6 +1,8 @@ -import { ReactNode } from 'react'; -import Navbar from './components/Navbar'; -import { Footer } from './components/Footer'; +import { ReactNode } from "react"; +import { useLocation } from "react-router-dom"; +import Navbar from "./components/Navbar"; +import { Footer } from "./components/Footer"; +import { getAutoNavigationSource } from "./utils/getAutoNavigationSource"; interface User { email: string; @@ -10,35 +12,37 @@ interface User { interface LayoutProps { children: ReactNode; activeCity?: string; - onSignInClick?: () => void; + onSignInClick?: () => void; onSignOutClick?: () => void; user?: User | null; } -export function Layout({ - children, - activeCity = 'Melbourne', +export function Layout({ + children, + activeCity, onSignInClick, onSignOutClick, - user + user, }: LayoutProps) { + const location = useLocation(); + + // 🧠 Use the helper to determine which city to show + const cityToUse = activeCity || getAutoNavigationSource(location); + return (
- {/* Navbar */} - {}} - onSignInClick={() => onSignInClick?.()} + onSignInClick={() => onSignInClick?.()} onSignOutClick={onSignOutClick} isUserSignedIn={!!user} user={user} /> - {/* Main Content */}
{children}
- {/* Footer */}
); -} \ No newline at end of file +} diff --git a/src/components/HowItWorksPage.tsx b/src/components/HowItWorksPage.tsx index 5600757..a15de1f 100644 --- a/src/components/HowItWorksPage.tsx +++ b/src/components/HowItWorksPage.tsx @@ -166,7 +166,7 @@ function HeroCarousel({ onCheckoutClick, onPassesClick }: { onCheckoutClick: ()
All-in-One City Pass - + {/* Description */} - + {/* Subtle overlay for depth */}
@@ -327,11 +327,10 @@ function HeroCarousel({ onCheckoutClick, onPassesClick }: { onCheckoutClick: () aria-label={`Go to slide ${index + 1}`} >
))} @@ -446,13 +445,13 @@ const steps: Step[] = [ ]; // Step Component -function StepSection({ - step, - index, +function StepSection({ + step, + index, onInView -}: { - step: Step; - index: number; +}: { + step: Step; + index: number; onInView: (index: number) => void; }) { const ref = useRef(null); @@ -528,7 +527,7 @@ function StepSection({
)} - + -
- - {/* Hero Section - Full Carousel */} - +
- {/* Unified How It Works Section with Flight Path */} -
- {/* Curved Flight Path SVG - Desktop Only */} -
- - {/* Define gradients */} - - - - - - - + {/* Hero Section - Full Carousel */} + - {/* Main curved flight path - Animated with scroll */} - + {/* Curved Flight Path SVG - Desktop Only */} +
+ - - {/* Shadow path for depth - follows main path */} - + xmlns="http://www.w3.org/2000/svg" + preserveAspectRatio="xMidYMin slice" + > + {/* Define gradients */} + + + + + + + - {/* Destination pin at end of path - appears when fully drawn */} - {activeStep === 2 && ( - - {/* Pin shadow */} - - - {/* Pin circle */} - - - {/* Pin highlight */} - - - )} - -
- -
- {/* Header */} - -

- How it{' '} - works -

-
- - {/* Steps Container */} -
- {steps.map((step, index) => ( - - ))} + + {/* Shadow path for depth - follows main path */} + + + {/* Destination pin at end of path - appears when fully drawn */} + {activeStep === 2 && ( + + {/* Pin shadow */} + + + {/* Pin circle */} + + + {/* Pin highlight */} + + + )} +
-
-
- - - {/* Additional Sections */} -
- {/* Why Choose CityCards Section */} -
- -
-
- - {/* Pass Options Overview Section */} -
-
- -

- Choose Your - Perfect Pass -

-

- Flexible options designed to match your travel style -

-
- -
- {/* Flexi Card */} +
+ {/* Header */} -
-

- FLEXI CARD -

-

- Perfect for travelers who want to explore selected attractions at their own pace with essential features. -

-
- -
    - {[ - 'Access to selected attractions', - 'Limited number of attractions per pass', - 'Flexible validity period', - 'Priority entry where available', - 'Mobile ticket delivery' - ].map((feature) => ( -
  • -
    - - - -
    - {feature} -
  • - ))} -
- -
-

- ✓ Free cancellation up to 24 hours • Instant delivery -

-
- - +

+ How it{' '} + works +

- {/* Unlimited Card */} - - {/* Popular Badge */} -
-
- Most Popular -
-
- -
-

- UNLIMITED CARD -

-

- The ultimate experience for adventure seekers who want unlimited access to all attractions with premium features. -

-
- -
    - {[ - 'Unlimited access to all attractions', - 'Time-limited validity (7 days)', - 'Skip-the-line access', - 'Expert guide inclusion', - 'Mobile app access', - 'Premium customer support' - ].map((feature) => ( -
  • -
    - - - -
    - {feature} -
  • - ))} -
- -
-

- ✓ Free cancellation up to 24 hours • Instant delivery -

-
- - -
-
-
-
- -
- {/* Magic Itinerary Teaser Section */} - - - {/* Enhanced Testimonials Section */} - - - {/* Mobile App Section */} - -
- - {/* CTA Section */} -
-
-
-
-
- -
- -

- Ready to Start Your Adventure? -

-

- Join millions of travelers who've discovered the smarter way to explore cities -

- -
- - + {/* Steps Container */} +
+ {steps.map((step, index) => ( + + ))}
- +
+
+ + + + {/* Additional Sections */} +
+ {/* Why Choose CityCards Section */} +
+ +
+
+ + {/* Pass Options Overview Section */} +
+
+ +

+ Choose Your + Perfect Pass +

+

+ Flexible options designed to match your travel style +

+
+ +
+ {/* Flexi Card */} + +
+

+ FLEXI CARD +

+

+ Perfect for travelers who want to explore selected attractions at their own pace with essential features. +

+
+ +
    + {[ + 'Access to selected attractions', + 'Limited number of attractions per pass', + 'Flexible validity period', + 'Priority entry where available', + 'Mobile ticket delivery' + ].map((feature) => ( +
  • +
    + + + +
    + {feature} +
  • + ))} +
+ +
+

+ ✓ Free cancellation up to 24 hours • Instant delivery +

+
+ + +
+ + {/* Unlimited Card */} + + {/* Popular Badge */} +
+
+ Most Popular +
+
+ +
+

+ UNLIMITED CARD +

+

+ The ultimate experience for adventure seekers who want unlimited access to all attractions with premium features. +

+
+ +
    + {[ + 'Unlimited access to all attractions', + 'Time-limited validity (7 days)', + 'Skip-the-line access', + 'Expert guide inclusion', + 'Mobile app access', + 'Premium customer support' + ].map((feature) => ( +
  • +
    + + + +
    + {feature} +
  • + ))} +
+ +
+

+ ✓ Free cancellation up to 24 hours • Instant delivery +

+
+ + +
+
+
+
+ +
+ {/* Magic Itinerary Teaser Section */} + + + {/* Enhanced Testimonials Section */} + + + {/* Mobile App Section */} + +
+ + {/* CTA Section */} +
+
+
+
+
+ +
+ +

+ Ready to Start Your Adventure? +

+

+ Join millions of travelers who've discovered the smarter way to explore cities +

+ +
+ + +
+
+
+
+ +
+ {/* Reviews Section */} +
- -
- {/* Reviews Section */} -
- -
); } diff --git a/src/components/MelbourneCardComparison.tsx b/src/components/MelbourneCardComparison.tsx index 710a40b..58aa1bb 100644 --- a/src/components/MelbourneCardComparison.tsx +++ b/src/components/MelbourneCardComparison.tsx @@ -6,7 +6,7 @@ import { motion } from 'motion/react'; const cardOptions = [ { id: 'selective', - name: 'Selective Card', + name: 'Flexi Card', subtitle: 'Pick 5-10 things to do from a choice of 102 attractions tours and activities', priceRange: '$89-159', duration: '3-7 days', diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index cda5985..39d7ba3 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -84,6 +84,9 @@ export default function Navbar({ const location = useLocation(); const navigate = useNavigate(); + const [lastKnownCity, setLastKnownCity] = useState<'landing' | 'melbourne'>('landing'); + + // More flexible navigation configuration const navigationConfig = { landing: [ @@ -163,29 +166,59 @@ export default function Navbar({ // Check if we're on landing page const isLandingPage = location.pathname === '/'; - // Auto-detect navigation source based on activeCity and current page + // Restore from session on mount + useEffect(() => { + const savedCity = sessionStorage.getItem('lastKnownCity'); + if (savedCity === 'melbourne' || savedCity === 'landing') { + setLastKnownCity(savedCity); + } + }, []); + + // Save whenever it changes + useEffect(() => { + sessionStorage.setItem('lastKnownCity', lastKnownCity); + }, [lastKnownCity]); + + // Update lastKnownCity automatically when route clearly belongs to one city + useEffect(() => { + const path = location.pathname; + + // Melbourne-specific routes + const melbournePaths = [ + '/melbourne', + '/magic-itinerary', + '/super-savings', + '/attractions' + ]; + + if (melbournePaths.some(p => path.startsWith(p))) { + setLastKnownCity('melbourne'); + } + + // Landing-specific routes (root or home-like) + if (path === '/' || path.startsWith('/explore') || path.startsWith('/contact')) { + setLastKnownCity('landing'); + } + }, [location.pathname]); + + // ✅ Determine which navbar to show const getAutoNavigationSource = (): 'landing' | 'melbourne' => { - // If activeCity is explicitly set to 'shared', detect from context - if (activeCity.toLowerCase() === 'shared') { - // Check if we're on Melbourne-specific pages - const isMelbournePage = - location.pathname === '/melbourne' || - location.pathname.startsWith('/attractions') || - location.pathname === '/magic-itinerary' || - location.pathname === '/super-savings'; + const path = location.pathname; - return isMelbournePage ? 'melbourne' : 'landing'; + // Explicit routes + if (path.startsWith('/melbourne')) return 'melbourne'; + if (path === '/' || path.startsWith('/explore')) return 'landing'; + + // Shared routes + if (['/passes', '/how-it-works'].includes(path)) { + return lastKnownCity; // ← remembers where user came from } - // If activeCity is explicitly Melbourne, use melbourne source - if (activeCity.toLowerCase() === 'melbourne') { - return 'melbourne'; - } - - // Default to landing - return 'landing'; + // Fallback + return lastKnownCity; }; + // Get navigation items based on current context const getNavigationItems = (): NavigationItem[] => { const currentSource = getAutoNavigationSource(); @@ -353,6 +386,14 @@ export default function Navbar({ return () => window.removeEventListener('scroll', handleScroll); }, []); + useEffect(() => { + if (activeCity.toLowerCase() === 'melbourne') { + setLastKnownCity('melbourne'); + } else if (activeCity.toLowerCase() === 'landing' || activeCity.toLowerCase() === 'landingpage') { + setLastKnownCity('landing'); + } + }, [activeCity]); + // Handle city change for mobile dropdown const handleMobileCityChange = (city: string) => { console.log('City selected:', city); diff --git a/src/components/PassesPage.tsx b/src/components/PassesPage.tsx index 2d38475..2b0b66f 100644 --- a/src/components/PassesPage.tsx +++ b/src/components/PassesPage.tsx @@ -191,7 +191,7 @@ export function PassesPage({ return ( void; @@ -55,6 +56,8 @@ export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTou ]; const [currentCardIndex, setCurrentCardIndex] = useState(0); + const [videoLoaded, setVideoLoaded] = useState(false); + const handleVideoLoad = () => setVideoLoaded(true); useEffect(() => { const timer = setInterval(() => { @@ -174,200 +177,42 @@ export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTou - {/* Right - Animated Card Stack */} + {/* Right - Video Section */} -
- {/* Card Stack Container */} -
- {/* Third Card (Background) */} - + {/* Video Wrapper */} +
+
diff --git a/src/pages/landingPage.tsx b/src/pages/landingPage.tsx index d855e88..3e7b8d2 100644 --- a/src/pages/landingPage.tsx +++ b/src/pages/landingPage.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { motion } from 'motion/react'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import { ChevronDown, MapPin, Star, Shield, Clock, Smartphone } from 'lucide-react'; import Navbar from '../components/Navbar'; import { Footer } from '../components/Footer'; @@ -17,6 +17,8 @@ import { LandingTrustSection } from '../components/LandingTrustSection'; import { LandingMobileAppSection } from '../components/LandingMobileAppSection'; import { LandingNewsletterSection } from '../components/LandingNewsletterSection'; import { CustomPostcards } from '../components/CustomPostcards'; +import { Layout } from '../Layout'; +import { getAutoNavigationSource } from '../utils/getAutoNavigationSource'; @@ -43,6 +45,8 @@ export function LandingPage({ onSignInClick, onSignOutClick, user }: LandingPageProps) { const [currentCityIndex, setCurrentCityIndex] = useState(0); + const location = useLocation(); + const activeCity = getAutoNavigationSource(location); const cities = [ { @@ -91,16 +95,12 @@ export function LandingPage({ onSignInClick, return (
{/* Navbar */} - { - // Handle city change if needed, or remove this prop - }} - onSignInClick={onSignInClick} - onSignOutClick={onSignOutClick} - isUserSignedIn={!!user} - user={user} - /> + {/* City Submenu */} {/* - {/* Footer */} -
); } \ No newline at end of file diff --git a/src/utils/getAutoNavigationSource.ts b/src/utils/getAutoNavigationSource.ts new file mode 100644 index 0000000..ac2b1a0 --- /dev/null +++ b/src/utils/getAutoNavigationSource.ts @@ -0,0 +1,35 @@ +import { Location } from "react-router-dom"; + +/** + * Determines which city layout/navigation should be active. + * + * It reads the current pathname and the last known city (from sessionStorage) + * to decide whether to show Landing or Melbourne navigation. + */ +export function getAutoNavigationSource(location: Location): "landing" | "melbourne" { + const path = location.pathname; + const storedCity = sessionStorage.getItem("lastCity"); + + // ✅ When user is on a Melbourne page, remember it + if (path.startsWith("/melbourne")) { + sessionStorage.setItem("lastCity", "melbourne"); + return "melbourne"; + } + + // ✅ Shared pages — should use last city if available + if ( + path.startsWith("/passes") || + path.startsWith("/how-it-works") || // added + path.startsWith("/your-card") || // added + path.startsWith("/checkout") || + path.startsWith("/faqs") || + path.startsWith("/about") || + path.startsWith("/contact") + ) { + // Use stored city if exists, otherwise default to landing + return storedCity === "melbourne" ? "melbourne" : "landing"; + } + + // ✅ Default to landing + return "landing"; +}