show the created itinerary summary on the summary page

This commit is contained in:
aryabenade
2026-04-22 17:04:34 +05:30
parent e3ee51f70b
commit adbf30a0c2
4 changed files with 352 additions and 5 deletions

View File

@@ -39,6 +39,8 @@ import { CheckoutPage2 } from './pages/CheckoutPage2';
import { SuperSavingsDetailsPage } from './pages/SuperSavingsDetailsPage';
import { ViewCardDetailsPage } from './pages/ViewCardDetailsPageDesign';
import { CreateMagicItineraryPageDesign } from './pages/CreateMagicIternaryPageDesign';
import { ItineraryViewPageDesign } from './pages/ItineraryViewPageDesign';
import ItinerarySummaryPage from './pages/ItinerarySummaryPage';
// User type definition
interface User {
@@ -222,7 +224,12 @@ export function AppRouter({
} />
<Route path="/itinerary-view-design" element={
<motion.div key="itinerary-view" {...pageTransition}>
<ItineraryViewPage {...commonNavHandlers} />
<ItineraryViewPageDesign {...commonNavHandlers} />
</motion.div>
} />
<Route path="/itinerary-summary/:itineraryId" element={
<motion.div key="itinerary-summary" {...pageTransition}>
<ItinerarySummaryPage {...commonNavHandlers} />
</motion.div>
} />

View File

@@ -1,5 +1,5 @@
import { createApi} from "@reduxjs/toolkit/query/react";
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "../baseQuery";
export const itineraryApi = createApi({
@@ -19,10 +19,15 @@ export const itineraryApi = createApi({
}),
getItineraryDetailsById: builder.query({
query: (itineraryId: number) => `/website/itinerary/${itineraryId}`,
}),
})
});
export const {
useCreateMagicItineraryMutation,
useGetItineraryDetailsByIdQuery,
} = itineraryApi;

View File

@@ -48,6 +48,7 @@ import MuseumSoundsGoodIcon from '../imports/NounRelax65970551-9923-633';
import MuseumLoveItIcon from '../imports/NounRelax65970551-9923-637';
import { useCreateMagicItineraryMutation } from '../Redux/services/itinerary.service';
import { toast } from 'sonner';
import { useNavigate } from 'react-router-dom';
interface User {
@@ -190,6 +191,8 @@ export function CreateMagicItineraryPageDesign({
const [selectedActivity, setSelectedActivity] = useState<string | null>(null);
const [createMagicItinerary] = useCreateMagicItineraryMutation();
const navigate= useNavigate()
const toggleFavorite = (activityKey: string) => {
setFavorites(prev => {
const newSet = new Set(prev);
@@ -259,6 +262,7 @@ export function CreateMagicItineraryPageDesign({
setGeneratedItinerary(response);
setShowResults(true);
toast.success("Itinerary created successfully!");
navigate(`/itinerary-summary/${response?.data?.id}`)
} catch (error) {
console.error("Error creating itinerary:", error);
toast.error("Failed to create itinerary. Please try again.");

View File

@@ -0,0 +1,331 @@
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import {
MapPin,
Calendar,
ChevronDown,
Share2,
Download,
ChevronRight,
} from 'lucide-react';
import { Button } from '../components/ui/button';
import { Card, CardContent } from '../components/ui/card';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import { useCreateMagicItineraryMutation, useGetItineraryDetailsByIdQuery } from '../Redux/services/itinerary.service';
import { toast } from 'sonner';
import { useNavigate, useParams } from 'react-router-dom';
const ItinerarySummaryPage = () => {
const [viewMode, setViewMode] = useState<'daily' | 'summary'>('daily');
const [selectedDayTab, setSelectedDayTab] = useState(1);
const [selectedActivity, setSelectedActivity] = useState<string | null>(null);
const navigate = useNavigate();
const { itineraryId } = useParams()
const { data: itineraryDetails, isLoading: itineraryDetailsLoading } = useGetItineraryDetailsByIdQuery(itineraryId);
const generatedItinerary = itineraryDetails ?? null;
const days = generatedItinerary?.days ?? [];
const summaries = generatedItinerary?.summary ?? [];
const selectedDayPlan = days?.find((d: any) => d.dayNumber === selectedDayTab);
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="space-y-8 max-w-3xl mx-auto"
>
{/* Title */}
<div className="text-center space-y-1">
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
<span className="font-normal">Your</span>
</h1>
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
<span className="font-bold text-primary italic">{generatedItinerary?.title}</span>
</h1>
</div>
{/* Trip Details Card */}
<div className="relative overflow-hidden rounded-2xl border border-gray-100 shadow-sm">
{/* Background Image */}
<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-5 right-5">
<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 leading-snug font-semibold">{generatedItinerary?.city}</h3>
</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-4">
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.totalDays}</span>
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Days</span>
</div>
<div className="flex flex-col items-center py-4">
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.totalStops}</span>
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Stops</span>
</div>
<div className="flex flex-col items-center py-4">
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.days[0]?.date}</span>
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Start Date</span>
</div>
</div>
</div>
{/* Share & Download Buttons */}
<div className="flex gap-3">
<Button
variant="outline"
className="flex-1 border-2 border-primary/20 text-primary hover:bg-primary/5 font-poppins font-medium rounded-xl py-3"
>
<Share2 className="w-4 h-4 mr-2" />
Share
</Button>
<Button
variant="outline"
className="flex-1 border-2 border-primary/20 text-primary hover:bg-primary/5 font-poppins font-medium rounded-xl py-3"
>
<Download className="w-4 h-4 mr-2" />
Download
</Button>
</div>
{/* View Toggle */}
<div className="flex justify-center">
<div className="bg-gray-100 p-1 rounded-full inline-flex">
<button
onClick={() => setViewMode('daily')}
className={`px-6 py-2.5 rounded-full font-poppins font-medium text-sm transition-all ${viewMode === 'daily'
? 'bg-white shadow-sm text-gray-900'
: 'text-gray-500 hover:text-gray-700'
}`}
>
Daily View
</button>
<button
onClick={() => setViewMode('summary')}
className={`px-6 py-2.5 rounded-full font-poppins font-medium text-sm transition-all ${viewMode === 'summary'
? 'bg-white shadow-sm text-gray-900'
: 'text-gray-500 hover:text-gray-700'
}`}
>
Summary
</button>
</div>
</div>
{/* Daily View */}
{viewMode === 'daily' && (
<div className="space-y-6">
{/* Day Tabs */}
<div className="flex items-center gap-2 overflow-x-auto pb-2">
{days?.map((day: any) => (
<button
key={day.dayNumber}
onClick={() => setSelectedDayTab(day.dayNumber)}
className={`px-5 py-2.5 rounded-xl whitespace-nowrap font-poppins text-base transition-all ${selectedDayTab === day.dayNumber
? 'text-primary font-semibold bg-primary/10 border border-primary/20'
: 'text-gray-400 font-medium hover:text-gray-600'
}`}
>
Day {day.dayNumber}
</button>
))}
{days?.length > 4 && (
<button className="p-2 text-gray-400 hover:text-gray-600">
<ChevronRight className="w-4 h-4" />
</button>
)}
</div>
{/* Activities for selected day */}
{selectedDayPlan && (
<AnimatePresence mode="wait">
<motion.div key={`day-${selectedDayTab}`} initial={{ opacity: 0, x: 10 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -10 }} transition={{ duration: 0.3 }} className="space-y-8">
{selectedDayPlan?.items?.map((activity: any, actIndex: number) => {
const activityKey = `day${selectedDayPlan.day}-act${actIndex}`;
return (
<motion.div
key={actIndex}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: actIndex * 0.08 }}
className="space-y-4"
>
{/* Time Label */}
<p className="font-poppins text-sm font-medium text-gray-500 text-center uppercase tracking-wider">
{activity.timeSlot}
</p>
{/* Activity Card */}
<Card className="overflow-hidden border border-gray-100 shadow-sm hover:shadow-lg transition-shadow duration-300 rounded-2xl">
<CardContent className="p-0">
{/* Image */}
<div className="relative h-56 md:h-64 bg-gray-200">
<ImageWithFallback
src={activity.imageUrl}
alt={activity.title}
className="w-full h-full object-cover"
/>
{/* TODO: Get Directions Badge redirect it to lat,long */}
<div className="absolute bottom-3 left-3">
<button className="flex items-center gap-1.5 bg-primary text-white px-4 py-2 rounded-full font-poppins text-xs font-semibold shadow-lg">
<MapPin className="w-3.5 h-3.5" />
Get Directions
</button>
</div>
</div>
{/* Content */}
<div className="p-5 space-y-3">
<h4 className="font-poppins text-lg font-semibold text-gray-900 leading-snug">
{activity.title}
</h4>
<p className="font-poppins text-sm font-normal text-gray-500 leading-relaxed">
{activity.locationName}
</p>
{/* Category Tags */}
<div className="flex flex-wrap gap-2">
{activity.categories?.map((cat: string, ci: number) => (
<span
key={ci}
className="font-poppins text-xs font-medium px-3 py-1.5 rounded-full border border-primary/20 text-primary bg-primary/5"
>
{cat}
</span>
))}
</div>
{/* Bullet Points */}
<div className="space-y-1.5 pt-1">
<div className="flex items-baseline gap-2">
<span className="text-primary flex-shrink-0 text-sm leading-relaxed"></span>
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">{activity.description}</span>
</div>
</div>
</div>
</CardContent>
</Card>
</motion.div>
);
})}
</motion.div>
</AnimatePresence>
)}
</div>
)}
{/* Summary View */}
{viewMode === 'summary' && (
<div className="space-y-6">
{days?.map((day: any, dayIndex: number) => {
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',
})
: '';
// ✅ Find the matching summary for this day
const daySummary = summaries.find((s: any) => s.dayNumber === day.dayNumber);
return (
<div key={dayIndex} className="border-l-4 border-primary/20 pl-5 space-y-3">
{/* Day Header */}
<div className="flex items-center justify-between">
<h3 className="font-poppins text-lg font-semibold text-gray-900">
Day {day.dayNumber}:
</h3>
<div className="flex items-center gap-1.5 text-primary">
<Calendar className="w-3.5 h-3.5" />
<span className="font-poppins text-sm font-medium">{dayDate}</span>
</div>
</div>
{/* Activity List */}
<div className="space-y-2">
{daySummary?.items?.map((item: any, actIndex: number) => {
const activityKey = `summary-day${day.dayNumber}-act${actIndex}`;
const isExpanded = selectedActivity === activityKey;
return (
<div key={actIndex} className="bg-gray-50 rounded-xl overflow-hidden">
<div
className="flex items-center justify-between px-4 py-3 cursor-pointer hover:bg-gray-100 transition-colors"
onClick={() =>
setSelectedActivity(isExpanded ? null : activityKey)
}
>
<p className="font-poppins text-sm font-medium text-gray-800">
{item.timeSlot}: {item.title}
</p>
<ChevronDown
className={`w-4 h-4 text-gray-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
/>
</div>
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
>
<div className="px-4 pb-4 space-y-2">
<div className="flex items-baseline gap-2">
<span className="text-primary flex-shrink-0 text-sm leading-relaxed"></span>
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
{item.description}
</span>
</div>
<button className="flex items-center gap-1.5 mt-2 bg-primary text-white px-4 py-2 rounded-full font-poppins text-xs font-semibold">
<MapPin className="w-3.5 h-3.5" />
Get directions
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
</div>
);
})}
</div>
)}
{/* Bottom Action */}
<div className="flex justify-center pt-4 pb-8">
<Button
onClick={() => navigate('/create-itinerary-design')}
className="w-full font-poppins font-semibold px-8 py-3 rounded-xl bg-primary hover:bg-primary/90 text-white shadow-md shadow-primary/20"
>
Create Another Itinerary
</Button>
</div>
</motion.div>
);
}
export default ItinerarySummaryPage