integrate api in viewIternary page
This commit is contained in:
@@ -222,7 +222,7 @@ export function AppRouter({
|
||||
<ItineraryViewPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} />
|
||||
<Route path="/itinerary-view-design" element={
|
||||
<Route path="/itinerary-view-design/:itineraryId" element={
|
||||
<motion.div key="itinerary-view" {...pageTransition}>
|
||||
<ItineraryViewPageDesign {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
|
||||
@@ -23,6 +23,9 @@ export const itineraryApi = createApi({
|
||||
query: (itineraryId: number) => `/website/itinerary/${itineraryId}`,
|
||||
}),
|
||||
|
||||
getUserItineraries: builder.query({
|
||||
query: () => `/website/itinerary/all-initineraries`,
|
||||
}),
|
||||
|
||||
})
|
||||
});
|
||||
@@ -30,4 +33,5 @@ export const itineraryApi = createApi({
|
||||
export const {
|
||||
useCreateMagicItineraryMutation,
|
||||
useGetItineraryDetailsByIdQuery,
|
||||
useGetUserItinerariesQuery
|
||||
} = itineraryApi;
|
||||
@@ -7,6 +7,9 @@ import { Badge } from '../components/ui/badge';
|
||||
import Navbar from '../components/Navbar';
|
||||
import { Footer } from '../components/Footer';
|
||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
||||
import { useGetItineraryDetailsByIdQuery } from '../Redux/services/itinerary.service';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import LoadingSpinner from '../components/LoadingSpinner';
|
||||
|
||||
interface ItineraryViewPageDesignProps {
|
||||
onBackClick: () => void;
|
||||
@@ -35,18 +38,6 @@ interface ItineraryViewPageDesignProps {
|
||||
user?: { email: string; name: string; } | null;
|
||||
}
|
||||
|
||||
// Enhanced activity type with more details
|
||||
interface Activity {
|
||||
time: string;
|
||||
activity: string;
|
||||
location: string;
|
||||
address: string;
|
||||
image: string;
|
||||
categories: string[];
|
||||
description: string[];
|
||||
isFavorite?: boolean;
|
||||
}
|
||||
|
||||
export function ItineraryViewPageDesign({
|
||||
onBackClick,
|
||||
onHomeClick,
|
||||
@@ -74,304 +65,36 @@ export function ItineraryViewPageDesign({
|
||||
user
|
||||
}: ItineraryViewPageDesignProps) {
|
||||
const [viewMode, setViewMode] = useState<'daily' | 'summary'>('daily');
|
||||
const [favorites, setFavorites] = useState<Set<string>>(new Set());
|
||||
// const [favorites, setFavorites] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleFavorite = (activityKey: string) => {
|
||||
setFavorites(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(activityKey)) {
|
||||
newSet.delete(activityKey);
|
||||
} else {
|
||||
newSet.add(activityKey);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
// ── API Integration ──────────────────────────────────────────────────────────
|
||||
const { itineraryId } = useParams();
|
||||
const { data: itineraryDetails, isLoading } = useGetItineraryDetailsByIdQuery(itineraryId);
|
||||
|
||||
// Enhanced itinerary data with images, addresses, and detailed info
|
||||
const generatedItinerary = {
|
||||
destination: {
|
||||
name: 'Melbourne',
|
||||
country: 'Australia',
|
||||
weather: '18°C, Sunny',
|
||||
image: 'https://images.unsplash.com/photo-1514395462725-fb4566210144?w=400&h=300&fit=crop'
|
||||
},
|
||||
totalDays: 3,
|
||||
estimatedCost: '$450 AUD',
|
||||
includedActivities: 18,
|
||||
dailyPlans: [
|
||||
{
|
||||
day: 1,
|
||||
title: "City Center & Culture",
|
||||
activities: [
|
||||
{
|
||||
time: '8:00 am',
|
||||
activity: 'The Langham Melbourne',
|
||||
location: 'The Langham Melbourne',
|
||||
address: '1 Southgate Avenue, Southbank VIC 3006',
|
||||
image: 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=800&h=600&fit=crop',
|
||||
categories: ['Accommodation', 'Luxury'],
|
||||
description: [
|
||||
'Check-in at luxury riverside hotel',
|
||||
'Enjoy complimentary breakfast',
|
||||
'Relax at the spa facilities',
|
||||
'Explore the surrounding Southbank area'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '10:00 am',
|
||||
activity: 'Federation Square',
|
||||
location: 'Federation Square',
|
||||
address: 'Corner Swanston & Flinders Streets, Melbourne VIC 3000',
|
||||
image: 'https://images.unsplash.com/photo-1514395462725-fb4566210144?w=800&h=600&fit=crop',
|
||||
categories: ['Culture', 'Landmark'],
|
||||
description: [
|
||||
'Explore Melbourne\'s cultural precinct',
|
||||
'Visit the ACMI museum',
|
||||
'Enjoy street performances',
|
||||
'Take photos at iconic locations'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '12:00 pm',
|
||||
activity: 'Degrave Street Café',
|
||||
location: 'Degrave Street Espresso Bar',
|
||||
address: '23-25 Degraves Street, Melbourne VIC 3000',
|
||||
image: 'https://images.unsplash.com/photo-1554118811-1e0d58224f24?w=800&h=600&fit=crop',
|
||||
categories: ['Food', 'Drinks', 'Culture'],
|
||||
description: [
|
||||
'Coffee at Pellegrini\'s Espresso Bar (iconic old-school cafe)',
|
||||
'Try the famous jam doughnuts',
|
||||
'Shop for fresh produce in the Deli Hall',
|
||||
'Pick up unique souvenirs in the General Merchandise section'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '2:00 pm',
|
||||
activity: 'Royal Botanic Gardens',
|
||||
location: 'Royal Botanic Gardens Victoria',
|
||||
address: 'Birdwood Avenue, South Yarra VIC 3141',
|
||||
image: 'https://images.unsplash.com/photo-1585320806297-9794b3e4eeae?w=800&h=600&fit=crop',
|
||||
categories: ['Nature', 'Culture'],
|
||||
description: [
|
||||
'Stroll through stunning landscaped gardens',
|
||||
'Visit the Australian Forest Walk',
|
||||
'Relax by the Ornamental Lake',
|
||||
'Join a free guided walking tour'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '4:00 pm',
|
||||
activity: 'National Gallery of Victoria',
|
||||
location: 'NGV International',
|
||||
address: '180 St Kilda Road, Melbourne VIC 3006',
|
||||
image: 'https://images.unsplash.com/photo-1564399577149-749794d74eee?w=800&h=600&fit=crop',
|
||||
categories: ['Culture', 'Art'],
|
||||
description: [
|
||||
'Explore Australia\'s oldest art museum',
|
||||
'View international and Australian art collections',
|
||||
'Visit the stunning water wall entrance',
|
||||
'Browse the NGV design store'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '7:00 pm',
|
||||
activity: 'Dinner at Chin Chin',
|
||||
location: 'Chin Chin Restaurant',
|
||||
address: '125 Flinders Lane, Melbourne VIC 3000',
|
||||
image: 'https://images.unsplash.com/photo-1552566626-52f8b828add9?w=800&h=600&fit=crop',
|
||||
categories: ['Food', 'Drinks'],
|
||||
description: [
|
||||
'Experience modern Thai cuisine',
|
||||
'Try signature dishes like the Betel Leaf',
|
||||
'Enjoy the vibrant atmosphere',
|
||||
'Book ahead or walk-in for bar seating'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
day: 2,
|
||||
title: "Markets & Neighborhoods",
|
||||
activities: [
|
||||
{
|
||||
time: '8:00 am',
|
||||
activity: 'Queen Victoria Market',
|
||||
location: 'Queen Victoria Market',
|
||||
address: 'Queen Street, Melbourne VIC 3000',
|
||||
image: 'https://images.unsplash.com/photo-1555939594-58d7cb561ad1?w=800&h=600&fit=crop',
|
||||
categories: ['Food', 'Shopping', 'Culture'],
|
||||
description: [
|
||||
'Explore Melbourne\'s historic market (since 1878)',
|
||||
'Sample fresh local produce',
|
||||
'Shop for artisan goods and souvenirs',
|
||||
'Grab breakfast at the Deli Hall'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '10:30 am',
|
||||
activity: 'Fitzroy Street Art Tour',
|
||||
location: 'Fitzroy Arts Precinct',
|
||||
address: 'Gertrude Street, Fitzroy VIC 3065',
|
||||
image: 'https://images.unsplash.com/photo-1499781350541-7783f6c6a0c8?w=800&h=600&fit=crop',
|
||||
categories: ['Culture', 'Art'],
|
||||
description: [
|
||||
'Walk through famous street art laneways',
|
||||
'Discover works by renowned artists',
|
||||
'Visit independent galleries',
|
||||
'Explore vintage and record stores'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '12:30 pm',
|
||||
activity: 'Brunswick Street Lunch',
|
||||
location: 'Brunswick Street Precinct',
|
||||
address: 'Brunswick Street, Fitzroy VIC 3065',
|
||||
image: 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&h=600&fit=crop',
|
||||
categories: ['Food', 'Drinks'],
|
||||
description: [
|
||||
'Choose from diverse dining options',
|
||||
'Try local cafes and restaurants',
|
||||
'Explore bookshops and boutiques',
|
||||
'Enjoy the vibrant neighborhood atmosphere'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '2:30 pm',
|
||||
activity: 'Carlton Gardens',
|
||||
location: 'Carlton Gardens',
|
||||
address: 'Carlton Gardens, Carlton VIC 3053',
|
||||
image: 'https://images.unsplash.com/photo-1519331379826-f10be5486c6f?w=800&h=600&fit=crop',
|
||||
categories: ['Nature', 'Culture', 'Landmark'],
|
||||
description: [
|
||||
'Visit the UNESCO World Heritage site',
|
||||
'See the Royal Exhibition Building',
|
||||
'Stroll through Victorian-era gardens',
|
||||
'Relax by the ornamental fountains'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '4:00 pm',
|
||||
activity: 'Melbourne Museum',
|
||||
location: 'Melbourne Museum',
|
||||
address: '11 Nicholson Street, Carlton VIC 3053',
|
||||
image: 'https://images.unsplash.com/photo-1566127992631-137a642a90f4?w=800&h=600&fit=crop',
|
||||
categories: ['Culture', 'Museum'],
|
||||
description: [
|
||||
'Explore natural and cultural history',
|
||||
'Visit the Bunjilaka Aboriginal Centre',
|
||||
'See the Forest Gallery living ecosystem',
|
||||
'Discover Melbourne\'s story exhibition'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '7:00 pm',
|
||||
activity: 'Rooftop Bar Experience',
|
||||
location: 'Naked for Satan',
|
||||
address: '285 Brunswick Street, Fitzroy VIC 3065',
|
||||
image: 'https://images.unsplash.com/photo-1514933651103-005eec06c04b?w=800&h=600&fit=crop',
|
||||
categories: ['Drinks', 'Food'],
|
||||
description: [
|
||||
'Enjoy sunset views from the rooftop',
|
||||
'Try Spanish-style pintxos',
|
||||
'Sample craft cocktails and local beers',
|
||||
'Experience Melbourne\'s bar culture'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
day: 3,
|
||||
title: "Coastal Adventure",
|
||||
activities: [
|
||||
{
|
||||
time: '8:00 am',
|
||||
activity: 'St. Kilda Beach',
|
||||
location: 'St. Kilda Beach',
|
||||
address: 'Jacka Boulevard, St Kilda VIC 3182',
|
||||
image: 'https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800&h=600&fit=crop',
|
||||
categories: ['Nature', 'Beach'],
|
||||
description: [
|
||||
'Morning walk along the iconic beach',
|
||||
'Visit the historic St Kilda Pier',
|
||||
'See the little penguins at sunset',
|
||||
'Explore the Sunday Esplanade Market (weekends)'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '10:00 am',
|
||||
activity: 'Acland Street Cafes',
|
||||
location: 'Acland Street',
|
||||
address: 'Acland Street, St Kilda VIC 3182',
|
||||
image: 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=800&h=600&fit=crop',
|
||||
categories: ['Food', 'Drinks'],
|
||||
description: [
|
||||
'Brunch at famous cake shops',
|
||||
'Try traditional European pastries',
|
||||
'Visit Lentil as Anything (pay-as-you-feel)',
|
||||
'Browse vintage shops and bookstores'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '12:00 pm',
|
||||
activity: 'Luna Park Melbourne',
|
||||
location: 'Luna Park Melbourne',
|
||||
address: '18 Lower Esplanade, St Kilda VIC 3182',
|
||||
image: 'https://images.unsplash.com/photo-1513026705753-bc3fffca8bf4?w=800&h=600&fit=crop',
|
||||
categories: ['Entertainment', 'Landmark'],
|
||||
description: [
|
||||
'Visit Melbourne\'s iconic amusement park',
|
||||
'Ride the historic Scenic Railway (1912)',
|
||||
'Take photos at Mr Moon entrance',
|
||||
'Enjoy carnival games and rides'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '2:00 pm',
|
||||
activity: 'Brighton Beach Boxes',
|
||||
location: 'Brighton Beach',
|
||||
address: 'Esplanade, Brighton VIC 3186',
|
||||
image: 'https://images.unsplash.com/photo-1520208422220-d12a3c588e6c?w=800&h=600&fit=crop',
|
||||
categories: ['Culture', 'Landmark'],
|
||||
description: [
|
||||
'Photograph the famous colorful bathing boxes',
|
||||
'Walk along the pristine beach',
|
||||
'Learn about the heritage structures',
|
||||
'Relax in the beachside atmosphere'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '4:00 pm',
|
||||
activity: 'Southbank Promenade',
|
||||
location: 'Southbank',
|
||||
address: 'Southbank Promenade, Southbank VIC 3006',
|
||||
image: 'https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=800&h=600&fit=crop',
|
||||
categories: ['Culture', 'Shopping'],
|
||||
description: [
|
||||
'Stroll along the Yarra River',
|
||||
'Visit arts and craft markets',
|
||||
'Explore restaurants and cafes',
|
||||
'Enjoy river views and street performers'
|
||||
]
|
||||
},
|
||||
{
|
||||
time: '7:00 pm',
|
||||
activity: 'Farewell Dinner at Vue de Monde',
|
||||
location: 'Vue de Monde',
|
||||
address: 'Level 55, Rialto, 525 Collins Street, Melbourne VIC 3000',
|
||||
image: 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=800&h=600&fit=crop',
|
||||
categories: ['Food', 'Drinks', 'Luxury'],
|
||||
description: [
|
||||
'Experience fine dining at 55th floor',
|
||||
'Enjoy panoramic Melbourne views',
|
||||
'Taste modern Australian cuisine',
|
||||
'Celebrate the end of your journey'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const generatedItinerary = itineraryDetails ?? null;
|
||||
const days = generatedItinerary?.days ?? [];
|
||||
const summaries = generatedItinerary?.summary ?? [];
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// const toggleFavorite = (activityKey: string) => {
|
||||
// setFavorites(prev => {
|
||||
// const newSet = new Set(prev);
|
||||
// if (newSet.has(activityKey)) {
|
||||
// newSet.delete(activityKey);
|
||||
// } else {
|
||||
// newSet.add(activityKey);
|
||||
// }
|
||||
// return newSet;
|
||||
// });
|
||||
// };
|
||||
|
||||
// ── Loading State ─────────────────────────────────────────────────────────────
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingSpinner/>
|
||||
);
|
||||
}
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
@@ -422,12 +145,14 @@ export function ItineraryViewPageDesign({
|
||||
<Star className="w-6 h-6 fill-current" />
|
||||
<h1 className="font-merchant text-4xl md:text-5xl lg:text-6xl leading-tight">
|
||||
<span className="font-light">Your</span>{' '}
|
||||
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Magic Itinerary</span>
|
||||
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pr-3">
|
||||
{generatedItinerary?.title ?? 'Magic Itinerary'}
|
||||
</span>
|
||||
</h1>
|
||||
<Star className="w-6 h-6 fill-current" />
|
||||
</div>
|
||||
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 max-w-2xl mx-auto">
|
||||
Here's your personalized {generatedItinerary.totalDays}-day adventure in {generatedItinerary.destination.name}!
|
||||
Here's your personalized {generatedItinerary?.totalDays}-day adventure in {generatedItinerary?.city}!
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
@@ -437,6 +162,7 @@ export function ItineraryViewPageDesign({
|
||||
<section className="py-8">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-6xl mx-auto space-y-8">
|
||||
|
||||
{/* View Toggle */}
|
||||
<div className="flex justify-center">
|
||||
<div className="bg-muted p-1 rounded-lg">
|
||||
@@ -459,31 +185,46 @@ export function ItineraryViewPageDesign({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Itinerary Overview */}
|
||||
{/* Itinerary Overview Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
>
|
||||
<Card className="p-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="text-center">
|
||||
<div className="font-merchant text-3xl text-primary mb-2">{generatedItinerary.totalDays}</div>
|
||||
<div className="font-poppins text-sm text-muted-foreground font-normal">Days</div>
|
||||
{/* Banner Image */}
|
||||
<Card className="overflow-hidden">
|
||||
<div className="relative h-40 md:h-48">
|
||||
<ImageWithFallback
|
||||
src={generatedItinerary?.cityBanner}
|
||||
alt={generatedItinerary?.city}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
|
||||
<div className="absolute bottom-4 left-6">
|
||||
<p className="font-poppins text-xs font-medium text-white/70 uppercase tracking-wider mb-1">Your Trip</p>
|
||||
<h3 className="font-merchant text-2xl md:text-3xl text-white font-semibold">{generatedItinerary?.city}</h3>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="font-merchant text-3xl text-primary mb-2">{generatedItinerary.includedActivities}</div>
|
||||
<div className="font-poppins text-sm text-muted-foreground font-normal">Activities</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Row */}
|
||||
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-white">
|
||||
<div className="flex flex-col items-center py-5">
|
||||
<span className="font-merchant text-3xl text-primary">{generatedItinerary?.totalDays}</span>
|
||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Days</span>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="font-merchant text-3xl text-primary mb-2">{generatedItinerary.estimatedCost}</div>
|
||||
<div className="font-poppins text-sm text-muted-foreground font-normal">Estimated Cost</div>
|
||||
<div className="flex flex-col items-center py-5">
|
||||
<span className="font-merchant text-3xl text-primary">{generatedItinerary?.totalStops}</span>
|
||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Stops</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center py-5">
|
||||
<span className="font-merchant text-3xl text-primary">{days[0]?.date}</span>
|
||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-1">Start Date</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Daily Plans - Enhanced View */}
|
||||
{/* ── Daily View ──────────────────────────────────────────────────── */}
|
||||
{viewMode === 'daily' && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@@ -491,43 +232,24 @@ export function ItineraryViewPageDesign({
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
className="space-y-12"
|
||||
>
|
||||
{generatedItinerary.dailyPlans.map((day, dayIndex) => (
|
||||
{days.map((day: any, dayIndex: number) => (
|
||||
<div key={dayIndex} className="space-y-6">
|
||||
{/* Location Header with Weather - Only show for first day or when city actually changes */}
|
||||
{(() => {
|
||||
// Get current day's destination (fallback to main destination if not specified)
|
||||
const currentDestination = (day as any).destination || generatedItinerary.destination;
|
||||
|
||||
// Check if this is the first day
|
||||
if (dayIndex === 0) return true;
|
||||
|
||||
// Check if destination changed from previous day
|
||||
if (dayIndex > 0) {
|
||||
const previousDay = generatedItinerary.dailyPlans[dayIndex - 1];
|
||||
const previousDestination = (previousDay as any).destination || generatedItinerary.destination;
|
||||
|
||||
// Only show if city name is different
|
||||
return currentDestination.name !== previousDestination.name;
|
||||
}
|
||||
|
||||
return false;
|
||||
})() && (
|
||||
|
||||
{/* City / Weather header — only on first day */}
|
||||
{dayIndex === 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 + dayIndex * 0.1 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="bg-gray-50 rounded-2xl p-6 shadow-sm border border-gray-200"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="font-poppins text-3xl md:text-4xl leading-tight mb-2">
|
||||
{((day as any).destination || generatedItinerary.destination).name}, {((day as any).destination || generatedItinerary.destination).country}
|
||||
{generatedItinerary?.city}, Australia
|
||||
</h2>
|
||||
<p className="font-poppins text-base text-primary font-medium">{((day as any).destination || generatedItinerary.destination).weather}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Sun className="w-10 h-10 text-amber-500" />
|
||||
</div>
|
||||
<Sun className="w-10 h-10 text-amber-500" />
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -540,24 +262,28 @@ export function ItineraryViewPageDesign({
|
||||
className="flex items-center gap-4 pl-2"
|
||||
>
|
||||
<div className="bg-gradient-to-br from-primary to-secondary text-white w-16 h-16 rounded-full flex items-center justify-center shadow-lg">
|
||||
<span className="font-merchant text-2xl font-semibold">{day.day}</span>
|
||||
<span className="font-merchant text-2xl font-semibold">{day.dayNumber}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-merchant text-2xl md:text-3xl leading-snug font-semibold">Day {day.day}</h3>
|
||||
<h3 className="font-merchant text-2xl md:text-3xl leading-snug font-semibold">
|
||||
Day {day.dayNumber}
|
||||
</h3>
|
||||
<p className="font-poppins text-base text-muted-foreground font-normal">{day.title}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* GMT Label */}
|
||||
{/* Time-zone label */}
|
||||
<div className="pl-2">
|
||||
<p className="font-poppins text-sm text-gray-500 font-normal">GMT</p>
|
||||
<p className="font-poppins text-sm text-gray-500 font-normal">
|
||||
{day.date}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Activity Cards - Desktop Grid Layout */}
|
||||
{/* Activity Cards */}
|
||||
<div className="space-y-8">
|
||||
{day.activities.map((activity, actIndex) => {
|
||||
const activityKey = `day${day.day}-act${actIndex}`;
|
||||
const isFavorite = favorites.has(activityKey);
|
||||
{day.items?.map((activity: any, actIndex: number) => {
|
||||
const activityKey = `day${day.dayNumber}-act${actIndex}`;
|
||||
// const isFavorite = favorites.has(activityKey);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -569,23 +295,25 @@ export function ItineraryViewPageDesign({
|
||||
>
|
||||
{/* Time Column */}
|
||||
<div className="flex-shrink-0 w-24 pt-2">
|
||||
<div className="font-poppins text-base font-medium text-gray-700">{activity.time}</div>
|
||||
<div className="font-poppins text-base font-medium text-gray-700">
|
||||
{activity.timeSlot}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Activity Card */}
|
||||
<div className="flex-1">
|
||||
<Card className="overflow-hidden hover:shadow-xl transition-shadow duration-300 border-2 border-gray-100">
|
||||
<CardContent className="p-0">
|
||||
{/* Hero Image with Overlay Buttons */}
|
||||
{/* Hero Image */}
|
||||
<div className="relative h-64 md:h-72 bg-gray-200">
|
||||
<ImageWithFallback
|
||||
src={activity.image}
|
||||
alt={activity.activity}
|
||||
src={activity.imageUrl}
|
||||
alt={activity.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
{/* Favorite Heart Button - Top Right */}
|
||||
<div className="absolute top-4 right-4">
|
||||
|
||||
{/* Favourite Button */}
|
||||
{/* <div className="absolute top-4 right-4">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
@@ -594,12 +322,18 @@ export function ItineraryViewPageDesign({
|
||||
>
|
||||
<Heart className={`w-5 h-5 ${isFavorite ? 'fill-primary text-primary' : 'text-gray-700'}`} />
|
||||
</Button>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Get Directions Button - Bottom Left */}
|
||||
{/* Get Directions — links to Google Maps via lat/lng */}
|
||||
<div className="absolute bottom-4 left-4">
|
||||
<Button
|
||||
<Button
|
||||
className="bg-primary hover:bg-primary/90 text-white font-poppins font-semibold shadow-lg px-6 py-3 rounded-xl"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://www.google.com/maps?q=${activity.latitude},${activity.longitude}`,
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
>
|
||||
<Navigation className="w-4 h-4 mr-2" />
|
||||
Get Directions
|
||||
@@ -607,40 +341,41 @@ export function ItineraryViewPageDesign({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Section */}
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-4">
|
||||
{/* Location Name & Address */}
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-merchant text-xl md:text-2xl leading-snug font-semibold text-gray-900">
|
||||
{activity.activity}
|
||||
{activity.title}
|
||||
</h4>
|
||||
<div className="flex items-start gap-2 text-gray-600">
|
||||
<MapPin className="w-4 h-4 mt-1 flex-shrink-0 text-primary" />
|
||||
<span className="font-poppins text-sm font-normal leading-relaxed">{activity.address}</span>
|
||||
<span className="font-poppins text-sm font-normal leading-relaxed">
|
||||
{activity.locationName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Badges */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activity.categories.map((category, catIndex) => (
|
||||
<Badge
|
||||
key={catIndex}
|
||||
{activity.categories?.map((cat: string, ci: number) => (
|
||||
<Badge
|
||||
key={ci}
|
||||
variant="secondary"
|
||||
className="font-poppins font-normal text-sm bg-primary/10 text-primary hover:bg-primary/20 px-3 py-1"
|
||||
>
|
||||
{category}
|
||||
{cat}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Activity Details - Bullet Points */}
|
||||
{/* Description */}
|
||||
<div className="space-y-2 pt-2">
|
||||
{activity.description.map((detail, detailIndex) => (
|
||||
<div key={detailIndex} className="flex items-start gap-3">
|
||||
<span className="text-primary font-semibold mt-1.5 flex-shrink-0">•</span>
|
||||
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">{detail}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-primary font-semibold mt-1 flex-shrink-0">•</span>
|
||||
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
|
||||
{activity.description}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -655,7 +390,7 @@ export function ItineraryViewPageDesign({
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Summary View */}
|
||||
{/* ── Summary View ─────────────────────────────────────────────────── */}
|
||||
{viewMode === 'summary' && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@@ -663,32 +398,57 @@ export function ItineraryViewPageDesign({
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<h3 className="font-merchant text-2xl md:text-3xl text-center mb-8 leading-tight font-semibold">Trip Summary</h3>
|
||||
<h3 className="font-merchant text-2xl md:text-3xl text-center mb-8 leading-tight font-semibold">
|
||||
Trip Summary
|
||||
</h3>
|
||||
<Card className="p-6">
|
||||
<div className="space-y-6">
|
||||
{generatedItinerary.dailyPlans.map((day, index) => (
|
||||
<div key={index} className="border-l-4 border-primary pl-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Calendar className="w-5 h-5 text-primary" />
|
||||
<h4 className="font-merchant text-lg md:text-xl leading-snug font-semibold">Day {day.day}: {day.title}</h4>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{day.activities.map((activity, actIndex) => (
|
||||
<div key={actIndex} className="flex items-start gap-3 text-sm">
|
||||
<CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<span className="font-poppins text-gray-700 font-medium">{activity.activity}</span>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className="font-poppins text-gray-500 text-xs font-normal">{activity.time}</span>
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="font-poppins text-gray-500 text-xs font-normal">{activity.location}</span>
|
||||
{days.map((day: any, dayIndex: number) => {
|
||||
// ✅ Match summary to the correct day by dayNumber
|
||||
const daySummary = summaries.find((s: any) => s.dayNumber === day.dayNumber);
|
||||
|
||||
const dayDate = days[0]?.date
|
||||
? new Date(
|
||||
new Date(days[0].date).setDate(
|
||||
new Date(days[0].date).getDate() + dayIndex
|
||||
)
|
||||
).toLocaleDateString('en-AU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
})
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div key={dayIndex} className="border-l-4 border-primary pl-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-5 h-5 text-primary" />
|
||||
<h4 className="font-merchant text-lg md:text-xl leading-snug font-semibold">
|
||||
Day {day.dayNumber}: {daySummary?.title ?? day.title}
|
||||
</h4>
|
||||
</div>
|
||||
<span className="font-poppins text-sm text-primary font-medium">{dayDate}</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{daySummary?.items?.map((item: any, actIndex: number) => (
|
||||
<div key={actIndex} className="flex items-start gap-3 text-sm">
|
||||
<CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<span className="font-poppins text-gray-700 font-medium">{item.title}</span>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className="font-poppins text-gray-500 text-xs font-normal">{item.timeSlot}</span>
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="font-poppins text-gray-500 text-xs font-normal">{item.locationName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
@@ -709,16 +469,11 @@ export function ItineraryViewPageDesign({
|
||||
<Heart className="w-5 h-5 mr-2" />
|
||||
Create Another
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-primary hover:bg-primary/90 font-poppins font-semibold px-8 py-3 text-lg"
|
||||
>
|
||||
<Button className="bg-primary hover:bg-primary/90 font-poppins font-semibold px-8 py-3 text-lg">
|
||||
<Download className="w-5 h-5 mr-2" />
|
||||
Save Itinerary
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="font-poppins font-medium px-8 py-3 text-lg"
|
||||
>
|
||||
<Button variant="outline" className="font-poppins font-medium px-8 py-3 text-lg">
|
||||
<Share2 className="w-5 h-5 mr-2" />
|
||||
Share Trip
|
||||
</Button>
|
||||
@@ -728,7 +483,7 @@ export function ItineraryViewPageDesign({
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<Footer
|
||||
<Footer
|
||||
onHomeClick={onHomeClick}
|
||||
onMelbourneClick={onMelbourneClick}
|
||||
onPassesClick={onPassesClick}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { useGetUserCardsQuery, useGetUserProfileDetailsQuery, useUpdateUserProfi
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import LoadingSpinner from '../components/LoadingSpinner';
|
||||
import { useGetUserItinerariesQuery } from '../Redux/services/itinerary.service';
|
||||
|
||||
interface ProfilePageProps {
|
||||
onBackClick: () => void;
|
||||
@@ -170,8 +171,10 @@ export function ProfilePage({
|
||||
const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId)
|
||||
const [updateUserProfileDetails, { isLoading: savingChanges }] = useUpdateUserProfileDetailsMutation();
|
||||
const { data, isLoading: loadingCards } = useGetUserCardsQuery(sort)
|
||||
const { data: userItineraries, isLoading: loadingItineraries } = useGetUserItinerariesQuery({})
|
||||
|
||||
const cards = data ?? []
|
||||
const itineraries = userItineraries?.itineraries ?? []
|
||||
|
||||
useEffect(() => {
|
||||
if (userDetails) {
|
||||
@@ -427,8 +430,8 @@ export function ProfilePage({
|
||||
<CardContent className="p-8 space-y-6">
|
||||
{(() => {
|
||||
// Determine which pass type to show
|
||||
const hasUnlimitedPass = activeCards.some((card:any) => card.cardType.cardTypeName === 'selective_pass');
|
||||
const hasSelectivePass = activeCards.some((card:any) => card.cardType.cardTypeName === 'unlimited_card');
|
||||
const hasUnlimitedPass = activeCards.some((card: any) => card.cardType.cardTypeName === 'selective_pass');
|
||||
const hasSelectivePass = activeCards.some((card: any) => card.cardType.cardTypeName === 'unlimited_card');
|
||||
|
||||
if (hasUnlimitedPass) {
|
||||
return (
|
||||
@@ -681,7 +684,7 @@ export function ProfilePage({
|
||||
{/* Offers Button */}
|
||||
<div className="mt-8 text-center">
|
||||
<Button
|
||||
onClick={()=>navigate("/super-savings")}
|
||||
onClick={() => navigate("/super-savings")}
|
||||
className="bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins px-8 py-3 font-normal"
|
||||
>
|
||||
<Star className="w-4 h-4 mr-2" />
|
||||
@@ -725,17 +728,17 @@ export function ProfilePage({
|
||||
<div className="space-y-2 text-sm font-poppins font-light">
|
||||
<div className="flex justify-between">
|
||||
{card.cardMode === "flexi" ? (
|
||||
<>
|
||||
<span>Attractions:</span>
|
||||
<span>{card.noOfAttractions}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>Days:</span>
|
||||
<span>{card.noOfDays}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<>
|
||||
<span>Attractions:</span>
|
||||
<span>{card.noOfAttractions}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>Days:</span>
|
||||
<span>{card.noOfDays}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Expired on:</span>
|
||||
@@ -762,47 +765,47 @@ export function ProfilePage({
|
||||
<h2 className="font-poppins text-2xl font-normal">My Itineraries</h2>
|
||||
<Button
|
||||
className="bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-normal"
|
||||
onClick={()=>navigate("/create-itinerary-design")}
|
||||
onClick={() => navigate("/create-itinerary-design")}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Create Itinerary
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{mockItineraries.length > 0 ? (
|
||||
{itineraries?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{mockItineraries.map((itinerary) => (
|
||||
{itineraries.map((itinerary: any) => (
|
||||
<Card key={itinerary.id}>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="font-normal font-poppins">{itinerary.name}</h3>
|
||||
<p className="text-sm text-gray-600 font-poppins font-light">{itinerary.city}</p>
|
||||
<h3 className="font-normal font-poppins">{ }</h3>
|
||||
<p className="text-sm text-gray-600 font-poppins font-light">{itinerary.city.cityName} Unlimited Card</p>
|
||||
</div>
|
||||
<Badge variant={itinerary.status === 'active' ? 'default' : 'secondary'}>
|
||||
{itinerary.status}
|
||||
<Badge variant={itinerary.isActive === true ? 'default' : 'secondary'}>
|
||||
{itinerary.isActive ? "Active" : "Inactive"}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-sm font-poppins font-light">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-gray-500" />
|
||||
<span>{itinerary.duration}</span>
|
||||
<span>{itinerary.totalDays}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4 text-gray-500" />
|
||||
<span>{itinerary.attractions} attractions</span>
|
||||
<span>{itinerary?.attractions} attractions</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-gray-500" />
|
||||
<span>Created {new Date(itinerary.createdDate).toLocaleDateString()}</span>
|
||||
<span>Created {new Date(itinerary.createdAt).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full mt-4 font-poppins font-normal"
|
||||
onClick={onViewItineraryClick}
|
||||
onClick={()=>navigate(`/itinerary-view-design/${itinerary.id}`)}
|
||||
>
|
||||
View Itinerary
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user