Compare commits
44 Commits
16c6161520
...
hemant
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54f33c2d34 | ||
| 103511eadb | |||
|
|
8a462b599e | ||
|
|
627137427d | ||
|
|
f01a5e0630 | ||
|
|
40ff761104 | ||
|
|
ff76d9a370 | ||
|
|
ecd2fb2719 | ||
|
|
c0a2c448e5 | ||
|
|
191aa3fd54 | ||
|
|
6a23429131 | ||
|
|
2c9bf7da83 | ||
|
|
8279715f2c | ||
|
|
37601dd51d | ||
|
|
16fe56913d | ||
|
|
a1a5c839dd | ||
|
|
d3bedeb56d | ||
|
|
cd7c6ffbaf | ||
|
|
cdfcc70e45 | ||
|
|
977eecd4d8 | ||
|
|
420d2038f2 | ||
|
|
27382c45e3 | ||
|
|
0e8045d9a2 | ||
| b3bb9f08cd | |||
|
|
b1fb01a2a6 | ||
|
|
ef1408fe2a | ||
|
|
23d1eaa43a | ||
|
|
cee671cf32 | ||
|
|
517a7c0446 | ||
|
|
0d83118938 | ||
|
|
0cb3d6c326 | ||
|
|
0c7667bf26 | ||
|
|
4c15fa597d | ||
|
|
617b494249 | ||
|
|
351c767104 | ||
|
|
61f8202fe1 | ||
|
|
ef15941484 | ||
|
|
8f3698b41f | ||
|
|
beae316bc0 | ||
|
|
adbf30a0c2 | ||
|
|
e3ee51f70b | ||
|
|
7aa9f833c0 | ||
|
|
37f50275a3 | ||
|
|
4c199f721a |
27
index.html
@@ -1,15 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CityCards Customer-web</title>
|
||||
</head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/png" href="/src/assets/citycards customer app.png" />
|
||||
<title>CityCards Customer-web</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
15
package-lock.json
generated
@@ -2257,6 +2257,7 @@
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-9.2.0.tgz",
|
||||
"integrity": "sha512-YSzLC0t6VS9MDdPTynSMqU8IxrItFUjkDORALFT6sSMR/XZ5Vgm3RDp/Gk7z727MC4A9s4MFVel0gF0c7+kdrg==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.16"
|
||||
}
|
||||
@@ -3097,6 +3098,7 @@
|
||||
"integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -3107,6 +3109,7 @@
|
||||
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -3117,6 +3120,7 @@
|
||||
"integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
@@ -3386,7 +3390,8 @@
|
||||
"version": "8.6.0",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/embla-carousel-react": {
|
||||
"version": "8.6.0",
|
||||
@@ -3981,6 +3986,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -4038,6 +4044,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -4064,6 +4071,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -4099,6 +4107,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
@@ -4301,7 +4310,8 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
@@ -4564,6 +4574,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
|
||||
@@ -185,7 +185,7 @@ function App() {
|
||||
</div>
|
||||
|
||||
{/* Card Title in Orange */}
|
||||
<p className="absolute font-poppins font-medium leading-[1.3] left-[50%] text-[#ffb23f] text-[24px] text-center top-[65px] tracking-[-0.96px] translate-x-[-50%] w-[202px]" style={{ fontVariationSettings: "'wdth' 100" }}>
|
||||
<p className="absolute font-appoppins font-medium leading-[1.3] left-[50%] text-[#ffb23f] text-[24px] text-center top-[65px] tracking-[-0.96px] translate-x-[-50%] w-[202px]" style={{ fontVariationSettings: "'wdth' 100" }}>
|
||||
{stickyCardType === 'unlimited' ? (
|
||||
<>Melbourne Unlimited Card</>
|
||||
) : (
|
||||
|
||||
@@ -6,17 +6,12 @@ import { MelbournePage } from './pages/MelbournePage';
|
||||
import { PassesPage } from './pages/PassesPage';
|
||||
import { AttractionsPage } from './pages/AttractionsPage';
|
||||
import { AttractionDetailsPage } from './pages/AttractionDetailsPage';
|
||||
import { CheckoutPage } from './pages/CheckoutPage';
|
||||
import { SecureCheckoutPage } from './pages/SecureCheckoutPage';
|
||||
import { BlogsPage } from './pages/BlogsPage';
|
||||
import { BlogDetailsPage } from './pages/BlogDetailsPage';
|
||||
import { HowItWorksPage } from './components/HowItWorksPage';
|
||||
import { FAQPage } from './components/FAQPage';
|
||||
import { PrivacyPolicyPage } from './pages/PrivacyPolicyPage';
|
||||
import { AboutUsPage } from './pages/AboutUsPage';
|
||||
import { ProfilePage } from './pages/ProfilePage';
|
||||
import { CreateMagicItineraryPage } from './pages/CreateMagicItineraryPage';
|
||||
import { ItineraryViewPage } from './pages/ItineraryViewPage';
|
||||
import { OffersPage } from './pages/OffersPage';
|
||||
import { CityCardsPage } from './pages/CityCardsPage';
|
||||
import { MagicItineraryPage } from './pages/MagicItineraryPage';
|
||||
@@ -24,7 +19,6 @@ import { PostCardsPage } from './pages/PostCardsPage';
|
||||
import { DownloadAppPage } from './pages/DownloadAppPage';
|
||||
import { HotelDiscountsPage } from './pages/HotelDiscountsPage';
|
||||
import { ContactUsPage } from './pages/ContactUsPage';
|
||||
|
||||
import { pageTransition } from './utils/animations';
|
||||
import { LandingPage } from './pages/landingPage';
|
||||
import ComingSoonPage from './pages/ComingSoonPage';
|
||||
@@ -34,11 +28,15 @@ import { LandingMagicItineraryPage } from './pages/LandingMagicItineraryPage';
|
||||
import { DiscoverPage } from './pages/DiscoverPage';
|
||||
import { CartPage } from './pages/CartPage';
|
||||
import { PaymentDetailsPage } from './pages/PaymentDetailsPage';
|
||||
import { CartPageDesign } from './pages/CartPageDesign';
|
||||
import { CheckoutPage2 } from './pages/CheckoutPage2';
|
||||
import { SuperSavingsDetailsPage } from './pages/SuperSavingsDetailsPage';
|
||||
import { ViewCardDetailsPage } from './pages/ViewCardDetailsPage';
|
||||
import ItinerarySummaryPage from './pages/ItinerarySummaryPage';
|
||||
import { PaymentSuccessPage } from './pages/PaymentSuccessPage';
|
||||
import { PaymentCancelPage } from './pages/PaymentCancelPage';
|
||||
import { ItineraryViewPage } from './pages/ItineraryViewPage';
|
||||
import { CheckoutPage } from './pages/CheckoutPage';
|
||||
import { CreateMagicItineraryPage } from './pages/CreateMagicIternaryPage';
|
||||
import RegisterPage from './components/RegisterPage';
|
||||
|
||||
// User type definition
|
||||
interface User {
|
||||
@@ -130,19 +128,6 @@ export function AppRouter({
|
||||
</motion.div>
|
||||
} />
|
||||
|
||||
{/* Checkout Routes */}
|
||||
{/* <Route path="/checkout" element={
|
||||
<motion.div key="checkout" {...pageTransition}>
|
||||
<CheckoutPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} /> */}
|
||||
|
||||
<Route path="/secure-checkout" element={
|
||||
<motion.div key="secure-checkout" {...pageTransition}>
|
||||
<SecureCheckoutPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} />
|
||||
|
||||
{/* Blog Routes */}
|
||||
<Route path="/blogs" element={
|
||||
<motion.div key="blogs" {...pageTransition}>
|
||||
@@ -196,6 +181,12 @@ export function AppRouter({
|
||||
<ProfilePage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} />
|
||||
<Route path="/view-card-details/:cardId" element={
|
||||
<motion.div key="profile" {...pageTransition}>
|
||||
<ViewCardDetailsPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} />
|
||||
|
||||
|
||||
{/* Itinerary Routes */}
|
||||
<Route path="/create-itinerary" element={
|
||||
@@ -204,11 +195,16 @@ export function AppRouter({
|
||||
</motion.div>
|
||||
} />
|
||||
|
||||
<Route path="/itinerary-view" element={
|
||||
<Route path="/view-itinerary/:itineraryId" element={
|
||||
<motion.div key="itinerary-view" {...pageTransition}>
|
||||
<ItineraryViewPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} />
|
||||
<Route path="/itinerary-summary/:itineraryId" element={
|
||||
<motion.div key="itinerary-summary" {...pageTransition}>
|
||||
<ItinerarySummaryPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} />
|
||||
|
||||
<Route path="/magic-itinerary" element={
|
||||
<motion.div key="magic-itinerary" {...pageTransition}>
|
||||
@@ -285,18 +281,19 @@ export function AppRouter({
|
||||
} />
|
||||
<Route path="/checkout" element={
|
||||
<motion.div key="super-savings" {...pageTransition}>
|
||||
<CheckoutPage2 {...commonNavHandlers} />
|
||||
<CheckoutPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} />
|
||||
<Route path="/cart-design" element={
|
||||
<motion.div key="super-savings" {...pageTransition}>
|
||||
<CartPageDesign {...commonNavHandlers} />
|
||||
<Route path="/register" element={
|
||||
<motion.div key="register" {...pageTransition}>
|
||||
<RegisterPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
} />
|
||||
|
||||
<Route path="/payment/:bookingId" element={
|
||||
<motion.div key="super-savings" {...pageTransition}>
|
||||
<PaymentDetailsPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
} />
|
||||
<Route path="/super-savings/:id" element={
|
||||
<motion.div key="super-savings" {...pageTransition}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { citiesApi } from "./services/cities.service";
|
||||
import { authApi } from "./services/auth.service";
|
||||
import { profileApi } from "./services/profile.service";
|
||||
import { cardsApi } from "./services/cards.service";
|
||||
import { itineraryApi } from "./services/itinerary.service";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@@ -11,7 +12,8 @@ export const store = configureStore({
|
||||
[citiesApi.reducerPath]: citiesApi.reducer,
|
||||
[authApi.reducerPath]: authApi.reducer,
|
||||
[profileApi.reducerPath]: profileApi.reducer,
|
||||
[cardsApi.reducerPath]:cardsApi.reducer
|
||||
[cardsApi.reducerPath]:cardsApi.reducer,
|
||||
[itineraryApi.reducerPath]:itineraryApi.reducer
|
||||
|
||||
},
|
||||
|
||||
@@ -21,7 +23,8 @@ export const store = configureStore({
|
||||
citiesApi.middleware,
|
||||
authApi.middleware,
|
||||
profileApi.middleware,
|
||||
cardsApi.middleware
|
||||
cardsApi.middleware,
|
||||
itineraryApi.middleware
|
||||
),
|
||||
});
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
|
||||
@@ -43,7 +43,7 @@ export const cardsApi = createApi({
|
||||
}),
|
||||
|
||||
payForCard: builder.mutation({
|
||||
query: (id) => ({
|
||||
query: (id) => ({
|
||||
url: `/website/passes/${id}/pay`,
|
||||
method: "POST",
|
||||
body: {},
|
||||
@@ -51,10 +51,9 @@ export const cardsApi = createApi({
|
||||
}),
|
||||
|
||||
confirmCardPayment: builder.mutation({
|
||||
query: (id) => ({
|
||||
url: `/website/passes/${id}/confirm-payment`,
|
||||
query: (payload: { id: string; checkoutSessionId: string }) => ({
|
||||
url: `/website/passes/${payload.id}/${payload.checkoutSessionId}/confirm-payment/`,
|
||||
method: "POST",
|
||||
// body: id,
|
||||
}),
|
||||
}),
|
||||
|
||||
|
||||
50
src/Redux/services/itinerary.service.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
import { baseQuery } from "../baseQuery";
|
||||
|
||||
export const itineraryApi = createApi({
|
||||
reducerPath: "itApi",
|
||||
baseQuery,
|
||||
|
||||
endpoints: (builder) => ({
|
||||
|
||||
createMagicItinerary: builder.mutation({
|
||||
query: (itineraryDetails) => ({ // keep the name of the variables being passed here same as when calling the mutation hook
|
||||
url: `/website/itinerary`,
|
||||
method: "POST",
|
||||
body: itineraryDetails
|
||||
}),
|
||||
|
||||
}),
|
||||
|
||||
getItineraryDetailsById: builder.query({
|
||||
query: (itineraryId: number) => `/website/itinerary/${itineraryId}`,
|
||||
}),
|
||||
|
||||
getUserItineraries: builder.query({
|
||||
query: (cityId) => {
|
||||
const params = new URLSearchParams()
|
||||
params.append('cityId', cityId);
|
||||
return `/website/itinerary/all-initineraries?${params.toString()}`
|
||||
}
|
||||
}),
|
||||
|
||||
downloadItinerary: builder.query<Blob, string>({
|
||||
query: (id) => ({
|
||||
url: `/mobile/itinerary/${id}/download`,
|
||||
method: 'GET',
|
||||
responseHandler: (response) => response.blob(),
|
||||
}),
|
||||
}),
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
export const {
|
||||
useCreateMagicItineraryMutation,
|
||||
useGetItineraryDetailsByIdQuery,
|
||||
useGetUserItinerariesQuery,
|
||||
useDownloadItineraryQuery,
|
||||
|
||||
|
||||
} = itineraryApi;
|
||||
@@ -24,16 +24,21 @@ export const profileApi = createApi({
|
||||
invalidatesTags: ["userDetails"]
|
||||
}),
|
||||
|
||||
getUserPasses: builder.query({
|
||||
query: ({ cardMode, sort }) => {
|
||||
getUserCards: builder.query({
|
||||
query: ({sort,cityId}) => {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if(cardMode) params.append('cardMode',cardMode);
|
||||
if(sort) params.append('sort',sort);
|
||||
params.append('cityXid', cityId);
|
||||
|
||||
if (sort) params.append('sort', sort);
|
||||
|
||||
return `/website/passes/all?${params.toString()}`
|
||||
}
|
||||
})
|
||||
}),
|
||||
|
||||
getUserCardDetails: builder.query({
|
||||
query: (cardId) => `/website/passes/${cardId}/details`,
|
||||
}),
|
||||
|
||||
})
|
||||
});
|
||||
@@ -41,5 +46,6 @@ export const profileApi = createApi({
|
||||
export const {
|
||||
useGetUserProfileDetailsQuery,
|
||||
useUpdateUserProfileDetailsMutation,
|
||||
useGetUserPassesQuery
|
||||
useGetUserCardsQuery,
|
||||
useGetUserCardDetailsQuery
|
||||
} = profileApi;
|
||||
BIN
src/assets/citycards customer app.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -45,6 +45,7 @@ export function CitySelectionDialog({
|
||||
navigate(`/${slugify(city.cityName)}`);
|
||||
localStorage.setItem("cityId", String(city.id))
|
||||
localStorage.setItem("cityName", String(city.cityName))
|
||||
sessionStorage.setItem("citySelected", String(city.cityName))
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ interface Testimonial {
|
||||
company: string;
|
||||
signature: string;
|
||||
}
|
||||
|
||||
const cityName = localStorage.getItem('cityName') || 'the city';
|
||||
const testimonials: Testimonial[] = [
|
||||
{
|
||||
id: 1,
|
||||
quote: "CityCards transformed our Melbourne trip into an unforgettable adventure. The seamless access to attractions and insider recommendations made every moment magical.",
|
||||
quote: `CityCards transformed our ${cityName} trip into an unforgettable adventure. The seamless access to attractions and insider recommendations made every moment magical.`,
|
||||
name: "Sarah Chen",
|
||||
company: "Travel Blogger",
|
||||
signature: "Sarah"
|
||||
|
||||
@@ -45,10 +45,10 @@ export function Footer({
|
||||
/>
|
||||
|
||||
{/* Enhanced White Gradient Overlay at Top */}
|
||||
<div className="absolute top-0 left-0 right-0 h-48 bg-gradient-to-b from-white via-white/95 via-white/80 via-white/60 via-white/40 to-transparent z-10" />
|
||||
<div className="absolute top-0 left-0 right-0 h-24 bg-gradient-to-b from-white via-white/95 via-white/80 via-white/60 via-white/40 to-transparent z-10" />
|
||||
|
||||
{/* Additional Smooth Transition Layer */}
|
||||
<div className="absolute top-0 left-0 right-0 h-24 bg-gradient-to-b from-white via-white/90 to-white/70 z-10" />
|
||||
<div className="absolute top-0 left-0 right-0 h-4 bg-gradient-to-b from-white via-white/90 to-white/70 z-10" />
|
||||
|
||||
{/* Dark overlay for text readability */}
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-black/30 to-black/70 z-20" />
|
||||
@@ -61,7 +61,7 @@ export function Footer({
|
||||
</div>
|
||||
|
||||
{/* Content Overlay */}
|
||||
<div className="relative z-30 py-24">
|
||||
<div className="relative z-30 py-4">
|
||||
<div className="container mx-auto px-4">
|
||||
{/* Footer Content Grid */}
|
||||
<div className="w-full mt-48 bg-primary/10 backdrop-blur-lg rounded-[10px] border border-white/10 p-12">
|
||||
|
||||
@@ -17,13 +17,13 @@ export function FooterBottom({ onPrivacyPolicyClick }: FooterBottomProps) {
|
||||
<div className="flex flex-col lg:flex-row justify-between items-center space-y-6 lg:space-y-0">
|
||||
{/* Copyright */}
|
||||
<p className="text-white/60 text-sm">
|
||||
© 2024 CityCards. All rights reserved.
|
||||
© 2026 CityCards. All rights reserved.
|
||||
</p>
|
||||
|
||||
{/* Right Section - Legal Links and Social Icons */}
|
||||
<div className="flex flex-col md:flex-row items-center space-y-4 md:space-y-0 md:space-x-8">
|
||||
{/* Legal Links */}
|
||||
<div className="flex space-x-6 text-sm">
|
||||
{/* <div className="flex space-x-6 text-sm">
|
||||
<motion.button
|
||||
onClick={onPrivacyPolicyClick}
|
||||
className="text-white/70 hover:text-white transition-colors duration-200"
|
||||
@@ -48,7 +48,7 @@ export function FooterBottom({ onPrivacyPolicyClick }: FooterBottomProps) {
|
||||
>
|
||||
Cookie Policy
|
||||
</motion.a>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Social Icons - Horizontal Layout */}
|
||||
<div className="flex space-x-3">
|
||||
|
||||
@@ -1,37 +1,21 @@
|
||||
import { motion } from 'motion/react';
|
||||
import { footerSections } from '../utils/footerConstants';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface FooterNavigationProps {
|
||||
onHomeClick?: () => void;
|
||||
onMelbourneClick?: () => void;
|
||||
onPassesClick?: () => void;
|
||||
onSignInClick?: () => void;
|
||||
onAttractionsClick?: () => void;
|
||||
onBlogsClick?: () => void;
|
||||
onHowItWorksClick?: () => void;
|
||||
onFAQClick?: () => void;
|
||||
onPrivacyPolicyClick?: () => void;
|
||||
onAboutUsClick?: () => void;
|
||||
onContactUsClick?: () => void;
|
||||
currentPage?: string;
|
||||
}
|
||||
const linkRoutes: Record<string, string> = {
|
||||
'Home': '/',
|
||||
// 'Cancellation policy': '/cancellation-policy',
|
||||
'How It Works': '/how-it-works',
|
||||
'FAQ': '/faq',
|
||||
'Blog': '/blogs',
|
||||
'Contact Us': '/contact-us',
|
||||
'Privacy Policy': '/privacy-policy',
|
||||
// 'Terms of Service': '/terms',
|
||||
};
|
||||
|
||||
export function FooterNavigation({
|
||||
onHomeClick,
|
||||
onMelbourneClick,
|
||||
onPassesClick,
|
||||
onSignInClick,
|
||||
onAttractionsClick,
|
||||
onBlogsClick,
|
||||
onHowItWorksClick,
|
||||
onFAQClick,
|
||||
onPrivacyPolicyClick,
|
||||
onAboutUsClick,
|
||||
onContactUsClick,
|
||||
currentPage
|
||||
}: FooterNavigationProps) {
|
||||
export function FooterNavigation() {
|
||||
return (
|
||||
<div className="lg:col-span-8 grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||
<div className="lg:col-span-8 grid grid-cols-2 md:grid-cols-3 gap-8">
|
||||
{Object.entries(footerSections).map(([key, section]) => (
|
||||
<motion.div
|
||||
key={key}
|
||||
@@ -45,50 +29,20 @@ export function FooterNavigation({
|
||||
}}
|
||||
>
|
||||
<h4 className="font-semibold text-white">{section.title}</h4>
|
||||
|
||||
<ul className="space-y-3">
|
||||
{section.links.map((link, index) => {
|
||||
const getClickHandler = () => {
|
||||
switch (link) {
|
||||
case 'Home': return onHomeClick;
|
||||
case 'Melbourne': return onMelbourneClick;
|
||||
case 'Passes': return onPassesClick;
|
||||
case 'Sign In': return onSignInClick;
|
||||
case 'Attractions': return onAttractionsClick;
|
||||
case 'Blog': return onBlogsClick;
|
||||
case 'How It Works': return onHowItWorksClick;
|
||||
case 'FAQ': return onFAQClick;
|
||||
case 'Privacy Policy': return onPrivacyPolicyClick;
|
||||
case 'Contact Us': return onContactUsClick;
|
||||
default: return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const clickHandler = getClickHandler();
|
||||
|
||||
return (
|
||||
<li key={link}>
|
||||
{clickHandler ? (
|
||||
<motion.button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
clickHandler();
|
||||
}}
|
||||
className="text-white/80 hover:text-white transition-colors duration-200 text-sm text-left"
|
||||
whileHover={{ x: 4 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{link}
|
||||
</motion.button>
|
||||
) : (
|
||||
<motion.span
|
||||
className="text-white/80 cursor-default text-sm"
|
||||
>
|
||||
{link}
|
||||
</motion.span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{section.links.map((link) => (
|
||||
<li key={link}>
|
||||
<motion.div whileHover={{ x: 4 }} transition={{ duration: 0.2 }}>
|
||||
<Link
|
||||
to={linkRoutes[link] || ""}
|
||||
className="text-white/80 hover:text-white transition-colors duration-200 text-sm"
|
||||
>
|
||||
{link}
|
||||
</Link>
|
||||
</motion.div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
@@ -19,13 +19,15 @@ export function HeroBannerCarousel({
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
|
||||
const cityName = localStorage.getItem("cityName")
|
||||
|
||||
const slides = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Discover",
|
||||
highlight: "Melbourne",
|
||||
highlight: cityName,
|
||||
subtitle: "Ultimate Guide to Iconic City",
|
||||
description: "From Flinders Street to St Kilda Beach: explore the best of Melbourne's landmarks, culture, food and more!",
|
||||
description: cityName === "Melbourne" ? "From Flinders Street to St Kilda Beach: explore the best of Melbourne's landmarks, culture, food and more!" : "From the Sydney Opera House to Bondi Beach: explore the best of Sydney’s landmarks, culture, food and more!",
|
||||
image: "https://images.unsplash.com/photo-1757470238279-0e9f331d02c9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBza3lsaW5lJTIwc3Vuc2V0fGVufDF8fHx8MTc2MDUwOTIyMHww&ixlib=rb-4.1.0&q=80&w=1080",
|
||||
cta: "Get Started",
|
||||
onClick: onCheckoutClick
|
||||
|
||||
@@ -7,6 +7,7 @@ interface HotelEsimOffersProps {
|
||||
}
|
||||
|
||||
export function HotelEsimOffers({ onEsimsClick, onHotelDiscountsClick }: HotelEsimOffersProps) {
|
||||
const cityName = localStorage.getItem("cityName")
|
||||
return (
|
||||
<div>
|
||||
<div className="space-y-0">
|
||||
@@ -64,7 +65,7 @@ export function HotelEsimOffers({ onEsimsClick, onHotelDiscountsClick }: HotelEs
|
||||
>
|
||||
<Wifi className="w-4 h-4 text-primary" />
|
||||
<span className="font-poppins text-sm font-medium text-primary">
|
||||
Stay Connected in Melbourne
|
||||
Stay Connected in {cityName}
|
||||
</span>
|
||||
</motion.div>
|
||||
<h2 className="font-poppins text-3xl md:text-5xl lg:text-6xl leading-tight text-foreground mb-6">
|
||||
@@ -72,7 +73,7 @@ export function HotelEsimOffers({ onEsimsClick, onHotelDiscountsClick }: HotelEs
|
||||
<span className="font-bold text-primary italic">Connected</span>
|
||||
</h2>
|
||||
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-700 max-w-2xl mx-auto">
|
||||
Get instant e-SIM connectivity across Australia. Stay online from the moment you land in Melbourne.
|
||||
Get instant e-SIM connectivity across Australia. Stay online from the moment you land in {cityName}.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -105,10 +106,10 @@ export function HotelEsimOffers({ onEsimsClick, onHotelDiscountsClick }: HotelEs
|
||||
<span className="font-poppins text-xs font-semibold text-white">🇦🇺 AUSTRALIA-WIDE COVERAGE</span>
|
||||
</div>
|
||||
<h3 className="font-poppins text-3xl md:text-4xl font-semibold text-white mb-4 leading-tight">
|
||||
Exclusive e-SIM Offers for Melbourne Visitors
|
||||
Exclusive e-SIM Offers for {cityName} Visitors
|
||||
</h3>
|
||||
<p className="font-poppins text-base text-white/90 mb-6">
|
||||
No more hunting for local SIM cards at the airport. Activate your e-SIM instantly and explore Melbourne with seamless connectivity.
|
||||
No more hunting for local SIM cards at the airport. Activate your e-SIM instantly and explore {cityName} with seamless connectivity.
|
||||
</p>
|
||||
|
||||
<motion.div
|
||||
@@ -207,7 +208,7 @@ export function HotelEsimOffers({ onEsimsClick, onHotelDiscountsClick }: HotelEs
|
||||
>
|
||||
<Hotel className="w-4 h-4 text-primary" />
|
||||
<span className="font-poppins text-sm font-medium text-primary">
|
||||
Premium Melbourne Hotels
|
||||
Premium {cityName} Hotels
|
||||
</span>
|
||||
</motion.div>
|
||||
<h2 className="font-poppins text-3xl md:text-5xl lg:text-6xl leading-tight text-foreground mb-6">
|
||||
@@ -215,7 +216,7 @@ export function HotelEsimOffers({ onEsimsClick, onHotelDiscountsClick }: HotelEs
|
||||
<span className="font-bold text-primary italic">Luxury</span>
|
||||
</h2>
|
||||
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-700 max-w-2xl mx-auto">
|
||||
Unlock exclusive rates at Melbourne's finest hotels. Your CityCard membership opens doors to premium CBD and waterfront stays.
|
||||
Unlock exclusive rates at {cityName}'s finest hotels. Your CityCard membership opens doors to premium CBD and waterfront stays.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -236,10 +237,10 @@ export function HotelEsimOffers({ onEsimsClick, onHotelDiscountsClick }: HotelEs
|
||||
<span className="font-poppins text-xs font-semibold text-white">🔥 MARRIOTT BONVOY PARTNER</span>
|
||||
</div>
|
||||
<h3 className="font-poppins text-3xl md:text-4xl font-semibold text-foreground mb-4 leading-tight">
|
||||
Melbourne Premium Stays at <span className="text-primary italic">Unbeatable Prices</span>
|
||||
{cityName} Premium Stays at <span className="text-primary italic">Unbeatable Prices</span>
|
||||
</h3>
|
||||
<p className="font-poppins text-base text-gray-700 mb-6">
|
||||
Access exclusive member rates at Melbourne's top hotels including Crown Towers, W Melbourne, and premium CBD properties. Enjoy complimentary upgrades and special amenities.
|
||||
Access exclusive member rates at {cityName}'s top hotels including Crown Towers, W {cityName}, and premium CBD properties. Enjoy complimentary upgrades and special amenities.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -253,7 +254,7 @@ export function HotelEsimOffers({ onEsimsClick, onHotelDiscountsClick }: HotelEs
|
||||
<div className="bg-gradient-to-br from-primary to-orange-500 rounded-3xl p-8 text-center shadow-xl">
|
||||
<div className="font-poppins text-6xl font-bold text-white mb-2">25%</div>
|
||||
<div className="font-poppins text-lg text-white/90 mb-1">Average Savings</div>
|
||||
<div className="font-poppins text-sm text-white/70">on Melbourne hotels</div>
|
||||
<div className="font-poppins text-sm text-white/70">on {cityName} hotels</div>
|
||||
</div>
|
||||
<motion.div
|
||||
className="absolute -top-2 -right-2 bg-yellow-400 rounded-full px-3 py-1"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Label } from './ui/label';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useLoginMutation, useVerifyOtpMutation } from '../Redux/services/auth.service';
|
||||
import { toast } from 'sonner';
|
||||
import { RegisterModal } from './RegisterModal';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface LoginModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -21,9 +21,9 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [helperText, setHelperText] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [showRegisterModal, setShowRegisterModal] = useState(false);
|
||||
|
||||
const { login } = useAuth();
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [sendOtp, { isLoading: isSendingOtp }] = useLoginMutation();
|
||||
const [verifyOtp, { isLoading: isVerifying }] = useVerifyOtpMutation();
|
||||
@@ -147,15 +147,21 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
|
||||
otp: otpString
|
||||
}).unwrap();
|
||||
|
||||
const userData = {
|
||||
userId: response?.user?.id,
|
||||
email: response?.email || email,
|
||||
name: response?.name || email.split('@')[0].charAt(0).toUpperCase() + email.split('@')[0].slice(1),
|
||||
accessToken: response?.accessToken,
|
||||
};
|
||||
|
||||
if (!response?.userExists) {
|
||||
localStorage.setItem("userEmail",email)
|
||||
navigate("/register")
|
||||
} else {
|
||||
const userData = {
|
||||
userId: response?.user?.id,
|
||||
email: response?.email || email,
|
||||
name: response?.name || email.split('@')[0].charAt(0).toUpperCase() + email.split('@')[0].slice(1),
|
||||
accessToken: response?.accessToken,
|
||||
};
|
||||
|
||||
login(userData);
|
||||
toast.success("User Logged in successfully")
|
||||
login(userData);
|
||||
toast.success("User Logged in successfully")
|
||||
}
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
setError(err?.data?.message || 'Invalid OTP. Please try again.');
|
||||
@@ -232,14 +238,7 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
|
||||
>
|
||||
{isSendingOtp ? 'Sending OTP...' : 'Send OTP'}
|
||||
</Button>
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={() => setShowRegisterModal(true)}
|
||||
className="font-poppins text-sm text-gray-600 hover:text-gray-800 transition-colors cursor-pointer"
|
||||
>
|
||||
Don't have an account? <span className="text-primary font-semibold">Register</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
@@ -314,15 +313,6 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
|
||||
)
|
||||
}
|
||||
</AnimatePresence >
|
||||
<RegisterModal
|
||||
isOpen={showRegisterModal}
|
||||
onClose={() => setShowRegisterModal(false)}
|
||||
onLoginClick={() => {
|
||||
setShowRegisterModal(false);
|
||||
setStep('email');
|
||||
setEmail('');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
||||
import { ChevronLeft, ChevronRight, Clock, Users, Star, Zap, CheckCircle, MapPin, Volume2, Camera, Coffee, Palette, Eye } from 'lucide-react';
|
||||
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||
import { motion } from 'motion/react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const melbourneAttractions = [
|
||||
{
|
||||
@@ -146,6 +147,8 @@ const categories = ["All", "Landmarks", "Gardens", "Markets", "Views", "Beach",
|
||||
|
||||
export function MelbourneAttractions() {
|
||||
const [activeCategory, setActiveCategory] = useState("All");
|
||||
const navigate = useNavigate();
|
||||
const cityName = localStorage.getItem("cityName")
|
||||
|
||||
const filteredAttractions = activeCategory === "All"
|
||||
? melbourneAttractions
|
||||
@@ -276,19 +279,19 @@ export function MelbourneAttractions() {
|
||||
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-primary/10 to-secondary/10 px-4 py-2 rounded-full mb-6">
|
||||
<div className="w-2 h-2 bg-gradient-to-r from-primary to-secondary rounded-full"></div>
|
||||
<span className="text-sm font-medium bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
||||
Melbourne Must-Sees
|
||||
{cityName} Must-Sees
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="heading-dynamic text-4xl md:text-5xl lg:text-6xl text-gray-900 mb-4">
|
||||
<span className="font-light">Discover</span>{' '}
|
||||
<span className="font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent italic pr-1">
|
||||
Melbourne's
|
||||
{cityName}'s
|
||||
</span>{' '}
|
||||
<span className="font-normal">Best</span>{' '}
|
||||
<span className="font-semibold text-emphasis">Experiences</span>
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Discover Melbourne's iconic landmarks, vibrant culture, world-class dining, and hidden gems - all included with your Melbourne CityCard
|
||||
Discover {cityName}'s iconic landmarks, vibrant culture, world-class dining, and hidden gems - all included with your {cityName} CityCard
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -382,8 +385,9 @@ export function MelbourneAttractions() {
|
||||
whileHover={{ scale: 1.05, boxShadow: "0 20px 40px rgba(99,102,241,0.3)" }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="relative bg-gradient-to-r from-primary to-secondary text-white py-4 px-12 rounded-lg text-lg shadow-xl transition-all duration-300 overflow-hidden group"
|
||||
onClick={()=>navigate('/passes')}
|
||||
>
|
||||
<span className="relative z-10">Get Your Melbourne Card</span>
|
||||
<span className="relative z-10">Get Your {cityName} Card</span>
|
||||
|
||||
{/* Shine animation */}
|
||||
<div className="absolute inset-0 opacity-30">
|
||||
|
||||
@@ -2,6 +2,8 @@ import { motion } from 'motion/react';
|
||||
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||
import { Calendar, Clock, User, ArrowRight, Coffee, Camera, MapPin, Star } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { useRef } from "react";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const blogPosts = [
|
||||
{
|
||||
@@ -46,7 +48,7 @@ const blogPosts = [
|
||||
excerpt: "From the iconic MCG to Formula 1 racing, discover why Melbourne holds the title of Australia's sporting capital and home to major international events.",
|
||||
image: "https://images.unsplash.com/photo-1720347247737-9252d85d3027?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBjaXR5JTIwc2t5bGluZSUyMGZsaW5kZXJzJTIwc3RyZWV0fGVufDF8fHx8MTc1NzMzOTAyNHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
|
||||
author: "Sports Fan",
|
||||
date: "Dec 8, 2024",
|
||||
date: "Dec 8, 2024",
|
||||
readTime: "6 min read",
|
||||
category: "Sports",
|
||||
featured: false,
|
||||
@@ -88,12 +90,19 @@ const categories = [
|
||||
];
|
||||
|
||||
export function MelbourneBlogs() {
|
||||
|
||||
const sectionRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const featuredPost = blogPosts.find(post => post.featured);
|
||||
const regularPosts = blogPosts.filter(post => !post.featured);
|
||||
|
||||
const cityName = localStorage.getItem('cityName');
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-gradient-to-br from-gray-50 via-white to-gray-50 relative overflow-hidden">
|
||||
{/* Background Pattern */}
|
||||
<section
|
||||
ref={sectionRef}
|
||||
className="py-20 bg-gradient-to-br from-gray-50 via-white to-gray-50 relative overflow-hidden"
|
||||
> {/* Background Pattern */}
|
||||
<div className="absolute inset-0 opacity-[0.02]">
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-gradient-to-br from-primary/20 via-secondary/20 to-primary/20"></div>
|
||||
</div>
|
||||
@@ -110,21 +119,21 @@ export function MelbourneBlogs() {
|
||||
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-primary/10 to-secondary/10 px-4 py-2 rounded-full mb-6">
|
||||
<div className="w-2 h-2 bg-gradient-to-r from-primary to-secondary rounded-full"></div>
|
||||
<span className="text-sm font-medium bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
||||
Melbourne Stories
|
||||
{cityName} Stories
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<h2 className="font-merchant text-4xl md:text-5xl lg:text-6xl text-gray-900 mb-6">
|
||||
<span className="font-normal">Melbourne</span>{' '}
|
||||
<span className="font-normal">{cityName}</span>{' '}
|
||||
<span className="font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent italic pr-2">
|
||||
Blogs
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
|
||||
<p className="text-xl text-gray-600 max-w-4xl mx-auto leading-relaxed">
|
||||
Dive deep into Melbourne's rich cultural tapestry, from hidden laneway treasures to world-renowned
|
||||
coffee culture. Discover insider stories, local secrets, and expert guides to Australia's cultural capital
|
||||
that will transform your Melbourne experience into an unforgettable journey.
|
||||
Dive deep into {cityName}'s rich cultural tapestry, from hidden laneway treasures to world-renowned
|
||||
coffee culture. Discover insider stories, local secrets, and expert guides to Australia's cultural capital
|
||||
that will transform your {cityName} experience into an unforgettable journey.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -189,7 +198,7 @@ export function MelbourneBlogs() {
|
||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
|
||||
{/* Category Badge */}
|
||||
<div className="absolute top-4 left-4 bg-white/95 backdrop-blur-sm text-gray-900 px-3 py-1 rounded-full text-xs font-medium">
|
||||
{post.category}
|
||||
@@ -217,7 +226,7 @@ export function MelbourneBlogs() {
|
||||
<h3 className="font-merchant text-xl font-semibold text-gray-900 mb-3 leading-tight group-hover:text-primary transition-colors duration-200 line-clamp-2">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
|
||||
<p className="text-gray-600 leading-relaxed mb-4 text-sm flex-1 line-clamp-3">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
@@ -261,23 +270,30 @@ export function MelbourneBlogs() {
|
||||
>
|
||||
<div className="bg-gradient-to-br from-primary/5 via-secondary/5 to-primary/5 rounded-3xl p-8 md:p-12 border border-gray-100">
|
||||
<h3 className="font-merchant text-2xl md:text-3xl font-semibold text-gray-900 mb-4">
|
||||
Want to explore Melbourne yourself?
|
||||
Want to explore {cityName} yourself?
|
||||
</h3>
|
||||
<p className="text-gray-600 text-lg mb-8 max-w-2xl mx-auto">
|
||||
Get your Melbourne CityCard and unlock access to all these incredible experiences and more.
|
||||
Get your {cityName} CityCard and unlock access to all these incredible experiences and more.
|
||||
Start your adventure today with exclusive deals and insider access.
|
||||
</p>
|
||||
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button
|
||||
className="bg-gradient-to-r from-primary to-secondary text-white font-semibold px-8 py-4 rounded-2xl hover:scale-105 transition-all duration-300 shadow-lg hover:shadow-xl"
|
||||
onClick={() => { navigate(`/${cityName.toLowerCase()}`), window.scrollTo({ top: 0, behavior: 'smooth' }); }}
|
||||
>
|
||||
<MapPin className="w-5 h-5 mr-2" />
|
||||
Explore Melbourne
|
||||
Explore {cityName}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-2 border-gray-300 text-gray-700 font-semibold px-8 py-4 rounded-2xl hover:border-primary hover:text-primary hover:scale-105 transition-all duration-300"
|
||||
onClick={() => {
|
||||
sectionRef.current?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Coffee className="w-5 h-5 mr-2" />
|
||||
View All Blogs
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { Check, X, Star, Users, MapPin, Calendar, Clock, Zap, Eye } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { motion } from 'motion/react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
// const cardOptions = [
|
||||
// {
|
||||
@@ -76,7 +77,10 @@ interface MelbourneCardComparisonProps {
|
||||
|
||||
export function MelbourneCardComparison({ onCheckoutClick, cards }: MelbourneCardComparisonProps) {
|
||||
const [selectedCard, setSelectedCard] = useState<string>('unlimited');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const cityName=localStorage.getItem('cityName');
|
||||
|
||||
const cardOptions = [
|
||||
{
|
||||
id: cards[0]?.id,
|
||||
@@ -179,9 +183,18 @@ export function MelbourneCardComparison({ onCheckoutClick, cards }: MelbourneCar
|
||||
</h2>
|
||||
|
||||
<p className="text-xl text-gray-600 max-w-4xl mx-auto leading-relaxed">
|
||||
Melbourne is a must-visit cultural epicenter, and this spectacular trip unlocks
|
||||
your access around the city in one easy. Save over the cost of visiting Melbourne's
|
||||
landmarks, have lunch at Phi Phi Leh, snorkel at Bamboo Island, and visit Monkey Beach.
|
||||
{cityName === 'Melbourne' && (
|
||||
<span>
|
||||
Melbourne is a must-visit cultural epicenter, and this spectacular trip unlocks
|
||||
your access around the city in one easy. Save over the cost of visiting Melbourne's
|
||||
landmarks, have lunch at Phi Phi Leh, snorkel at Bamboo Island, and visit Monkey Beach.
|
||||
</span>
|
||||
)}
|
||||
{cityName === 'Sydney' && (
|
||||
<span>
|
||||
Sydney is a dazzling harbor city that blends iconic landmarks with vibrant coastal escapes. This unforgettable trip gives you seamless access across Sydney in one easy pass. Save on the cost of visiting Sydney’s world‑famous attractions, cruise past the Sydney Opera House and Harbour Bridge, relax on Bondi Beach, snorkel at Manly, and explore the wildlife at Taronga Zoo.
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -255,7 +268,7 @@ export function MelbourneCardComparison({ onCheckoutClick, cards }: MelbourneCar
|
||||
withShine={true}
|
||||
className="w-full h-14 rounded-2xl text-white font-semibold text-lg hover:scale-105 transition-all duration-300 shadow-lg hover:shadow-xl cursor-pointer"
|
||||
style={{ backgroundColor: '#F95F62' }}
|
||||
onClick={onCheckoutClick}
|
||||
onClick={()=>navigate("/passes")}
|
||||
>
|
||||
Buy {card.name}
|
||||
</Button>
|
||||
|
||||
@@ -18,59 +18,61 @@ import {
|
||||
AccordionTrigger,
|
||||
} from "./ui/accordion";
|
||||
|
||||
const cityName = localStorage.getItem('cityName') || 'the city';
|
||||
|
||||
const faqData = [
|
||||
{
|
||||
id: "refund",
|
||||
question: "Can I get a refund on my Melbourne CityCard?",
|
||||
answer: "Yes, you can cancel your Melbourne CityCard and receive a full refund if you cancel at least 24 hours in advance of your selected start date. For cancellations within 24 hours, refunds are subject to our cancellation policy. Digital cards can be refunded instantly through your account.",
|
||||
icon: CreditCard
|
||||
},
|
||||
// {
|
||||
// id: "refund",
|
||||
// question: "Can I get a refund on my Melbourne CityCard?",
|
||||
// answer: "Yes, you can cancel your Melbourne CityCard and receive a full refund if you cancel at least 24 hours in advance of your selected start date. For cancellations within 24 hours, refunds are subject to our cancellation policy. Digital cards can be refunded instantly through your account.",
|
||||
// icon: CreditCard
|
||||
// },
|
||||
{
|
||||
id: "duration",
|
||||
question: "How long is my Melbourne CityCard valid?",
|
||||
answer: "Melbourne CityCards are available in 1, 2, 3, and 5-day options. Your card activates on the first attraction you visit and is valid for consecutive days only. The 5-day Melbourne Unlimited Card provides the best value for extended stays with access to over 40 premium attractions.",
|
||||
question: `How long is my ${cityName} CityCard valid?`,
|
||||
answer: `${cityName} CityCards are available in 1, 2, 3, and 5-day options. Your card activates on the first attraction you visit and is valid for consecutive days only. The 5-day ${cityName} Unlimited Card provides the best value for extended stays with access to over 40 premium attractions.`,
|
||||
icon: Calendar
|
||||
},
|
||||
{
|
||||
id: "transportation",
|
||||
question: "Does the Melbourne CityCard include public transport?",
|
||||
answer: "The Melbourne Unlimited Card includes a complimentary Myki card loaded with travel credit for trams, trains, and buses within Melbourne's CBD and inner suburbs. The Selective Card focuses on attractions only, but we provide detailed transport guides for each venue.",
|
||||
question: `Does the ${cityName} CityCard include public transport?`,
|
||||
answer: `The ${cityName} Unlimited Card includes a complimentary Myki card loaded with travel credit for trams, trains, and buses within ${cityName}'s CBD and inner suburbs. The Selective Card focuses on attractions only, but we provide detailed transport guides for each venue.`,
|
||||
icon: Train
|
||||
},
|
||||
{
|
||||
id: "attractions",
|
||||
question: "What are the must-visit attractions included with my card?",
|
||||
answer: "Your Melbourne CityCard includes iconic experiences like Eureka Tower SkyDeck, Royal Botanic Gardens tours, Melbourne Zoo, SEA LIFE Melbourne Aquarium, and Melbourne Star observation wheel. Plus unique local experiences like laneways art tours, coffee culture walks, and rooftop dining discounts.",
|
||||
question: `What are the must-visit attractions included with my ${cityName} CityCard?`,
|
||||
answer: `Your ${cityName} CityCard includes iconic experiences like Eureka Tower SkyDeck, Royal Botanic Gardens tours, ${cityName} Zoo, SEA LIFE ${cityName} Aquarium, and ${cityName} Star observation wheel. Plus unique local experiences like laneways art tours, coffee culture walks, and rooftop dining discounts.`,
|
||||
icon: Camera
|
||||
},
|
||||
{
|
||||
id: "best-time",
|
||||
question: "When is the best time to visit Melbourne?",
|
||||
answer: "Melbourne is fantastic year-round! Spring (Sep-Nov) offers perfect weather and blooming gardens. Summer (Dec-Feb) brings outdoor festivals and rooftop season. Autumn (Mar-May) showcases beautiful foliage and harvest events. Winter (Jun-Aug) is ideal for cozy cafes, indoor attractions, and cultural experiences.",
|
||||
question: `When is the best time to visit ${cityName}?`,
|
||||
answer: `${cityName} is fantastic year-round! Spring (Sep-Nov) offers perfect weather and blooming gardens. Summer (Dec-Feb) brings outdoor festivals and rooftop season. Autumn (Mar-May) showcases beautiful foliage and harvest events. Winter (Jun-Aug) is ideal for cozy cafes, indoor attractions, and cultural experiences.`,
|
||||
icon: Clock
|
||||
},
|
||||
{
|
||||
id: "coffee-culture",
|
||||
question: "How can I experience Melbourne's famous coffee culture?",
|
||||
answer: "Your Melbourne CityCard includes guided coffee tours through famous laneways, visits to historic coffee roasters, and discounts at award-winning cafes. We've partnered with local baristas to offer exclusive tastings and behind-the-scenes experiences at Melbourne's most beloved coffee institutions.",
|
||||
question: `How can I experience ${cityName}'s famous coffee culture?`,
|
||||
answer: `Your ${cityName} CityCard includes guided coffee tours through famous laneways, visits to historic coffee roasters, and discounts at award-winning cafes. We've partnered with local baristas to offer exclusive tastings and behind-the-scenes experiences at ${cityName}'s most beloved coffee institutions.`,
|
||||
icon: Coffee
|
||||
},
|
||||
{
|
||||
id: "group-bookings",
|
||||
question: "Do you offer group discounts for families or friends?",
|
||||
answer: "Yes! Groups of 4+ receive automatic discounts, and families with children under 16 get special pricing. School groups and corporate bookings receive additional benefits. Contact our Melbourne team for custom packages that can include private tours and exclusive venue access.",
|
||||
question: `Do you offer group discounts for families or friends?`,
|
||||
answer: `Yes! Groups of 4+ receive automatic discounts, and families with children under 16 get special pricing. School groups and corporate bookings receive additional benefits. Contact our ${cityName} team for custom packages that can include private tours and exclusive venue access.`,
|
||||
icon: Users
|
||||
},
|
||||
{
|
||||
id: "mobile-app",
|
||||
question: "Do I need the mobile app to use my Melbourne CityCard?",
|
||||
answer: "While not required, our mobile app enhances your Melbourne experience with interactive maps, real-time attraction wait times, insider tips from locals, and the ability to skip lines at participating venues. Download it for offline access to your itinerary and exclusive app-only deals.",
|
||||
question: `Do I need the mobile app to use my ${cityName} CityCard?`,
|
||||
answer: `While not required, our mobile app enhances your ${cityName} experience with interactive maps, real-time attraction wait times, insider tips from locals, and the ability to skip lines at participating venues. Download it for offline access to your itinerary and exclusive app-only deals.`,
|
||||
icon: Smartphone
|
||||
},
|
||||
{
|
||||
id: "neighborhoods",
|
||||
question: "Which Melbourne neighborhoods should I explore?",
|
||||
answer: "Your CityCard provides access to experiences across Melbourne's diverse neighborhoods: Fitzroy for street art and vintage shopping, St. Kilda for beaches and nightlife, Southbank for dining and culture, CBD for iconic attractions, and Richmond for authentic Vietnamese food and shopping.",
|
||||
question: `Which ${cityName} neighborhoods should I explore?`,
|
||||
answer: `Your CityCard provides access to experiences across ${cityName}'s diverse neighborhoods: Fitzroy for street art and vintage shopping, St. Kilda for beaches and nightlife, Southbank for dining and culture, CBD for iconic attractions, and Richmond for authentic Vietnamese food and shopping.`,
|
||||
icon: MapPin
|
||||
}
|
||||
];
|
||||
@@ -95,7 +97,7 @@ export function MelbourneFAQ() {
|
||||
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-primary/10 to-secondary/10 px-4 py-2 rounded-full mb-6">
|
||||
<HelpCircle className="w-4 h-4 text-primary" />
|
||||
<span className="text-sm font-medium bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
||||
Melbourne Guide
|
||||
{cityName} Guide
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -107,8 +109,8 @@ export function MelbourneFAQ() {
|
||||
</h2>
|
||||
|
||||
<p className="text-xl text-gray-600 max-w-4xl mx-auto leading-relaxed">
|
||||
Everything you need to know about exploring Melbourne with your CityCard. From iconic attractions
|
||||
to hidden local gems, we've got your Melbourne adventure covered.
|
||||
Everything you need to know about exploring {cityName} with your CityCard. From iconic attractions
|
||||
to hidden local gems, we've got your {cityName} adventure covered.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -161,7 +163,7 @@ export function MelbourneFAQ() {
|
||||
</div>
|
||||
|
||||
{/* Call to Action */}
|
||||
<motion.div
|
||||
{/* <motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
@@ -205,7 +207,7 @@ export function MelbourneFAQ() {
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div> */}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ export function MelbourneTourOverview() {
|
||||
}
|
||||
];
|
||||
|
||||
const tourHighlights = [
|
||||
const MelbourneTourHighlights = [
|
||||
{
|
||||
icon: Star,
|
||||
text: "Experience the cultural capital's vibrant arts scene and hidden laneways",
|
||||
@@ -69,6 +69,43 @@ export function MelbourneTourOverview() {
|
||||
}
|
||||
];
|
||||
|
||||
const SydneyTourHighlights = [
|
||||
{
|
||||
icon: Star,
|
||||
text: "Experience Sydney’s iconic harbour lifestyle with Opera House and Harbour Bridge views",
|
||||
color: "text-yellow-600"
|
||||
},
|
||||
{
|
||||
icon: Coffee,
|
||||
text: "Discover Sydney’s vibrant café culture, waterfront dining, and buzzing food markets",
|
||||
color: "text-amber-600"
|
||||
},
|
||||
{
|
||||
icon: Camera,
|
||||
text: "Capture panoramic views from Sydney Tower Eye and scenic harbour cruises",
|
||||
color: "text-purple-600"
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
text: "Enjoy beachside vibes at Bondi and Manly, plus lively nightlife in Darling Harbour",
|
||||
color: "text-green-600"
|
||||
},
|
||||
{
|
||||
icon: MapPin,
|
||||
text: "Explore historic charm in The Rocks and coastal walks like Bondi to Coogee",
|
||||
color: "text-blue-600"
|
||||
},
|
||||
{
|
||||
icon: Calendar,
|
||||
text: "Access year-round festivals, cultural events, and dynamic harbour celebrations",
|
||||
color: "text-rose-600"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
const cityName = localStorage.getItem("cityName")
|
||||
const selectedHighlights = cityName === 'Melbourne' ? MelbourneTourHighlights : SydneyTourHighlights;
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-gradient-to-br from-white via-gray-50/30 to-white relative overflow-hidden">
|
||||
{/* Background Pattern */}
|
||||
@@ -88,7 +125,7 @@ export function MelbourneTourOverview() {
|
||||
className="mb-16"
|
||||
>
|
||||
<h2 className="heading-dynamic font-merchant text-4xl md:text-5xl lg:text-6xl text-gray-900 mb-8">
|
||||
<span className="font-light">Melbourne</span>{' '}
|
||||
<span className="font-light">{cityName}</span>{' '}
|
||||
<span className="font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent italic pr-2">
|
||||
Tour
|
||||
</span>{' '}
|
||||
@@ -108,11 +145,20 @@ export function MelbourneTourOverview() {
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<p className="text-lg md:text-xl text-gray-700 leading-relaxed">
|
||||
Melbourne is a must-visit cultural epicenter, and this spectacular experience unlocks
|
||||
your access around the city in one easy pass. Save over the cost of visiting Melbourne's
|
||||
landmarks, explore famous laneways and street art, enjoy world-class dining in hidden bars,
|
||||
and immerse yourself in the sports capital's vibrant atmosphere. From Royal Botanic Gardens
|
||||
to Federation Square, hotel pickup and drop-off all included.
|
||||
{ cityName === 'Melbourne' && (
|
||||
<span>
|
||||
Melbourne is a must-visit cultural epicenter, and this spectacular experience unlocks
|
||||
your access around the city in one easy pass. Save over the cost of visiting Melbourne's
|
||||
landmarks, explore famous laneways and street art, enjoy world-class dining in hidden bars,
|
||||
and immerse yourself in the sports capital's vibrant atmosphere. From Royal Botanic Gardens
|
||||
to Federation Square, hotel pickup and drop-off all included.
|
||||
</span>
|
||||
)}
|
||||
{ cityName === 'Sydney' && (
|
||||
<span>
|
||||
Sydney is a must-visit global destination, blending iconic landmarks with vibrant coastal charm, and this all-in-one experience gives you seamless access across the city. Save more while exploring Sydney’s top attractions, wander through historic neighborhoods like The Rocks, admire world-famous sights such as the Sydney Opera House and Harbour Bridge, and relax along stunning beaches like Bondi and Manly. Enjoy diverse dining from waterfront restaurants to hidden cafés, soak in the lively cultural scene, and experience the energy of Australia’s most dynamic harbor city—all with convenient access to key locations and experiences included.
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
@@ -226,7 +272,7 @@ export function MelbourneTourOverview() {
|
||||
</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
{tourHighlights.map((highlight, index) => (
|
||||
{selectedHighlights.map((highlight, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
||||
@@ -2,12 +2,10 @@ 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 { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
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';
|
||||
import melbourneLogo from '../assets/melbourne-logo.png';
|
||||
import { CitySelectionDialog, slugify } from './CitySelectionDialog';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { LoginModal } from './LoginModal';
|
||||
@@ -62,15 +60,12 @@ interface NavigationItem {
|
||||
export default function Navbar({
|
||||
activeCity,
|
||||
onCityChange,
|
||||
onSignInClick,
|
||||
onSignOutClick,
|
||||
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 [activeCityDropdown, setActiveCityDropdown] = useState(false);
|
||||
const [isCityDialogOpen, setIsCityDialogOpen] = useState(false);
|
||||
@@ -79,7 +74,6 @@ export default function Navbar({
|
||||
const [dialogSource, setDialogSource] = useState<'navbar' | 'cta'>('navbar');
|
||||
|
||||
const languageRef = useRef<HTMLDivElement>(null);
|
||||
const cartRef = useRef<HTMLDivElement>(null);
|
||||
const userRef = useRef<HTMLDivElement>(null);
|
||||
const cityRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -96,7 +90,7 @@ export default function Navbar({
|
||||
const cityName = localStorage.getItem("cityName")
|
||||
|
||||
// const citySelected = location.pathname.includes(slugify(cityName) || "")
|
||||
const citySelected = cityName
|
||||
const citySelected = sessionStorage.getItem("citySelected")
|
||||
|
||||
const baseUrl = import.meta.env.VITE_BASE_URL;
|
||||
|
||||
@@ -137,15 +131,6 @@ export default function Navbar({
|
||||
path: '/whats-included',
|
||||
isShared: false
|
||||
},
|
||||
// Position 4 - Shared item
|
||||
// {
|
||||
// label: 'Your Card',
|
||||
// path: '/passes',
|
||||
// isShared: true,
|
||||
// landingLabel: 'Your Card',
|
||||
// melbourneLabel: 'Your Card'
|
||||
// },
|
||||
// Position 5
|
||||
{
|
||||
label: 'FAQ',
|
||||
path: '/faq',
|
||||
@@ -159,7 +144,7 @@ export default function Navbar({
|
||||
melbourneLabel: 'Your Postcard'
|
||||
}
|
||||
],
|
||||
melbourne: [
|
||||
citySelected: [
|
||||
// Position 1
|
||||
{
|
||||
label: 'Attractions',
|
||||
@@ -188,11 +173,11 @@ export default function Navbar({
|
||||
},
|
||||
// Position 5 - Shared item
|
||||
{
|
||||
label: 'Your Card',
|
||||
label: 'Buy Cards',
|
||||
path: `/passes`,
|
||||
isShared: true,
|
||||
landingLabel: 'Your Card',
|
||||
melbourneLabel: 'Your Card'
|
||||
landingLabel: 'Buy Cards',
|
||||
melbourneLabel: 'Buy Cards'
|
||||
},
|
||||
{
|
||||
label: 'Your Postcard',
|
||||
@@ -240,20 +225,20 @@ export default function Navbar({
|
||||
}, [location.pathname]);
|
||||
|
||||
// ✅ Determine which navbar to show
|
||||
const getAutoNavigationSource = (): 'landing' | 'melbourne' => {
|
||||
const getAutoNavigationSource = () => {
|
||||
const path = location.pathname;
|
||||
|
||||
// Explicit routes
|
||||
if (path.startsWith('/melbourne')) return 'melbourne';
|
||||
// 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 (['/passes', '/how-it-works'].includes(path)) {
|
||||
// return lastKnownCity; // ← remembers where user came from
|
||||
// }
|
||||
|
||||
// Fallback
|
||||
return lastKnownCity;
|
||||
return citySelected;
|
||||
};
|
||||
|
||||
|
||||
@@ -261,7 +246,7 @@ export default function Navbar({
|
||||
const getNavigationItems = (): NavigationItem[] => {
|
||||
const currentSource = getAutoNavigationSource();
|
||||
const items = currentSource === 'landing' ?
|
||||
navigationConfig.landing : navigationConfig.melbourne;
|
||||
navigationConfig.landing : navigationConfig.citySelected;
|
||||
|
||||
return items.map((item, index) => ({
|
||||
...item,
|
||||
@@ -370,34 +355,6 @@ export default function Navbar({
|
||||
{ id: '2', name: 'Melbourne Premium Pass', price: '$129', quantity: 1 },
|
||||
];
|
||||
|
||||
// Calculate cart total
|
||||
const cartTotal = cartItems.reduce((total, item) => {
|
||||
const price = parseFloat(item.price.replace('$', ''));
|
||||
return total + (price * item.quantity);
|
||||
}, 0);
|
||||
|
||||
// Cart dropdown items with proper navigation for checkout
|
||||
const cartDropdownItems: DropdownItem[] = [
|
||||
...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: () => {
|
||||
navigate('/checkout');
|
||||
setActiveCartDropdown(false);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const closeMobileMenu = () => {
|
||||
setIsMobileMenuOpen(false);
|
||||
};
|
||||
@@ -425,9 +382,9 @@ export default function Navbar({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeCity.toLowerCase() === 'melbourne') {
|
||||
if (activeCity?.toLowerCase() === 'melbourne') {
|
||||
setLastKnownCity('melbourne');
|
||||
} else if (activeCity.toLowerCase() === 'landing' || activeCity.toLowerCase() === 'landingpage') {
|
||||
} else if (activeCity?.toLowerCase() === 'landing' || activeCity?.toLowerCase() === 'landingpage') {
|
||||
setLastKnownCity('landing');
|
||||
}
|
||||
}, [activeCity]);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { motion, AnimatePresence } from 'motion/react';
|
||||
import { Sparkles, MapPin, Calendar, Wand2, Clock } from 'lucide-react';
|
||||
// import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||
import cityTourVideo from '../assets/citycards-vid.mp4';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface PersonalizedTourHeroProps {
|
||||
onCreateItineraryClick?: () => void;
|
||||
@@ -17,6 +18,7 @@ interface AttractionCard {
|
||||
}
|
||||
|
||||
export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTourHeroProps) {
|
||||
const navigate = useNavigate();
|
||||
const attractionCards: AttractionCard[] = [
|
||||
{
|
||||
id: 1,
|
||||
@@ -163,8 +165,8 @@ export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTou
|
||||
className="flex flex-col sm:flex-row gap-4 items-start sm:items-center"
|
||||
>
|
||||
<button
|
||||
onClick={onCreateItineraryClick}
|
||||
className="group relative px-8 py-4 rounded-lg flex items-center gap-2 overflow-hidden transition-all duration-300 hover:scale-105 shadow-lg hover:shadow-xl font-poppins font-semibold text-base text-white bg-gradient-to-r from-primary via-orange-500 to-rose-500 hover:from-primary/90 hover:via-orange-500/90 hover:to-rose-500/90"
|
||||
onClick={() => navigate('/create-itinerary')}
|
||||
className="group cursor-pointer px-8 py-4 rounded-lg flex items-center gap-2 overflow-hidden transition-all duration-300 hover:scale-105 shadow-lg hover:shadow-xl font-poppins font-semibold text-base text-white bg-gradient-to-r from-primary via-orange-500 to-rose-500 hover:from-primary/90 hover:via-orange-500/90 hover:to-rose-500/90"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-700" />
|
||||
<Wand2 className="w-5 h-5 relative z-10 group-hover:rotate-12 transition-transform duration-300" />
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { X } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { Label } from './ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
||||
import { useRegisterMutation } from '../Redux/services/auth.service';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface RegisterModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onLoginClick: () => void;
|
||||
}
|
||||
|
||||
export function RegisterModal({ isOpen, onClose, onLoginClick }: RegisterModalProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
emailAddress: '',
|
||||
isdCode: '+91',
|
||||
mobileNumber: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
country: 'Australia',
|
||||
postalCode: ''
|
||||
});
|
||||
const [helperText, setHelperText] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [register, { isLoading: isRegistering }] = useRegisterMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setFormData({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
emailAddress: '',
|
||||
isdCode: '+91',
|
||||
mobileNumber: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
country: 'Australia',
|
||||
postalCode: ''
|
||||
});
|
||||
setHelperText('');
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
if (!formData.firstName.trim()) {
|
||||
toast.error('First name is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.lastName.trim()) {
|
||||
toast.error('Last name is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.emailAddress.trim() || !formData.emailAddress.includes('@')) {
|
||||
toast.error('Please enter a valid email address');
|
||||
return false;
|
||||
}
|
||||
if (!formData.mobileNumber.trim()) {
|
||||
toast.error('Mobile number is required');
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+$/.test(formData.mobileNumber.trim())) {
|
||||
toast.error('Mobile number must contain only digits');
|
||||
return false;
|
||||
}
|
||||
if (!formData.address1.trim()) {
|
||||
toast.error('Address is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.city.trim()) {
|
||||
toast.error('City is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.state.trim()) {
|
||||
toast.error('State is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.postalCode.trim()) {
|
||||
toast.error('Postal code is required');
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+$/.test(formData.postalCode.trim())) {
|
||||
toast.error('Postal code must contain only digits');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setHelperText('');
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await register(formData).unwrap();
|
||||
console.log('Registration response:', response);
|
||||
|
||||
toast.success('Registration successful! Please login.');
|
||||
setTimeout(() => {
|
||||
onLoginClick();
|
||||
onClose();
|
||||
}, 2000);
|
||||
} catch (error: any) {
|
||||
console.error('Registration error:', error);
|
||||
const errorMessage = error?.data?.message || 'Registration failed. Please try again.';
|
||||
toast.error(errorMessage);
|
||||
setHelperText(errorMessage);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleRegister();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
className="fixed inset-0 flex items-center justify-center z-50 p-4 overflow-y-auto"
|
||||
>
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-2xl mx-auto overflow-hidden max-h-[90vh] overflow-y-auto">
|
||||
<div className="relative px-8 pt-8 pb-4 top-0 bg-white z-10">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-6 right-6 w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 transition-colors cursor-pointer"
|
||||
>
|
||||
<X className="w-4 h-4 text-gray-600" />
|
||||
</button>
|
||||
|
||||
<h2 className="font-merchant text-2xl font-semibold text-gray-900 mb-2">
|
||||
Create Account
|
||||
</h2>
|
||||
<p className="font-poppins text-sm text-gray-600">
|
||||
Register to get started with City Cards
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="px-8 pb-8">
|
||||
<div className="space-y-6">
|
||||
{/* Personal Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-poppins text-base font-semibold text-gray-800">Personal Information</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
First Name <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter your first name"
|
||||
value={formData.firstName}
|
||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Last Name <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter your last name"
|
||||
value={formData.lastName}
|
||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Email Address <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Enter your email address"
|
||||
value={formData.emailAddress}
|
||||
onChange={(e) => handleInputChange('emailAddress', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
ISD Code
|
||||
</Label>
|
||||
<Select value={formData.isdCode} onValueChange={(value: any) => handleInputChange('isdCode', value)}>
|
||||
<SelectTrigger className="h-12 bg-gray-50 border-0 rounded-xl cursor-pointer">
|
||||
<SelectValue placeholder="Select code" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="+1">+1 (USA)</SelectItem>
|
||||
<SelectItem value="+44">+44 (UK)</SelectItem>
|
||||
<SelectItem value="+61">+61 (Australia)</SelectItem>
|
||||
<SelectItem value="+91">+91 (India)</SelectItem>
|
||||
<SelectItem value="+86">+86 (China)</SelectItem>
|
||||
<SelectItem value="+81">+81 (Japan)</SelectItem>
|
||||
<SelectItem value="+49">+49 (Germany)</SelectItem>
|
||||
<SelectItem value="+33">+33 (France)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2 space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Mobile Number <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="tel"
|
||||
placeholder="Enter your mobile number"
|
||||
value={formData.mobileNumber}
|
||||
onChange={(e) => handleInputChange('mobileNumber', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Address Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-poppins text-base font-semibold text-gray-800">Address Information</h3>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Address Line 1 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter street address"
|
||||
value={formData.address1}
|
||||
onChange={(e) => handleInputChange('address1', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Address Line 2
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter apartment, suite, unit (optional)"
|
||||
value={formData.address2}
|
||||
onChange={(e) => handleInputChange('address2', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
City <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter city name"
|
||||
value={formData.city}
|
||||
onChange={(e) => handleInputChange('city', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
State <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter state name"
|
||||
value={formData.state}
|
||||
onChange={(e) => handleInputChange('state', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Country <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Select value={formData.country} onValueChange={(value: any) => handleInputChange('country', value)}>
|
||||
<SelectTrigger className="h-12 bg-gray-50 border-0 rounded-xl cursor-pointer">
|
||||
<SelectValue placeholder="Select country" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Australia">Australia</SelectItem>
|
||||
<SelectItem value="United States">United States</SelectItem>
|
||||
<SelectItem value="United Kingdom">United Kingdom</SelectItem>
|
||||
<SelectItem value="Canada">Canada</SelectItem>
|
||||
<SelectItem value="India">India</SelectItem>
|
||||
<SelectItem value="Germany">Germany</SelectItem>
|
||||
<SelectItem value="France">France</SelectItem>
|
||||
<SelectItem value="Japan">Japan</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Postal Code <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter postal code"
|
||||
value={formData.postalCode}
|
||||
onChange={(e) => handleInputChange('postalCode', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{helperText && (
|
||||
<p className={`font-poppins text-xs ${helperText.includes('successful') ? 'text-green-600' : 'text-red-500'}`}>
|
||||
{helperText}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleRegister}
|
||||
disabled={isLoading || isRegistering}
|
||||
className="w-full h-12 bg-gray-800 hover:bg-gray-900 text-white font-poppins font-semibold rounded-xl transition-colors cursor-pointer"
|
||||
>
|
||||
{isLoading || isRegistering ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 border-2 border-white/20 border-t-white rounded-full animate-spin" />
|
||||
Creating Account...
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
Register
|
||||
<svg className="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
onLoginClick();
|
||||
onClose();
|
||||
}}
|
||||
className="font-poppins text-sm text-gray-600 hover:text-gray-800 transition-colors cursor-pointer"
|
||||
>
|
||||
Already have an account? <span className="text-primary font-semibold">Login</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
297
src/components/RegisterPage.tsx
Normal file
@@ -0,0 +1,297 @@
|
||||
import { useState } from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { useRegisterMutation } from '../Redux/services/auth.service';
|
||||
import { toast } from 'sonner';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import Navbar from './Navbar';
|
||||
import { Footer } from './Footer';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Label } from './ui/label';
|
||||
|
||||
export default function RegisterPage() {
|
||||
const { login } = useAuth();
|
||||
const email = localStorage.getItem("userEmail")
|
||||
const [formData, setFormData] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
emailAddress: email ?? "",
|
||||
isdCode: '',
|
||||
mobileNumber: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
country: '',
|
||||
postalCode: ''
|
||||
});
|
||||
|
||||
const [helperText, setHelperText] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [register, { isLoading: isRegistering }] = useRegisterMutation();
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
// First Name
|
||||
if (!formData.firstName.trim()) return toast.error('First name is required'), false;
|
||||
if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only alphabets'), false;
|
||||
if (formData.firstName.length < 2 || formData.firstName.length > 50) return toast.error('First name must be between 2 and 50 characters'), false;
|
||||
|
||||
// Last Name
|
||||
if (!formData.lastName.trim()) return toast.error('Last name is required'), false;
|
||||
if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only alphabets'), false;
|
||||
if (formData.lastName.length < 2 || formData.lastName.length > 50) return toast.error('Last name must be between 2 and 50 characters'), false;
|
||||
|
||||
// Email
|
||||
if (!formData.emailAddress.includes('@')) return toast.error('Invalid email address'), false;
|
||||
|
||||
// ISD Code
|
||||
if (!formData.isdCode.startsWith("+")) return toast.error("ISD code must start with '+'"), false;
|
||||
if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only numbers after '+'"), false;
|
||||
|
||||
// Mobile Number
|
||||
if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Invalid mobile number'), false;
|
||||
if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false;
|
||||
|
||||
// Address Line 1
|
||||
if (!formData.address1.trim()) return toast.error('Address required'), false;
|
||||
if (!/^[A-Za-z0-9\s]+$/.test(formData.address1)) return toast.error('Address must be alphanumeric'), false;
|
||||
if (formData.address1.length < 5 || formData.address1.length > 100) return toast.error('Address must be between 5 and 100 characters'), false;
|
||||
|
||||
// City
|
||||
if (!formData.city.trim()) return toast.error('City required'), false;
|
||||
if (!/^[A-Za-z\s]+$/.test(formData.city)) return toast.error('City must contain only alphabets'), false;
|
||||
if (formData.city.length < 2 || formData.city.length > 50) return toast.error('City must be between 2 and 50 characters'), false;
|
||||
|
||||
// State
|
||||
if (!formData.state.trim()) return toast.error('State required'), false;
|
||||
if (!/^[A-Za-z\s]+$/.test(formData.state)) return toast.error('State must contain only alphabets'), false;
|
||||
if (formData.state.length < 2 || formData.state.length > 50) return toast.error('State must be between 2 and 50 characters'), false;
|
||||
|
||||
// Country
|
||||
if (!formData.country.trim()) return toast.error('Country required'), false;
|
||||
if (!/^[A-Za-z\s]+$/.test(formData.country)) return toast.error('Country must contain only alphabets'), false;
|
||||
if (formData.country.length < 2 || formData.country.length > 50) return toast.error('Country must be between 2 and 50 characters'), false;
|
||||
|
||||
// Postal Code
|
||||
if (!/^\d+$/.test(formData.postalCode)) return toast.error('Postal code should only contain numbers'), false;
|
||||
if (formData.postalCode.length < 4 || formData.postalCode.length > 10) return toast.error('Postal code must be between 4 and 10 digits'), false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
setIsLoading(true);
|
||||
setHelperText('');
|
||||
|
||||
try {
|
||||
const response = await register(formData).unwrap();
|
||||
toast.success('Registration successful!');
|
||||
const userData = {
|
||||
userId: response?.user?.id,
|
||||
email: response?.email || formData.emailAddress,
|
||||
name: response?.name || formData.emailAddress.split('@')[0].charAt(0).toUpperCase() + formData.emailAddress.split('@')[0].slice(1),
|
||||
accessToken: response?.accessToken,
|
||||
};
|
||||
login(userData);
|
||||
localStorage.removeItem("userEmail")
|
||||
navigate("/")
|
||||
} catch (err: any) {
|
||||
const msg = err?.data?.message || 'Registration failed';
|
||||
toast.error(msg);
|
||||
setHelperText(msg);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-gray-50 w-full">
|
||||
{/* Navbar */}
|
||||
<Navbar activeCity="" />
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-grow w-full px-6 md:px-10 py-10 mt-20">
|
||||
<div className="w-full max-w-5xl mx-auto">
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h2 className="font-merchant text-3xl font-semibold text-gray-900 mb-2">
|
||||
Create Account
|
||||
</h2>
|
||||
<p className="font-poppins text-gray-600">
|
||||
Register to get started with City Cards
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Form Container */}
|
||||
<div className="bg-white rounded-2xl border border-gray-200 p-8 space-y-8">
|
||||
|
||||
{/* Personal Info */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-poppins font-semibold text-gray-800 text-lg">
|
||||
Personal Information
|
||||
</h3>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<Label htmlFor="firstName" className="font-poppins font-light">First Name</Label>
|
||||
<Input
|
||||
id="firstName"
|
||||
value={formData.firstName}
|
||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="lastName" className="font-poppins font-light">Last Name</Label>
|
||||
<Input
|
||||
id="lastName"
|
||||
value={formData.lastName}
|
||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="emailAddress" className="font-poppins font-light">Email Address</Label>
|
||||
<Input
|
||||
id="emailAddress"
|
||||
type="email"
|
||||
value={formData.emailAddress}
|
||||
disabled
|
||||
onChange={(e) => handleInputChange('emailAddress', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<Label htmlFor="isdCode" className="font-poppins font-light">ISD Code</Label>
|
||||
<Input
|
||||
id="isdCode"
|
||||
placeholder="example: +91"
|
||||
value={formData.isdCode}
|
||||
onChange={(e) => handleInputChange('isdCode', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<Label htmlFor="mobileNumber" className="font-poppins font-light">Mobile Number</Label>
|
||||
<Input
|
||||
id="mobileNumber"
|
||||
value={formData.mobileNumber}
|
||||
onChange={(e) => handleInputChange('mobileNumber', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Address */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-poppins font-semibold text-gray-800 text-lg">
|
||||
Address Information
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="address1" className="font-poppins font-light">Address Line 1</Label>
|
||||
<Input
|
||||
id="address1"
|
||||
value={formData.address1}
|
||||
onChange={(e) => handleInputChange('address1', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="address2" className="font-poppins font-light">Address Line 2</Label>
|
||||
<Input
|
||||
id="address2"
|
||||
value={formData.address2}
|
||||
onChange={(e) => handleInputChange('address2', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<Label htmlFor="city" className="font-poppins font-light">City</Label>
|
||||
<Input
|
||||
id="city"
|
||||
value={formData.city}
|
||||
onChange={(e) => handleInputChange('city', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="state" className="font-poppins font-light">State</Label>
|
||||
<Input
|
||||
id="state"
|
||||
value={formData.state}
|
||||
onChange={(e) => handleInputChange('state', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<Label htmlFor="country" className="font-poppins font-light">Country</Label>
|
||||
<Input
|
||||
id="country"
|
||||
value={formData.country}
|
||||
onChange={(e) => handleInputChange('country', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="postalCode" className="font-poppins font-light">Postal Code</Label>
|
||||
<Input
|
||||
id="postalCode"
|
||||
value={formData.postalCode}
|
||||
onChange={(e) => handleInputChange('postalCode', e.target.value)}
|
||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{helperText && (
|
||||
<p className="text-sm text-red-500">{helperText}</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleRegister}
|
||||
disabled={isLoading || isRegistering}
|
||||
className="w-full cursor-pointer bg-gray-800 hover:bg-gray-900 md:px-10 h-12 text-white rounded-xl"
|
||||
>
|
||||
{isLoading || isRegistering ? 'Registering...' : 'Register'}
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="mt-auto">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -40,6 +40,8 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("accessToken")
|
||||
localStorage.removeItem("userId")
|
||||
localStorage.removeItem("userEmail")
|
||||
sessionStorage.removeItem("citySelected")
|
||||
navigate("/")
|
||||
}
|
||||
|
||||
|
||||
42
src/imports/AfterLogin.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
function Text() {
|
||||
return (
|
||||
<div className="h-[14px] relative shrink-0 w-[13.703px]" data-name="Text">
|
||||
<div className="bg-clip-padding border-0 border-[transparent] border-solid box-border h-[14px] relative w-[13.703px]">
|
||||
<div className="absolute font-['Poppins:Bold',_sans-serif] leading-[0] left-0 not-italic text-[10.5px] text-nowrap text-white top-0">
|
||||
<p className="leading-[14px] whitespace-pre">JD</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Container() {
|
||||
return (
|
||||
<div className="box-border content-stretch flex items-center justify-center pl-0 pr-[0.016px] py-0 relative rounded-[3.35544e+07px] shrink-0 size-[28px]" data-name="Container" style={{ backgroundImage: "linear-gradient(90deg, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0.4) 100%), linear-gradient(90deg, rgb(249, 95, 98) 0%, rgb(249, 95, 98) 100%)" }}>
|
||||
<Text />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Text1() {
|
||||
return (
|
||||
<div className="content-stretch flex h-[17.5px] items-start relative shrink-0 w-[86.047px]" data-name="Text">
|
||||
<div className="font-['Poppins:SemiBold',_sans-serif] leading-[0] not-italic relative shrink-0 text-[12.25px] text-nowrap text-white">
|
||||
<p className="leading-[17.5px] whitespace-pre">MY CITY CARD</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AfterLogin() {
|
||||
return (
|
||||
<div className="bg-[#f95f62] relative rounded-[3.35544e+07px] shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)] size-full" data-name="After Login">
|
||||
<div className="flex flex-row items-center justify-center relative size-full">
|
||||
<div className="box-border content-stretch flex gap-[4px] items-center justify-center overflow-clip px-[15px] py-[12px] relative size-full">
|
||||
<Container />
|
||||
<Text1 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/imports/BeforeLogin.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
function Text() {
|
||||
return (
|
||||
<div className="content-stretch flex h-[17.5px] items-start relative shrink-0" data-name="Text">
|
||||
<div className="font-['Poppins:SemiBold',_sans-serif] leading-[0] not-italic relative shrink-0 text-[12.25px] text-nowrap text-white">
|
||||
<p className="leading-[17.5px] whitespace-pre">GET A CITY CARD</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BeforeLogin() {
|
||||
return (
|
||||
<div className="bg-[#f95f62] relative rounded-[3.35544e+07px] shadow-[0px_10px_15px_-3px_rgba(0,0,0,0.1),0px_4px_6px_-4px_rgba(0,0,0,0.1)] size-full" data-name="Before Login">
|
||||
<div className="flex flex-col items-center relative size-full">
|
||||
<div className="box-border content-stretch flex flex-col gap-[8px] items-center overflow-clip px-[47px] py-[17px] relative size-full">
|
||||
<Text />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
916
src/imports/MagicItinDay5.tsx
Normal file
@@ -0,0 +1,916 @@
|
||||
import svgPaths from "./svg-adqhdgpsh";
|
||||
import imgPhoto from "figma:asset/895585e16a5ef99513fed52e650cc187c5d00f0a.png";
|
||||
import imgRectangle36 from "figma:asset/c878c3c798a222b8d2bc4e2bd140d905882d2175.png";
|
||||
import imgRectangle37 from "figma:asset/a125d02d1ff3e548286c149140a80da8c96f2b93.png";
|
||||
|
||||
function Confetti() {
|
||||
return (
|
||||
<div className="absolute h-[600.579px] left-0 top-[729.79px] w-[392px]" data-name="Confetti">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 32 32">
|
||||
<g id="Confetti" opacity="0">
|
||||
<path d={svgPaths.p30846ac0} fill="var(--fill-0, #F266FF)" id="Vector" />
|
||||
<path d={svgPaths.p1af8f150} id="Vector_2" stroke="var(--stroke-0, #A1FF66)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p2e546900} fill="var(--fill-0, #66FFA8)" id="Vector_3" />
|
||||
<path d={svgPaths.p2a600580} id="Vector_4" stroke="var(--stroke-0, #8C66FF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p3b167700} fill="var(--fill-0, #66D9FF)" id="Vector_5" />
|
||||
<path d={svgPaths.p114f0d00} id="Vector_6" stroke="var(--stroke-0, #66CCFF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p6b82b00} fill="var(--fill-0, #7D66FF)" id="Vector_7" />
|
||||
<path d={svgPaths.p3a1b1dc0} id="Vector_8" stroke="var(--stroke-0, #E066FF)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1f72dd00} id="Vector_9" stroke="var(--stroke-0, #66FFED)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p31508a60} id="Vector_10" stroke="var(--stroke-0, #FFC766)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p3e65c400} id="Vector_11" stroke="var(--stroke-0, #FF6E66)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1ca023c0} id="Vector_12" stroke="var(--stroke-0, #E8FF66)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p36217800} fill="var(--fill-0, #DEFF66)" id="Vector_13" />
|
||||
<path d={svgPaths.p13f60480} id="Vector_14" stroke="var(--stroke-0, #66FFE8)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p125d3600} fill="var(--fill-0, #A6FF66)" id="Vector_15" />
|
||||
<path d={svgPaths.p10f7c300} fill="var(--fill-0, #85FF66)" id="Vector_16" />
|
||||
<path d={svgPaths.p365e9600} fill="var(--fill-0, #66FFBF)" id="Vector_17" />
|
||||
<path d={svgPaths.p1617b600} fill="var(--fill-0, #66FFB5)" id="Vector_18" />
|
||||
<path d={svgPaths.p2f540d00} fill="var(--fill-0, #D966FF)" id="Vector_19" />
|
||||
<path d={svgPaths.p26750500} fill="var(--fill-0, #66FF7F)" id="Vector_20" />
|
||||
<path d={svgPaths.pca2f800} id="Vector_21" stroke="var(--stroke-0, #FFB866)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p130d1600} fill="var(--fill-0, #B8FF66)" id="Vector_22" />
|
||||
<path d={svgPaths.p112ca500} id="Vector_23" stroke="var(--stroke-0, #66FFC9)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p30587f00} id="Vector_24" stroke="var(--stroke-0, #FF66B5)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p2c53d00} fill="var(--fill-0, #FFB866)" id="Vector_25" />
|
||||
<path d={svgPaths.p245b5500} fill="var(--fill-0, #FFDB66)" id="Vector_26" />
|
||||
<path d={svgPaths.p13ac0f00} fill="var(--fill-0, #D4FF66)" id="Vector_27" />
|
||||
<path d={svgPaths.p2d198000} id="Vector_28" stroke="var(--stroke-0, #66FF96)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.pc00e050} id="Vector_29" stroke="var(--stroke-0, #66FFC2)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p1ae38580} id="Vector_30" stroke="var(--stroke-0, #66FF7D)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1d9db000} fill="var(--fill-0, #FFB366)" id="Vector_31" />
|
||||
<path d={svgPaths.pcd62e8} id="Vector_32" stroke="var(--stroke-0, #F266FF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.pa248b00} fill="var(--fill-0, #6699FF)" id="Vector_33" />
|
||||
<path d={svgPaths.p23dc0000} id="Vector_34" stroke="var(--stroke-0, #FF7366)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p21984100} fill="var(--fill-0, #FF6669)" id="Vector_35" />
|
||||
<path d={svgPaths.p315a1e30} fill="var(--fill-0, #FF8266)" id="Vector_36" />
|
||||
<path d={svgPaths.p36d2ba80} id="Vector_37" stroke="var(--stroke-0, #E366FF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.pee931c0} fill="var(--fill-0, #FF66E8)" id="Vector_38" />
|
||||
<path d={svgPaths.p23d70180} fill="var(--fill-0, #66FF8C)" id="Vector_39" />
|
||||
<path d={svgPaths.p3ba87480} fill="var(--fill-0, #66FFF5)" id="Vector_40" />
|
||||
<path d="M14.4383 38.0174L0 4.01739" id="Vector_41" stroke="var(--stroke-0, #FF7566)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1d1bf900} id="Vector_42" stroke="var(--stroke-0, #8AFF66)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p39b4f00} fill="var(--fill-0, #ED66FF)" id="Vector_43" />
|
||||
<path d={svgPaths.p2369e2c0} id="Vector_44" stroke="var(--stroke-0, #FF6682)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p21a2df00} fill="var(--fill-0, #FF66BD)" id="Vector_45" />
|
||||
<path d={svgPaths.p245cc9b0} fill="var(--fill-0, #66FFF2)" id="Vector_46" />
|
||||
<path d={svgPaths.p293abff0} fill="var(--fill-0, #FFB866)" id="Vector_47" />
|
||||
<path d={svgPaths.p28906180} fill="var(--fill-0, #66E6FF)" id="Vector_48" />
|
||||
<path d={svgPaths.p3282b480} fill="var(--fill-0, #FF66ED)" id="Vector_49" />
|
||||
<path d={svgPaths.p3d2fe400} id="Vector_50" stroke="var(--stroke-0, #667AFF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p1fd17c80} fill="var(--fill-0, #FF6666)" id="Vector_51" />
|
||||
<path d={svgPaths.p1ada2c00} fill="var(--fill-0, #FF667A)" id="Vector_52" />
|
||||
<path d={svgPaths.p55e1500} id="Vector_53" stroke="var(--stroke-0, #66D1FF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p9491b80} fill="var(--fill-0, #FF7566)" id="Vector_54" />
|
||||
<path d={svgPaths.p394df1c0} fill="var(--fill-0, #91FF66)" id="Vector_55" />
|
||||
<path d={svgPaths.p39dd4ef0} fill="var(--fill-0, #CCFF66)" id="Vector_56" />
|
||||
<path d={svgPaths.p38fea80} fill="var(--fill-0, #CCFF66)" id="Vector_57" />
|
||||
<path d={svgPaths.p751e500} fill="var(--fill-0, #66FFA8)" id="Vector_58" />
|
||||
<path d={svgPaths.p3ae84670} fill="var(--fill-0, #75FF66)" id="Vector_59" />
|
||||
<path d={svgPaths.p195a2f80} fill="var(--fill-0, #FF66EB)" id="Vector_60" />
|
||||
<path d={svgPaths.p1c14be00} fill="var(--fill-0, #66FF9E)" id="Vector_61" />
|
||||
<path d={svgPaths.p302dfa00} fill="var(--fill-0, #FF6682)" id="Vector_62" />
|
||||
<path d={svgPaths.p8f80e00} fill="var(--fill-0, #F266FF)" id="Vector_63" />
|
||||
<path d={svgPaths.p37f77100} id="Vector_64" stroke="var(--stroke-0, #66FFEB)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p31b9a200} fill="var(--fill-0, #BAFF66)" id="Vector_65" />
|
||||
<path d={svgPaths.p1cfa9a70} fill="var(--fill-0, #66FFB0)" id="Vector_66" />
|
||||
<path d={svgPaths.p273a0500} fill="var(--fill-0, #66FFF7)" id="Vector_67" />
|
||||
<path d={svgPaths.p1621380} fill="var(--fill-0, #F766FF)" id="Vector_68" />
|
||||
<path d={svgPaths.p32b87800} id="Vector_69" stroke="var(--stroke-0, #FF66ED)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p9a6ab80} id="Vector_70" stroke="var(--stroke-0, #96FF66)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p1261c6f0} fill="var(--fill-0, #FF66E8)" id="Vector_71" />
|
||||
<path d={svgPaths.p31d95b00} fill="var(--fill-0, #6670FF)" id="Vector_72" />
|
||||
<path d={svgPaths.p670ab80} fill="var(--fill-0, #66FF7F)" id="Vector_73" />
|
||||
<path d={svgPaths.p20991b80} fill="var(--fill-0, #DE66FF)" id="Vector_74" />
|
||||
<path d={svgPaths.pb0da100} id="Vector_75" stroke="var(--stroke-0, #BA66FF)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p2dc248a0} id="Vector_76" stroke="var(--stroke-0, #FFAD66)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1b50ff00} fill="var(--fill-0, #8CFF66)" id="Vector_77" />
|
||||
<path d={svgPaths.p33b66680} fill="var(--fill-0, #99FF66)" id="Vector_78" />
|
||||
<path d={svgPaths.p1f83a980} fill="var(--fill-0, #66FFFF)" id="Vector_79" />
|
||||
<path d="M361.68 6.01758L392 22.0176" id="Vector_80" stroke="var(--stroke-0, #FF66A8)" strokeLinecap="round" strokeWidth="4" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Icon() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[15.984px]" data-name="Icon">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 15.9836 15.9836">
|
||||
<g id="Icon">
|
||||
<path d={svgPaths.pabdaa80} id="Vector" stroke="var(--stroke-0, #4A5565)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33197" />
|
||||
<path d={svgPaths.p23a159d0} id="Vector_2" stroke="var(--stroke-0, #4A5565)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33197" />
|
||||
<path d={svgPaths.p5521d80} id="Vector_3" stroke="var(--stroke-0, #4A5565)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33197" />
|
||||
<path d={svgPaths.p6cb7a00} id="Vector_4" stroke="var(--stroke-0, #4A5565)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33197" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CompletionScreen() {
|
||||
return (
|
||||
<div className="h-[42px] relative rounded-[36829000px] shrink-0 w-full" data-name="CompletionScreen">
|
||||
<div aria-hidden="true" className="absolute border-[#e5e7eb] border-[1.098px] border-solid inset-0 pointer-events-none rounded-[36829000px]" />
|
||||
<div className="flex flex-row items-center justify-center size-full">
|
||||
<div className="bg-clip-padding border-0 border-[transparent] border-solid content-stretch flex gap-[8px] items-center justify-center px-[111px] py-[12px] relative size-full">
|
||||
<Icon />
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#4a5565] text-[16px] whitespace-nowrap">Start Over</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame() {
|
||||
return (
|
||||
<div className="bg-[#f95f62] h-[42px] relative rounded-[38px] shrink-0 w-full">
|
||||
<div className="flex flex-row items-center justify-center size-full">
|
||||
<div className="bg-clip-padding border-0 border-[transparent] border-solid content-stretch flex items-center justify-center pl-[8px] pr-[12px] py-[12px] relative size-full">
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[16px] text-white whitespace-nowrap">Get My Trip Plan</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Container() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex flex-col gap-[12px] items-start justify-center left-[20.46px] opacity-0 top-[186px] w-[336px]" data-name="Container">
|
||||
<CompletionScreen />
|
||||
<Frame />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HeaderText() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex flex-col gap-[8px] items-start left-[20.46px] not-italic opacity-0 text-center top-[65px] w-[352.015px]" data-name="header text">
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[0] min-w-full relative shrink-0 text-[#101828] text-[24px] tracking-[-0.96px] w-[min-content]">
|
||||
<span className="font-['Poppins:Regular',sans-serif] leading-[1.3] text-[rgba(16,24,40,0.6)]">Your</span>
|
||||
<span className="leading-[1.3]">{` `}</span>
|
||||
<span className="leading-[1.3] text-[#f95f62]">Magic Itinerary</span>
|
||||
<span className="leading-[1.3]">{` `}</span>
|
||||
<span className="font-['Poppins:Regular',sans-serif] leading-[1.3] text-[rgba(16,24,40,0.6)]">is</span>
|
||||
<span className="leading-[1.3]">{` `}</span>
|
||||
<span className="leading-[1.3] text-[#f95f62]">Ready</span>
|
||||
<span className="leading-[1.3]">{` ✨`}</span>
|
||||
</p>
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] relative shrink-0 text-[14px] text-[rgba(0,0,0,0.6)] w-[336px]">{`We've got everything we need to plan your perfect trip`}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame37() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex flex-col font-['Poppins:SemiBold',sans-serif] gap-[6px] items-start leading-[0] left-[19.5px] not-italic text-[#101828] text-center top-[82px] w-[351.979px]">
|
||||
<div className="flex flex-col h-[21.107px] justify-center relative shrink-0 text-[24px] w-full">
|
||||
<p className="leading-[32px]">{`Your `}</p>
|
||||
</div>
|
||||
<div className="flex flex-col h-[21.107px] justify-center relative shrink-0 text-[0px] w-full">
|
||||
<p className="text-[24px]">
|
||||
<span className="leading-[32px] text-[#f95f62]">Melbourne</span>
|
||||
<span className="leading-[32px]">{` `}</span>
|
||||
<span className="leading-[32px] text-[#f95f62]">Magic Itinerary</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Photo() {
|
||||
return (
|
||||
<div className="flex-[1_0_0] min-h-px min-w-px relative rounded-[2px] w-full" data-name="photo">
|
||||
<img alt="" className="absolute inset-0 max-w-none object-cover pointer-events-none rounded-[2px] size-full" src={imgPhoto} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OuterFrame() {
|
||||
return (
|
||||
<div className="bg-white content-stretch flex flex-col items-center overflow-clip relative self-stretch shrink-0 w-[86px]" data-name="outer frame">
|
||||
<Photo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group1() {
|
||||
return (
|
||||
<div className="absolute inset-[11.11%]">
|
||||
<div className="absolute inset-[-0.71%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 14.2 14.2">
|
||||
<g id="Group 1000002093">
|
||||
<path d={svgPaths.p1aba0880} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p1fa04100} fill="var(--fill-0, #F95F62)" id="Vector_2" />
|
||||
<path d={svgPaths.pe6ea010} fill="var(--fill-0, #F95F62)" id="Vector_3" />
|
||||
<path d={svgPaths.pbdbaa00} fill="var(--fill-0, #F95F62)" id="Vector_4" />
|
||||
<path d={svgPaths.p18d2ab00} fill="var(--fill-0, #F95F62)" id="Vector_5" />
|
||||
<path d={svgPaths.p16eea180} fill="var(--fill-0, #F95F62)" id="Vector_6" />
|
||||
<path clipRule="evenodd" d={svgPaths.p3e80b480} fill="var(--fill-0, #F95F62)" fillRule="evenodd" id="Vector_7" stroke="var(--stroke-0, white)" strokeWidth="0.2" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame35() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[18px]">
|
||||
<Group1 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame30() {
|
||||
return (
|
||||
<div className="content-center flex flex-wrap gap-[8px] items-center relative shrink-0">
|
||||
<Frame35 />
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#f95f62] text-[16px] whitespace-nowrap">3 Days</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame36() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[18px]">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 18 18">
|
||||
<g id="Frame 1597882141">
|
||||
<path d={svgPaths.pcef7b00} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame31() {
|
||||
return (
|
||||
<div className="content-center flex flex-wrap gap-[8px] items-center relative shrink-0">
|
||||
<Frame36 />
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#f95f62] text-[16px] whitespace-nowrap">12 stops</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Overview1() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[8px] items-start relative shrink-0 w-[237.5px]" data-name="overview">
|
||||
<Frame30 />
|
||||
<Frame31 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame32() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[8px] items-start relative shrink-0 w-full">
|
||||
<div className="flex flex-col font-['Poppins:Regular',sans-serif] h-[9.672px] justify-center leading-[0] min-w-full not-italic relative shrink-0 text-[10px] text-[rgba(0,0,0,0.8)] uppercase w-[min-content]">
|
||||
<p className="leading-[13.852px]">TRIP DETAILS:</p>
|
||||
</div>
|
||||
<Overview1 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute inset-[12.5%_5.95%_8.33%_8.34%]" data-name="Group">
|
||||
<div className="absolute inset-[-5.26%_0_0_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 12 11.6667">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p4d87d80} fill="var(--fill-0, black)" fillOpacity="0.8" id="Vector" />
|
||||
<path d={svgPaths.p3d2bae00} id="Vector_2" stroke="var(--stroke-0, #999999)" strokeLinecap="round" strokeWidth="1.16667" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LetsIconsDateFill() {
|
||||
return (
|
||||
<div className="overflow-clip relative shrink-0 size-[14px]" data-name="lets-icons:date-fill">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Adults() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] h-[16px] items-center justify-center relative shrink-0" data-name="adults">
|
||||
<LetsIconsDateFill />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[14px] not-italic relative shrink-0 text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px] whitespace-nowrap">22/02/2025</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RiParentFill() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[14px]" data-name="ri:parent-fill">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 14 14">
|
||||
<g id="ri:parent-fill">
|
||||
<path d={svgPaths.p7670300} fill="var(--fill-0, black)" fillOpacity="0.8" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Adults1() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] h-[16px] items-center justify-center relative shrink-0" data-name="adults">
|
||||
<RiParentFill />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[14px] not-italic relative shrink-0 text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px] whitespace-nowrap">3 adults</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FaSolidChild() {
|
||||
return (
|
||||
<div className="h-[14px] relative shrink-0 w-[10.5px]" data-name="fa-solid:child">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 10.5 14">
|
||||
<g clipPath="url(#clip0_9924_1087)" id="fa-solid:child">
|
||||
<path d={svgPaths.p2e64dc00} fill="var(--fill-0, black)" fillOpacity="0.8" id="Vector" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9924_1087">
|
||||
<rect fill="white" height="14" width="10.5" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Kids() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] h-[18.667px] items-center relative shrink-0" data-name="kids">
|
||||
<FaSolidChild />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[14px] not-italic relative shrink-0 text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px] whitespace-nowrap">3 kids</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame21() {
|
||||
return (
|
||||
<div className="content-center flex flex-wrap gap-[12px] items-center relative shrink-0 w-full">
|
||||
<Adults />
|
||||
<Adults1 />
|
||||
<Kids />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame28() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[12px] items-start relative shrink-0 w-[237.5px]">
|
||||
<Frame32 />
|
||||
<Frame21 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame29() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] items-start relative shrink-0 w-full">
|
||||
<OuterFrame />
|
||||
<Frame28 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MaterialSymbolsShare() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[16px]" data-name="material-symbols:share">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
|
||||
<g id="material-symbols:share">
|
||||
<path d={svgPaths.p31f8ed00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame9() {
|
||||
return (
|
||||
<div className="bg-[rgba(249,95,98,0)] flex-[1_0_0] h-[44px] min-h-px min-w-px relative rounded-[8px]">
|
||||
<div className="flex flex-row items-center justify-center overflow-clip rounded-[inherit] size-full">
|
||||
<div className="content-stretch flex gap-[8px] items-center justify-center p-[8px] relative size-full">
|
||||
<MaterialSymbolsShare />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#f95f62] text-[12px] whitespace-nowrap">Share</p>
|
||||
</div>
|
||||
</div>
|
||||
<div aria-hidden="true" className="absolute border border-[#f95f62] border-solid inset-0 pointer-events-none rounded-[8px]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DownloadMinimalisticSvgrepoCom() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[16px]" data-name="download-minimalistic_svgrepo.com">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
|
||||
<g id="download-minimalistic_svgrepo.com">
|
||||
<path d={svgPaths.p13f2e80} id="Vector" stroke="var(--stroke-0, #F95F62)" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d={svgPaths.p21309b00} id="Vector_2" stroke="var(--stroke-0, #F95F62)" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame10() {
|
||||
return (
|
||||
<div className="bg-[rgba(249,95,98,0)] flex-[1_0_0] h-[44px] min-h-px min-w-px relative rounded-[8px]">
|
||||
<div className="flex flex-row items-center justify-center overflow-clip rounded-[inherit] size-full">
|
||||
<div className="content-stretch flex gap-[8px] items-center justify-center p-[8px] relative size-full">
|
||||
<DownloadMinimalisticSvgrepoCom />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#f95f62] text-[12px] whitespace-nowrap">Download</p>
|
||||
</div>
|
||||
</div>
|
||||
<div aria-hidden="true" className="absolute border border-[#f95f62] border-solid inset-0 pointer-events-none rounded-[8px]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame11() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[12px] items-center relative shrink-0 w-full">
|
||||
<Frame9 />
|
||||
<Frame10 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Overview() {
|
||||
return (
|
||||
<div className="bg-white content-stretch flex flex-col gap-[8px] items-center p-[8px] relative rounded-[6px] shadow-[0px_23.087px_46.175px_0px_rgba(209,213,220,0.5)] shrink-0 w-[352px]" data-name="Overview">
|
||||
<Frame29 />
|
||||
<div className="h-0 relative shrink-0 w-full">
|
||||
<div className="absolute inset-[-1px_0_0_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 336 1">
|
||||
<line id="Line 631" stroke="var(--stroke-0, black)" strokeLinecap="round" strokeOpacity="0.04" x1="0.5" x2="335.5" y1="0.5" y2="0.5" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<Frame11 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame4() {
|
||||
return (
|
||||
<div className="bg-[#fee7e7] h-[50px] overflow-clip relative rounded-[100px] shrink-0 w-[353px]">
|
||||
<div className="absolute bg-white content-stretch flex items-center justify-center left-[4px] overflow-clip px-[24px] py-[12px] rounded-[100px] top-[4px] w-[155px]" data-name="Button">
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#2a2a2a] text-[16px] whitespace-nowrap">Daily View</p>
|
||||
</div>
|
||||
<div className="absolute content-stretch flex items-center justify-center left-[163px] overflow-clip px-[24px] py-[12px] rounded-[100px] top-[4px] w-[174px]" data-name="Button">
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#2a2a2a] text-[16px] whitespace-nowrap">Summary</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MaterialSymbolsChevronRightRounded() {
|
||||
return (
|
||||
<div className="absolute left-[328px] size-[24px] top-[15px]" data-name="material-symbols:chevron-right-rounded">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 24 24">
|
||||
<g id="material-symbols:chevron-right-rounded">
|
||||
<rect fill="var(--fill-0, #F95F62)" fillOpacity="0.04" height="23" rx="11.5" width="23" x="0.5" y="0.5" />
|
||||
<rect height="23" rx="11.5" stroke="var(--stroke-0, #F95F62)" width="23" x="0.5" y="0.5" />
|
||||
<path d={svgPaths.p149b8d00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame5() {
|
||||
return (
|
||||
<div className="h-[50px] overflow-clip relative shrink-0 w-[353px]">
|
||||
<p className="absolute font-['Poppins:Regular',sans-serif] leading-[22px] left-0 not-italic text-[#f95f62] text-[18px] top-[16px] tracking-[-0.72px] whitespace-nowrap">Day 1</p>
|
||||
<p className="absolute font-['Poppins:Regular',sans-serif] leading-[22px] left-[75px] not-italic text-[18px] text-[rgba(0,0,0,0.8)] top-[16px] tracking-[-0.72px] whitespace-nowrap">Day 2</p>
|
||||
<p className="absolute font-['Poppins:Regular',sans-serif] leading-[22px] left-[155px] not-italic text-[18px] text-[rgba(0,0,0,0.8)] top-[16px] tracking-[-0.72px] whitespace-nowrap">Day 3</p>
|
||||
<p className="absolute font-['Poppins:Regular',sans-serif] leading-[22px] left-[235px] not-italic text-[18px] text-[rgba(0,0,0,0.8)] top-[16px] tracking-[-0.72px] whitespace-nowrap">Day 4</p>
|
||||
<p className="absolute font-['Poppins:Regular',sans-serif] leading-[22px] left-[316px] not-italic text-[18px] text-[rgba(0,0,0,0.4)] top-[16px] tracking-[-0.72px] whitespace-nowrap">D</p>
|
||||
<p className="absolute font-['Poppins:Medium',sans-serif] leading-[18px] left-[363.33px] not-italic text-[#8e8e8e] text-[16px] top-[16px] whitespace-nowrap">Day 3</p>
|
||||
<p className="absolute font-['Poppins:Medium',sans-serif] leading-[18px] left-[456.33px] not-italic text-[#8e8e8e] text-[16px] top-[16px] whitespace-nowrap">Day 4</p>
|
||||
<div className="absolute h-0 left-0 top-[45px] w-[43px]">
|
||||
<div className="absolute inset-[-0.5px_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 43 1">
|
||||
<path d="M0 0.5H43" id="Vector 31" stroke="var(--stroke-0, #F95F62)" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute h-0 left-[6.92px] top-[45px] w-[393px]">
|
||||
<div className="absolute inset-[-0.5px_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 393 1">
|
||||
<path d="M0 0.5H393" id="Vector 31" stroke="var(--stroke-0, #F95F62)" strokeOpacity="0.12" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<MaterialSymbolsChevronRightRounded />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectionTabs() {
|
||||
return (
|
||||
<div className="relative shrink-0 w-full" data-name="selection tabs">
|
||||
<div className="flex flex-col items-center size-full">
|
||||
<div className="content-stretch flex flex-col gap-[16px] items-center px-[20px] relative size-full">
|
||||
<Frame4 />
|
||||
<Frame5 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame27() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center justify-center px-[12px] relative rounded-[24px] shrink-0">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] not-italic relative shrink-0 text-[14px] text-[rgba(0,0,0,0.8)] whitespace-nowrap">09:00 AM</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Time() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[8px] items-center justify-center relative shrink-0 w-[352.888px]" data-name="time">
|
||||
<Frame27 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame2() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[2px] items-start not-italic relative shrink-0 w-full">
|
||||
<p className="font-['Poppins:SemiBold',sans-serif] leading-[22px] relative shrink-0 text-[#2a2a2a] text-[16px] whitespace-nowrap">Melbourne Zoo</p>
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] relative shrink-0 text-[12px] text-[rgba(0,0,0,0.6)] w-[358px]">Elliott Avenue, Parkville, Victoria 3052</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame1() {
|
||||
return <div className="absolute h-[16px] left-[290px] top-[218px] w-[67px]" />;
|
||||
}
|
||||
|
||||
function Cards() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[6px] items-start relative shrink-0 w-full" data-name="Cards">
|
||||
<div className="h-[211px] relative rounded-[8px] shrink-0 w-full">
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none rounded-[8px]">
|
||||
<img alt="" className="absolute h-[250.95%] left-0 max-w-none top-[-41.17%] w-full" src={imgRectangle36} />
|
||||
</div>
|
||||
</div>
|
||||
<Frame2 />
|
||||
<Frame1 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame13() {
|
||||
return (
|
||||
<div className="bg-[rgba(254,231,231,0.6)] content-stretch flex items-center justify-center px-[12px] py-[6px] relative rounded-[100px] shrink-0">
|
||||
<div aria-hidden="true" className="absolute border border-[#bb474a] border-solid inset-0 pointer-events-none rounded-[100px]" />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#bb474a] text-[12px] whitespace-nowrap">Food</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame16() {
|
||||
return (
|
||||
<div className="bg-[rgba(254,231,231,0.6)] content-stretch flex items-center justify-center px-[12px] py-[6px] relative rounded-[100px] shrink-0">
|
||||
<div aria-hidden="true" className="absolute border border-[#bb474a] border-solid inset-0 pointer-events-none rounded-[100px]" />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#bb474a] text-[12px] whitespace-nowrap">Photography</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame17() {
|
||||
return (
|
||||
<div className="bg-[rgba(254,231,231,0.6)] content-stretch flex items-center justify-center px-[12px] py-[6px] relative rounded-[100px] shrink-0">
|
||||
<div aria-hidden="true" className="absolute border border-[#bb474a] border-solid inset-0 pointer-events-none rounded-[100px]" />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#bb474a] text-[12px] whitespace-nowrap">Culture</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame18() {
|
||||
return (
|
||||
<div className="bg-[rgba(254,231,231,0.6)] content-stretch flex items-center justify-center px-[12px] py-[6px] relative rounded-[100px] shrink-0">
|
||||
<div aria-hidden="true" className="absolute border border-[#bb474a] border-solid inset-0 pointer-events-none rounded-[100px]" />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#bb474a] text-[12px] whitespace-nowrap">Souvenirs</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame22() {
|
||||
return (
|
||||
<div className="content-center flex flex-wrap gap-[8px] items-center relative shrink-0 w-full">
|
||||
<Frame13 />
|
||||
<Frame16 />
|
||||
<Frame17 />
|
||||
<Frame18 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MdiLocation() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[24px]" data-name="mdi:location">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 24 24">
|
||||
<g id="mdi:location">
|
||||
<path d={svgPaths.p3aac8400} fill="var(--fill-0, white)" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame6() {
|
||||
return (
|
||||
<div className="absolute bg-[#f95f62] content-stretch flex gap-[10px] items-center justify-center left-[12px] px-[8px] py-[4px] rounded-[100px] top-[11px]">
|
||||
<MdiLocation />
|
||||
<div className="flex flex-col font-['Poppins:Regular',sans-serif] justify-end leading-[0] not-italic relative shrink-0 text-[14px] text-white whitespace-nowrap">
|
||||
<p className="leading-[1.6]">Get Directions</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame23() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[12px] items-start relative shrink-0 w-[353px]">
|
||||
<Cards />
|
||||
<Frame22 />
|
||||
<ul className="block font-['Poppins:Regular',sans-serif] leading-[0] list-disc min-w-full not-italic relative shrink-0 text-[12px] text-[rgba(0,0,0,0.8)] w-[min-content]">
|
||||
<li className="mb-0 ms-[18px]">
|
||||
<span className="leading-[20px]">Discover various species of wildlife only present in this zoo!</span>
|
||||
</li>
|
||||
<li className="mb-0 ms-[18px]">
|
||||
<span className="leading-[20px]">{`Try the amazing food from our local produce `}</span>
|
||||
</li>
|
||||
<li className="ms-[18px]">
|
||||
<span className="leading-[20px]">Shop for souvenirs that makes you trip a memorable one</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Frame6 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame34() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[16px] items-start relative shrink-0">
|
||||
<Time />
|
||||
<Frame23 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame26() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center justify-center px-[12px] relative rounded-[24px] shrink-0">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] not-italic relative shrink-0 text-[14px] text-[rgba(0,0,0,0.8)] whitespace-nowrap">02:00 PM</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame3() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[2px] items-start not-italic relative shrink-0 w-full">
|
||||
<p className="font-['Poppins:SemiBold',sans-serif] leading-[22px] relative shrink-0 text-[#2a2a2a] text-[16px] whitespace-nowrap">Melbourne Skydeck</p>
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] min-w-full relative shrink-0 text-[12px] text-[rgba(0,0,0,0.6)] w-[min-content]">South Bank, just across the Yarra River from Melbourne’s CBD</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Cards1() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[6px] items-start relative shrink-0 w-full" data-name="Cards">
|
||||
<div className="h-[211px] relative rounded-[8px] shrink-0 w-full">
|
||||
<img alt="" className="absolute inset-0 max-w-none object-cover pointer-events-none rounded-[8px] size-full" src={imgRectangle37} />
|
||||
</div>
|
||||
<Frame3 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MdiLocation1() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[24px]" data-name="mdi:location">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 24 24">
|
||||
<g id="mdi:location">
|
||||
<path d={svgPaths.p3aac8400} fill="var(--fill-0, white)" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame7() {
|
||||
return (
|
||||
<div className="absolute bg-[#f95f62] content-stretch flex gap-[10px] items-center justify-center left-[12px] px-[8px] py-[4px] rounded-[100px] top-[10.58px]">
|
||||
<MdiLocation1 />
|
||||
<div className="flex flex-col font-['Poppins:Regular',sans-serif] justify-end leading-[0] not-italic relative shrink-0 text-[14px] text-white whitespace-nowrap">
|
||||
<p className="leading-[1.6]">Get Directions</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame14() {
|
||||
return (
|
||||
<div className="bg-[rgba(254,231,231,0.6)] content-stretch flex items-center justify-center px-[12px] py-[6px] relative rounded-[100px] shrink-0">
|
||||
<div aria-hidden="true" className="absolute border border-[#bb474a] border-solid inset-0 pointer-events-none rounded-[100px]" />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#bb474a] text-[12px] whitespace-nowrap">Food</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame19() {
|
||||
return (
|
||||
<div className="bg-[rgba(254,231,231,0.6)] content-stretch flex items-center justify-center px-[12px] py-[6px] relative rounded-[100px] shrink-0">
|
||||
<div aria-hidden="true" className="absolute border border-[#bb474a] border-solid inset-0 pointer-events-none rounded-[100px]" />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#bb474a] text-[12px] whitespace-nowrap">Photography</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame20() {
|
||||
return (
|
||||
<div className="bg-[rgba(254,231,231,0.6)] content-stretch flex items-center justify-center px-[12px] py-[6px] relative rounded-[100px] shrink-0">
|
||||
<div aria-hidden="true" className="absolute border border-[#bb474a] border-solid inset-0 pointer-events-none rounded-[100px]" />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#bb474a] text-[12px] whitespace-nowrap">Souvenirs</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame25() {
|
||||
return (
|
||||
<div className="content-center flex flex-wrap gap-[8px] items-center relative shrink-0 w-full">
|
||||
<Frame14 />
|
||||
<Frame19 />
|
||||
<Frame20 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame24() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[12px] items-start relative shrink-0 w-[353px]">
|
||||
<Cards1 />
|
||||
<Frame7 />
|
||||
<Frame25 />
|
||||
<ul className="block font-['Poppins:Regular',sans-serif] leading-[0] list-disc min-w-full not-italic relative shrink-0 text-[12px] text-[rgba(0,0,0,0.8)] w-[min-content]">
|
||||
<li className="mb-0 ms-[18px]">
|
||||
<span className="leading-[20px]">Take in the breathtaking views from the summit overlooking Melbourne.</span>
|
||||
</li>
|
||||
<li className="mb-0 ms-[18px]">
|
||||
<span className="leading-[20px]">Try out the infamous sky dining experience</span>
|
||||
</li>
|
||||
<li className="ms-[18px]">
|
||||
<span className="leading-[20px]">Have a peek at the award winning voyager theatre</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame33() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[16px] items-center relative shrink-0">
|
||||
<Frame26 />
|
||||
<Frame24 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame8() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[24px] items-center relative shrink-0">
|
||||
<Frame34 />
|
||||
<Frame33 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ItineraryDetails() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[24px] h-[1417px] items-center relative shrink-0" data-name="itinerary details">
|
||||
<SelectionTabs />
|
||||
<Frame8 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Content() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex flex-col gap-[24px] h-[1673px] items-center left-0 top-[172px] w-[392px]" data-name="content">
|
||||
<Overview />
|
||||
<ItineraryDetails />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ArrowRightLine() {
|
||||
return (
|
||||
<div className="relative size-[24px]" data-name="arrow-right-line">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 24 24">
|
||||
<g id="arrow-right-line">
|
||||
<path d={svgPaths.p15a24580} fill="var(--fill-0, #212121)" fillOpacity="0.4" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame15() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] items-center py-[12px] relative shrink-0 w-[76px]">
|
||||
<div className="flex items-center justify-center relative shrink-0">
|
||||
<div className="flex-none rotate-180">
|
||||
<ArrowRightLine />
|
||||
</div>
|
||||
</div>
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[12px] text-black whitespace-nowrap">Back</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Arrow() {
|
||||
return (
|
||||
<div className="absolute h-[3.274px] left-[10.64px] top-[11.11px] w-[3.293px]" data-name="arrow">
|
||||
<div className="absolute inset-[0_-3.43%_-8.67%_-8.62%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 3.69019 3.55896">
|
||||
<g id="arrow">
|
||||
<path d={svgPaths.p1a652670} fill="var(--stroke-0, #F95F62)" id="Line 13" />
|
||||
<path d={svgPaths.p28638300} fill="var(--stroke-0, #F95F62)" id="Line 14" />
|
||||
<path d={svgPaths.p2ce32bc0} fill="var(--stroke-0, #F95F62)" id="Line 15" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Logo() {
|
||||
return (
|
||||
<div className="absolute contents left-[4.57px] top-[4px]" data-name="logo">
|
||||
<div className="-translate-y-1/2 absolute flex flex-col font-['Chillax:Medium',sans-serif] h-[14.097px] justify-center leading-[0] left-[4.57px] not-italic text-[#f95f62] text-[19.657px] top-[12.95px] tracking-[-1.1794px] w-[13.265px]">
|
||||
<p className="leading-[normal]">C</p>
|
||||
</div>
|
||||
<div className="absolute left-[6.36px] size-[1.329px] top-[5.24px]">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1.3292 1.3292">
|
||||
<circle cx="0.6646" cy="0.6646" fill="var(--fill-0, #F95F62)" id="Ellipse 4" r="0.6646" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="absolute h-[1.411px] left-[8.64px] top-[4px] w-[1.329px]">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1.3292 1.41095">
|
||||
<ellipse cx="0.6646" cy="0.705473" fill="var(--fill-0, #F95F62)" id="Ellipse 5" rx="0.6646" ry="0.705473" />
|
||||
</svg>
|
||||
</div>
|
||||
<Arrow />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame12() {
|
||||
return (
|
||||
<div className="bg-[rgba(249,95,98,0.12)] content-stretch flex items-center justify-center relative rounded-[62px] shrink-0 size-[24px]">
|
||||
<Logo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex items-center justify-between left-[20px] top-[18px] w-[351.968px]" data-name="header">
|
||||
<Frame15 />
|
||||
<Frame12 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DietQuestion() {
|
||||
return (
|
||||
<div className="absolute bg-white h-[1445px] left-0 overflow-clip top-0 w-[393px]" data-name="DietQuestion">
|
||||
<Confetti />
|
||||
<Container />
|
||||
<HeaderText />
|
||||
<Frame37 />
|
||||
<Content />
|
||||
<Header />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MagicItinDay() {
|
||||
return (
|
||||
<div className="bg-white relative size-full" data-name="magic itin - day 5">
|
||||
<DietQuestion />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
996
src/imports/MagicItinSummaryTab.tsx
Normal file
@@ -0,0 +1,996 @@
|
||||
import svgPaths from "./svg-8l5dq4lsp8";
|
||||
import imgPhoto from "figma:asset/895585e16a5ef99513fed52e650cc187c5d00f0a.png";
|
||||
|
||||
function Confetti() {
|
||||
return (
|
||||
<div className="absolute h-[600.579px] left-0 top-[729.79px] w-[392px]" data-name="Confetti">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 32 32">
|
||||
<g id="Confetti" opacity="0">
|
||||
<path d={svgPaths.p30846ac0} fill="var(--fill-0, #F266FF)" id="Vector" />
|
||||
<path d={svgPaths.p1af8f150} id="Vector_2" stroke="var(--stroke-0, #A1FF66)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p2e546900} fill="var(--fill-0, #66FFA8)" id="Vector_3" />
|
||||
<path d={svgPaths.p2a600580} id="Vector_4" stroke="var(--stroke-0, #8C66FF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p3b167700} fill="var(--fill-0, #66D9FF)" id="Vector_5" />
|
||||
<path d={svgPaths.p114f0d00} id="Vector_6" stroke="var(--stroke-0, #66CCFF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p6b82b00} fill="var(--fill-0, #7D66FF)" id="Vector_7" />
|
||||
<path d={svgPaths.p3a1b1dc0} id="Vector_8" stroke="var(--stroke-0, #E066FF)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1f72dd00} id="Vector_9" stroke="var(--stroke-0, #66FFED)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p31508a60} id="Vector_10" stroke="var(--stroke-0, #FFC766)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p3e65c400} id="Vector_11" stroke="var(--stroke-0, #FF6E66)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1ca023c0} id="Vector_12" stroke="var(--stroke-0, #E8FF66)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p36217800} fill="var(--fill-0, #DEFF66)" id="Vector_13" />
|
||||
<path d={svgPaths.p13f60480} id="Vector_14" stroke="var(--stroke-0, #66FFE8)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p125d3600} fill="var(--fill-0, #A6FF66)" id="Vector_15" />
|
||||
<path d={svgPaths.p10f7c300} fill="var(--fill-0, #85FF66)" id="Vector_16" />
|
||||
<path d={svgPaths.p365e9600} fill="var(--fill-0, #66FFBF)" id="Vector_17" />
|
||||
<path d={svgPaths.p1617b600} fill="var(--fill-0, #66FFB5)" id="Vector_18" />
|
||||
<path d={svgPaths.p2f540d00} fill="var(--fill-0, #D966FF)" id="Vector_19" />
|
||||
<path d={svgPaths.p26750500} fill="var(--fill-0, #66FF7F)" id="Vector_20" />
|
||||
<path d={svgPaths.pca2f800} id="Vector_21" stroke="var(--stroke-0, #FFB866)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p130d1600} fill="var(--fill-0, #B8FF66)" id="Vector_22" />
|
||||
<path d={svgPaths.p112ca500} id="Vector_23" stroke="var(--stroke-0, #66FFC9)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p30587f00} id="Vector_24" stroke="var(--stroke-0, #FF66B5)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p2c53d00} fill="var(--fill-0, #FFB866)" id="Vector_25" />
|
||||
<path d={svgPaths.p245b5500} fill="var(--fill-0, #FFDB66)" id="Vector_26" />
|
||||
<path d={svgPaths.p13ac0f00} fill="var(--fill-0, #D4FF66)" id="Vector_27" />
|
||||
<path d={svgPaths.p2d198000} id="Vector_28" stroke="var(--stroke-0, #66FF96)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.pc00e050} id="Vector_29" stroke="var(--stroke-0, #66FFC2)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p1ae38580} id="Vector_30" stroke="var(--stroke-0, #66FF7D)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1d9db000} fill="var(--fill-0, #FFB366)" id="Vector_31" />
|
||||
<path d={svgPaths.pcd62e8} id="Vector_32" stroke="var(--stroke-0, #F266FF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.pa248b00} fill="var(--fill-0, #6699FF)" id="Vector_33" />
|
||||
<path d={svgPaths.p23dc0000} id="Vector_34" stroke="var(--stroke-0, #FF7366)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p21984100} fill="var(--fill-0, #FF6669)" id="Vector_35" />
|
||||
<path d={svgPaths.p315a1e30} fill="var(--fill-0, #FF8266)" id="Vector_36" />
|
||||
<path d={svgPaths.p36d2ba80} id="Vector_37" stroke="var(--stroke-0, #E366FF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.pee931c0} fill="var(--fill-0, #FF66E8)" id="Vector_38" />
|
||||
<path d={svgPaths.p23d70180} fill="var(--fill-0, #66FF8C)" id="Vector_39" />
|
||||
<path d={svgPaths.p3ba87480} fill="var(--fill-0, #66FFF5)" id="Vector_40" />
|
||||
<path d="M14.4383 38.0174L0 4.01739" id="Vector_41" stroke="var(--stroke-0, #FF7566)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1d1bf900} id="Vector_42" stroke="var(--stroke-0, #8AFF66)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p39b4f00} fill="var(--fill-0, #ED66FF)" id="Vector_43" />
|
||||
<path d={svgPaths.p2369e2c0} id="Vector_44" stroke="var(--stroke-0, #FF6682)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p21a2df00} fill="var(--fill-0, #FF66BD)" id="Vector_45" />
|
||||
<path d={svgPaths.p245cc9b0} fill="var(--fill-0, #66FFF2)" id="Vector_46" />
|
||||
<path d={svgPaths.p293abff0} fill="var(--fill-0, #FFB866)" id="Vector_47" />
|
||||
<path d={svgPaths.p28906180} fill="var(--fill-0, #66E6FF)" id="Vector_48" />
|
||||
<path d={svgPaths.p3282b480} fill="var(--fill-0, #FF66ED)" id="Vector_49" />
|
||||
<path d={svgPaths.p3d2fe400} id="Vector_50" stroke="var(--stroke-0, #667AFF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p1fd17c80} fill="var(--fill-0, #FF6666)" id="Vector_51" />
|
||||
<path d={svgPaths.p1ada2c00} fill="var(--fill-0, #FF667A)" id="Vector_52" />
|
||||
<path d={svgPaths.p55e1500} id="Vector_53" stroke="var(--stroke-0, #66D1FF)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p9491b80} fill="var(--fill-0, #FF7566)" id="Vector_54" />
|
||||
<path d={svgPaths.p394df1c0} fill="var(--fill-0, #91FF66)" id="Vector_55" />
|
||||
<path d={svgPaths.p39dd4ef0} fill="var(--fill-0, #CCFF66)" id="Vector_56" />
|
||||
<path d={svgPaths.p38fea80} fill="var(--fill-0, #CCFF66)" id="Vector_57" />
|
||||
<path d={svgPaths.p751e500} fill="var(--fill-0, #66FFA8)" id="Vector_58" />
|
||||
<path d={svgPaths.p3ae84670} fill="var(--fill-0, #75FF66)" id="Vector_59" />
|
||||
<path d={svgPaths.p195a2f80} fill="var(--fill-0, #FF66EB)" id="Vector_60" />
|
||||
<path d={svgPaths.p1c14be00} fill="var(--fill-0, #66FF9E)" id="Vector_61" />
|
||||
<path d={svgPaths.p302dfa00} fill="var(--fill-0, #FF6682)" id="Vector_62" />
|
||||
<path d={svgPaths.p8f80e00} fill="var(--fill-0, #F266FF)" id="Vector_63" />
|
||||
<path d={svgPaths.p37f77100} id="Vector_64" stroke="var(--stroke-0, #66FFEB)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p31b9a200} fill="var(--fill-0, #BAFF66)" id="Vector_65" />
|
||||
<path d={svgPaths.p1cfa9a70} fill="var(--fill-0, #66FFB0)" id="Vector_66" />
|
||||
<path d={svgPaths.p273a0500} fill="var(--fill-0, #66FFF7)" id="Vector_67" />
|
||||
<path d={svgPaths.p1621380} fill="var(--fill-0, #F766FF)" id="Vector_68" />
|
||||
<path d={svgPaths.p32b87800} id="Vector_69" stroke="var(--stroke-0, #FF66ED)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p9a6ab80} id="Vector_70" stroke="var(--stroke-0, #96FF66)" strokeLinecap="round" strokeWidth="4" />
|
||||
<path d={svgPaths.p1261c6f0} fill="var(--fill-0, #FF66E8)" id="Vector_71" />
|
||||
<path d={svgPaths.p31d95b00} fill="var(--fill-0, #6670FF)" id="Vector_72" />
|
||||
<path d={svgPaths.p670ab80} fill="var(--fill-0, #66FF7F)" id="Vector_73" />
|
||||
<path d={svgPaths.p20991b80} fill="var(--fill-0, #DE66FF)" id="Vector_74" />
|
||||
<path d={svgPaths.pb0da100} id="Vector_75" stroke="var(--stroke-0, #BA66FF)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p2dc248a0} id="Vector_76" stroke="var(--stroke-0, #FFAD66)" strokeLinecap="round" strokeWidth="3" />
|
||||
<path d={svgPaths.p1b50ff00} fill="var(--fill-0, #8CFF66)" id="Vector_77" />
|
||||
<path d={svgPaths.p33b66680} fill="var(--fill-0, #99FF66)" id="Vector_78" />
|
||||
<path d={svgPaths.p1f83a980} fill="var(--fill-0, #66FFFF)" id="Vector_79" />
|
||||
<path d="M361.68 6.01758L392 22.0176" id="Vector_80" stroke="var(--stroke-0, #FF66A8)" strokeLinecap="round" strokeWidth="4" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Icon() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[15.984px]" data-name="Icon">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 15.9836 15.9836">
|
||||
<g id="Icon">
|
||||
<path d={svgPaths.pabdaa80} id="Vector" stroke="var(--stroke-0, #4A5565)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33197" />
|
||||
<path d={svgPaths.p23a159d0} id="Vector_2" stroke="var(--stroke-0, #4A5565)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33197" />
|
||||
<path d={svgPaths.p5521d80} id="Vector_3" stroke="var(--stroke-0, #4A5565)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33197" />
|
||||
<path d={svgPaths.p6cb7a00} id="Vector_4" stroke="var(--stroke-0, #4A5565)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.33197" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CompletionScreen() {
|
||||
return (
|
||||
<div className="h-[42px] relative rounded-[36829000px] shrink-0 w-full" data-name="CompletionScreen">
|
||||
<div aria-hidden="true" className="absolute border-[#e5e7eb] border-[1.098px] border-solid inset-0 pointer-events-none rounded-[36829000px]" />
|
||||
<div className="flex flex-row items-center justify-center size-full">
|
||||
<div className="bg-clip-padding border-0 border-[transparent] border-solid content-stretch flex gap-[8px] items-center justify-center px-[111px] py-[12px] relative size-full">
|
||||
<Icon />
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#4a5565] text-[16px] whitespace-nowrap">Start Over</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame() {
|
||||
return (
|
||||
<div className="bg-[#f95f62] h-[42px] relative rounded-[38px] shrink-0 w-full">
|
||||
<div className="flex flex-row items-center justify-center size-full">
|
||||
<div className="bg-clip-padding border-0 border-[transparent] border-solid content-stretch flex items-center justify-center pl-[8px] pr-[12px] py-[12px] relative size-full">
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[16px] text-white whitespace-nowrap">Get My Trip Plan</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Container() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex flex-col gap-[12px] items-start justify-center left-[20.46px] opacity-0 top-[186px] w-[336px]" data-name="Container">
|
||||
<CompletionScreen />
|
||||
<Frame />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HeaderText() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex flex-col gap-[8px] items-start left-[20.46px] not-italic opacity-0 text-center top-[65px] w-[352.015px]" data-name="header text">
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[0] min-w-full relative shrink-0 text-[#101828] text-[24px] tracking-[-0.96px] w-[min-content]">
|
||||
<span className="font-['Poppins:Regular',sans-serif] leading-[1.3] text-[rgba(16,24,40,0.6)]">Your</span>
|
||||
<span className="leading-[1.3]">{` `}</span>
|
||||
<span className="leading-[1.3] text-[#f95f62]">Magic Itinerary</span>
|
||||
<span className="leading-[1.3]">{` `}</span>
|
||||
<span className="font-['Poppins:Regular',sans-serif] leading-[1.3] text-[rgba(16,24,40,0.6)]">is</span>
|
||||
<span className="leading-[1.3]">{` `}</span>
|
||||
<span className="leading-[1.3] text-[#f95f62]">Ready</span>
|
||||
<span className="leading-[1.3]">{` ✨`}</span>
|
||||
</p>
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] relative shrink-0 text-[14px] text-[rgba(0,0,0,0.6)] w-[336px]">{`We've got everything we need to plan your perfect trip`}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame35() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex flex-col font-['Poppins:SemiBold',sans-serif] gap-[6px] items-start leading-[0] left-[20.46px] not-italic text-[#101828] text-center top-[89px] w-[351.979px]">
|
||||
<div className="flex flex-col h-[21.107px] justify-center relative shrink-0 text-[24px] w-full">
|
||||
<p className="leading-[32px]">{`Your `}</p>
|
||||
</div>
|
||||
<div className="flex flex-col h-[21.107px] justify-center relative shrink-0 text-[0px] w-full">
|
||||
<p className="text-[24px]">
|
||||
<span className="leading-[32px] text-[#f95f62]">Melbourne</span>
|
||||
<span className="leading-[32px]">{` `}</span>
|
||||
<span className="leading-[32px] text-[#f95f62]">Magic Itinerary</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Photo() {
|
||||
return (
|
||||
<div className="flex-[1_0_0] min-h-px min-w-px relative rounded-[2px] w-full" data-name="photo">
|
||||
<img alt="" className="absolute inset-0 max-w-none object-cover pointer-events-none rounded-[2px] size-full" src={imgPhoto} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OuterFrame() {
|
||||
return (
|
||||
<div className="bg-white content-stretch flex flex-col items-center overflow-clip relative self-stretch shrink-0 w-[86px]" data-name="outer frame">
|
||||
<Photo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group5() {
|
||||
return (
|
||||
<div className="absolute inset-[11.11%]">
|
||||
<div className="absolute inset-[-0.71%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 14.2 14.2">
|
||||
<g id="Group 1000002093">
|
||||
<path d={svgPaths.p1aba0880} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p1fa04100} fill="var(--fill-0, #F95F62)" id="Vector_2" />
|
||||
<path d={svgPaths.pe6ea010} fill="var(--fill-0, #F95F62)" id="Vector_3" />
|
||||
<path d={svgPaths.pbdbaa00} fill="var(--fill-0, #F95F62)" id="Vector_4" />
|
||||
<path d={svgPaths.p18d2ab00} fill="var(--fill-0, #F95F62)" id="Vector_5" />
|
||||
<path d={svgPaths.p16eea180} fill="var(--fill-0, #F95F62)" id="Vector_6" />
|
||||
<path clipRule="evenodd" d={svgPaths.p3e80b480} fill="var(--fill-0, #F95F62)" fillRule="evenodd" id="Vector_7" stroke="var(--stroke-0, white)" strokeWidth="0.2" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame31() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[18px]">
|
||||
<Group5 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame28() {
|
||||
return (
|
||||
<div className="content-center flex flex-wrap gap-[8px] items-center relative shrink-0">
|
||||
<Frame31 />
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#f95f62] text-[16px] whitespace-nowrap">3 Days</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame32() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[18px]">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 18 18">
|
||||
<g id="Frame 1597882141">
|
||||
<path d={svgPaths.pcef7b00} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame29() {
|
||||
return (
|
||||
<div className="content-center flex flex-wrap gap-[8px] items-center relative shrink-0">
|
||||
<Frame32 />
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#f95f62] text-[16px] whitespace-nowrap">12 stops</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Overview1() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[8px] items-start relative shrink-0 w-[237.5px]" data-name="overview">
|
||||
<Frame28 />
|
||||
<Frame29 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame30() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[8px] items-start relative shrink-0 w-full">
|
||||
<div className="flex flex-col font-['Poppins:Regular',sans-serif] h-[9.672px] justify-center leading-[0] min-w-full not-italic relative shrink-0 text-[10px] text-[rgba(0,0,0,0.8)] uppercase w-[min-content]">
|
||||
<p className="leading-[13.852px]">TRIP DETAILS:</p>
|
||||
</div>
|
||||
<Overview1 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute inset-[12.5%_5.95%_8.33%_8.34%]" data-name="Group">
|
||||
<div className="absolute inset-[-5.26%_0_0_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 12 11.6667">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p4d87d80} fill="var(--fill-0, black)" fillOpacity="0.8" id="Vector" />
|
||||
<path d={svgPaths.p3d2bae00} id="Vector_2" stroke="var(--stroke-0, #999999)" strokeLinecap="round" strokeWidth="1.16667" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LetsIconsDateFill() {
|
||||
return (
|
||||
<div className="overflow-clip relative shrink-0 size-[14px]" data-name="lets-icons:date-fill">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Adults() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] h-[16px] items-center justify-center relative shrink-0" data-name="adults">
|
||||
<LetsIconsDateFill />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[14px] not-italic relative shrink-0 text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px] whitespace-nowrap">22/02/2025</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RiParentFill() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[14px]" data-name="ri:parent-fill">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 14 14">
|
||||
<g id="ri:parent-fill">
|
||||
<path d={svgPaths.p7670300} fill="var(--fill-0, black)" fillOpacity="0.8" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Adults1() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] h-[16px] items-center justify-center relative shrink-0" data-name="adults">
|
||||
<RiParentFill />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[14px] not-italic relative shrink-0 text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px] whitespace-nowrap">3 adults</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FaSolidChild() {
|
||||
return (
|
||||
<div className="h-[14px] relative shrink-0 w-[10.5px]" data-name="fa-solid:child">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 10.5 14">
|
||||
<g clipPath="url(#clip0_9924_1087)" id="fa-solid:child">
|
||||
<path d={svgPaths.p2e64dc00} fill="var(--fill-0, black)" fillOpacity="0.8" id="Vector" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9924_1087">
|
||||
<rect fill="white" height="14" width="10.5" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Kids() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] h-[18.667px] items-center relative shrink-0" data-name="kids">
|
||||
<FaSolidChild />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[14px] not-italic relative shrink-0 text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px] whitespace-nowrap">3 kids</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame9() {
|
||||
return (
|
||||
<div className="content-center flex flex-wrap gap-[12px] items-center relative shrink-0 w-full">
|
||||
<Adults />
|
||||
<Adults1 />
|
||||
<Kids />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame26() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[12px] items-start relative shrink-0 w-[237.5px]">
|
||||
<Frame30 />
|
||||
<Frame9 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame27() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] items-start relative shrink-0 w-full">
|
||||
<OuterFrame />
|
||||
<Frame26 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MaterialSymbolsShare() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[16px]" data-name="material-symbols:share">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
|
||||
<g id="material-symbols:share">
|
||||
<path d={svgPaths.p31f8ed00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame3() {
|
||||
return (
|
||||
<div className="bg-[rgba(249,95,98,0)] flex-[1_0_0] h-[44px] min-h-px min-w-px relative rounded-[8px]">
|
||||
<div className="flex flex-row items-center justify-center overflow-clip rounded-[inherit] size-full">
|
||||
<div className="content-stretch flex gap-[8px] items-center justify-center p-[8px] relative size-full">
|
||||
<MaterialSymbolsShare />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#f95f62] text-[12px] whitespace-nowrap">Share</p>
|
||||
</div>
|
||||
</div>
|
||||
<div aria-hidden="true" className="absolute border border-[#f95f62] border-solid inset-0 pointer-events-none rounded-[8px]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DownloadMinimalisticSvgrepoCom() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[16px]" data-name="download-minimalistic_svgrepo.com">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 16 16">
|
||||
<g id="download-minimalistic_svgrepo.com">
|
||||
<path d={svgPaths.p13f2e80} id="Vector" stroke="var(--stroke-0, #F95F62)" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d={svgPaths.p21309b00} id="Vector_2" stroke="var(--stroke-0, #F95F62)" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame4() {
|
||||
return (
|
||||
<div className="bg-[rgba(249,95,98,0)] flex-[1_0_0] h-[44px] min-h-px min-w-px relative rounded-[8px]">
|
||||
<div className="flex flex-row items-center justify-center overflow-clip rounded-[inherit] size-full">
|
||||
<div className="content-stretch flex gap-[8px] items-center justify-center p-[8px] relative size-full">
|
||||
<DownloadMinimalisticSvgrepoCom />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#f95f62] text-[12px] whitespace-nowrap">Download</p>
|
||||
</div>
|
||||
</div>
|
||||
<div aria-hidden="true" className="absolute border border-[#f95f62] border-solid inset-0 pointer-events-none rounded-[8px]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame5() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[12px] items-center relative shrink-0 w-full">
|
||||
<Frame3 />
|
||||
<Frame4 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Overview() {
|
||||
return (
|
||||
<div className="bg-white content-stretch flex flex-col gap-[8px] items-center p-[8px] relative rounded-[6px] shadow-[0px_23.087px_46.175px_0px_rgba(209,213,220,0.5)] shrink-0 w-[352px]" data-name="Overview">
|
||||
<Frame27 />
|
||||
<div className="h-0 relative shrink-0 w-full">
|
||||
<div className="absolute inset-[-1px_0_0_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 336 1">
|
||||
<line id="Line 631" stroke="var(--stroke-0, black)" strokeLinecap="round" strokeOpacity="0.04" x1="0.5" x2="335.5" y1="0.5" y2="0.5" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<Frame5 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame1() {
|
||||
return (
|
||||
<div className="bg-[#fee7e7] h-[50px] overflow-clip relative rounded-[100px] shrink-0 w-[353px]">
|
||||
<div className="absolute bg-white content-stretch flex items-center justify-center left-[182.5px] overflow-clip px-[24px] py-[12px] rounded-[100px] top-[3.66px] w-[167px]" data-name="Button">
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#2a2a2a] text-[16px] whitespace-nowrap">Summary</p>
|
||||
</div>
|
||||
<div className="absolute content-stretch flex items-center justify-center left-[4px] overflow-clip px-[24px] py-[12px] rounded-[100px] top-[4px] w-[174px]" data-name="Button">
|
||||
<p className="font-['Poppins:Medium',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#2a2a2a] text-[16px] whitespace-nowrap">Daily View</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame33() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center relative shrink-0">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#212121] text-[16px] whitespace-nowrap">Day 1:</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group1() {
|
||||
return (
|
||||
<div className="absolute inset-[12.5%_8.33%_8.33%_8.33%]" data-name="Group">
|
||||
<div className="absolute inset-[-5.26%_0_0_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 11.6667 11.6667">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p34b84f00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p219a1600} id="Vector_2" stroke="var(--stroke-0, #F95F62)" strokeLinecap="round" strokeWidth="1.16667" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LetsIconsDateFill1() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[14px]" data-name="lets-icons:date-fill">
|
||||
<Group1 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Adults2() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[4px] h-[16px] items-center justify-center relative shrink-0" data-name="adults">
|
||||
<LetsIconsDateFill1 />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#f95f62] text-[12px] whitespace-nowrap">21/02/2025</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame10() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center justify-between relative shrink-0 w-full">
|
||||
<Frame33 />
|
||||
<Adults2 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame11() {
|
||||
return (
|
||||
<div className="bg-[#fee7e7] relative rounded-[5px] shrink-0 w-full">
|
||||
<div className="flex flex-row items-center size-full">
|
||||
<div className="content-stretch flex items-center justify-between px-[14px] py-[10px] relative size-full">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] not-italic relative shrink-0 text-[#212121] text-[14px] whitespace-nowrap">09:00 am: Melbourne Zoo</p>
|
||||
<div className="flex h-[5px] items-center justify-center relative shrink-0 w-[10px]" style={{ "--transform-inner-width": "1185", "--transform-inner-height": "21" } as React.CSSProperties}>
|
||||
<div className="-scale-y-100 flex-none rotate-90">
|
||||
<div className="h-[10px] relative w-[5px]">
|
||||
<div className="absolute inset-[-6.25%_-12.5%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 6.25 11.25">
|
||||
<path d={svgPaths.pec20f00} id="Vector 796" stroke="var(--stroke-0, black)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.25" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame15() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center justify-between relative shrink-0 w-[303px]">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] not-italic relative shrink-0 text-[#212121] text-[14px] whitespace-nowrap">01:00 pm: Melbourne Skydeck</p>
|
||||
<div className="flex h-[5px] items-center justify-center relative shrink-0 w-[10px]" style={{ "--transform-inner-width": "1185", "--transform-inner-height": "21" } as React.CSSProperties}>
|
||||
<div className="-scale-y-100 flex-none rotate-90">
|
||||
<div className="h-[10px] relative w-[5px]">
|
||||
<div className="absolute inset-[-6.25%_-12.5%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 6.25 11.25">
|
||||
<path d={svgPaths.pec20f00} id="Vector 796" stroke="var(--stroke-0, black)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.25" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group2() {
|
||||
return (
|
||||
<div className="absolute inset-[8.33%_12.5%_0.78%_12.5%]" data-name="Group">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 12 14.5427">
|
||||
<g id="Group">
|
||||
<g id="Vector" />
|
||||
<path clipRule="evenodd" d={svgPaths.p31a57b00} fill="var(--fill-0, white)" fillRule="evenodd" id="Vector_2" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MingcuteLocationLine() {
|
||||
return (
|
||||
<div className="overflow-clip relative shrink-0 size-[16px]" data-name="mingcute:location-line">
|
||||
<Group2 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame7() {
|
||||
return (
|
||||
<div className="bg-[#f95f62] content-stretch flex gap-[6px] items-center justify-center px-[12px] py-[8px] relative rounded-[100px] shrink-0">
|
||||
<MingcuteLocationLine />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[14px] not-italic relative shrink-0 text-[11px] text-white tracking-[0.06px] whitespace-nowrap">Get directions</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame14() {
|
||||
return (
|
||||
<div className="bg-[#fee7e7] relative rounded-[5px] shrink-0 w-full">
|
||||
<div className="flex flex-col justify-center size-full">
|
||||
<div className="content-stretch flex flex-col gap-[10px] items-start justify-center px-[14px] py-[10px] relative size-full">
|
||||
<Frame15 />
|
||||
<ul className="block font-['Poppins:Regular',sans-serif] leading-[0] list-disc min-w-full not-italic relative shrink-0 text-[12px] text-[rgba(0,0,0,0.8)] w-[min-content]">
|
||||
<li className="mb-0 ms-[18px]">
|
||||
<span className="leading-[20px]">Take in the breathtaking views from the summit overlooking Melbourne.</span>
|
||||
</li>
|
||||
<li className="mb-0 ms-[18px]">
|
||||
<span className="leading-[20px]">Try out the infamous sky dining experience</span>
|
||||
</li>
|
||||
<li className="ms-[18px]">
|
||||
<span className="leading-[20px]">Have a peek at the award winning voyager theatre</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Frame7 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame13() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[10px] items-start relative shrink-0 w-full">
|
||||
<Frame11 />
|
||||
<Frame14 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame12() {
|
||||
return (
|
||||
<div className="relative rounded-[10px] shrink-0 w-full">
|
||||
<div aria-hidden="true" className="absolute border border-[#f95f62] border-solid inset-0 pointer-events-none rounded-[10px]" />
|
||||
<div className="content-stretch flex flex-col gap-[22px] items-start px-[8px] py-[12px] relative size-full">
|
||||
<Frame10 />
|
||||
<Frame13 />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame34() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center relative shrink-0">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#212121] text-[16px] whitespace-nowrap">Day 2:</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group3() {
|
||||
return (
|
||||
<div className="absolute inset-[12.5%_8.33%_8.33%_8.33%]" data-name="Group">
|
||||
<div className="absolute inset-[-5.26%_0_0_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 11.6667 11.6667">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p34b84f00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p219a1600} id="Vector_2" stroke="var(--stroke-0, #F95F62)" strokeLinecap="round" strokeWidth="1.16667" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LetsIconsDateFill2() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[14px]" data-name="lets-icons:date-fill">
|
||||
<Group3 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Adults3() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[4px] h-[16px] items-center justify-center relative shrink-0" data-name="adults">
|
||||
<LetsIconsDateFill2 />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#f95f62] text-[12px] whitespace-nowrap">21/02/2025</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame17() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center justify-between relative shrink-0 w-full">
|
||||
<Frame34 />
|
||||
<Adults3 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame19() {
|
||||
return (
|
||||
<div className="bg-[#fee7e7] relative rounded-[5px] shrink-0 w-full">
|
||||
<div className="flex flex-row items-center size-full">
|
||||
<div className="content-stretch flex items-center justify-between px-[14px] py-[10px] relative size-full">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] not-italic relative shrink-0 text-[#212121] text-[14px] whitespace-nowrap">09:00 am: Royal Botanic Gardens Victoria</p>
|
||||
<div className="flex h-[5px] items-center justify-center relative shrink-0 w-[10px]" style={{ "--transform-inner-width": "1185", "--transform-inner-height": "21" } as React.CSSProperties}>
|
||||
<div className="-scale-y-100 flex-none rotate-90">
|
||||
<div className="h-[10px] relative w-[5px]">
|
||||
<div className="absolute inset-[-6.25%_-12.5%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 6.25 11.25">
|
||||
<path d={svgPaths.pec20f00} id="Vector 796" stroke="var(--stroke-0, black)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.25" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame20() {
|
||||
return (
|
||||
<div className="bg-[#fee7e7] relative rounded-[5px] shrink-0 w-full">
|
||||
<div className="flex flex-row items-center size-full">
|
||||
<div className="content-stretch flex items-center justify-between px-[14px] py-[10px] relative size-full">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] not-italic relative shrink-0 text-[#212121] text-[14px] whitespace-nowrap">01:00 pm: National Gallery of Victoria</p>
|
||||
<div className="flex h-[5px] items-center justify-center relative shrink-0 w-[10px]" style={{ "--transform-inner-width": "1185", "--transform-inner-height": "21" } as React.CSSProperties}>
|
||||
<div className="-scale-y-100 flex-none rotate-90">
|
||||
<div className="h-[10px] relative w-[5px]">
|
||||
<div className="absolute inset-[-6.25%_-12.5%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 6.25 11.25">
|
||||
<path d={svgPaths.pec20f00} id="Vector 796" stroke="var(--stroke-0, black)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.25" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame18() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[10px] items-start relative shrink-0 w-full">
|
||||
<Frame19 />
|
||||
<Frame20 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame16() {
|
||||
return (
|
||||
<div className="relative rounded-[10px] shrink-0 w-full">
|
||||
<div aria-hidden="true" className="absolute border border-[rgba(249,95,98,0.12)] border-solid inset-0 pointer-events-none rounded-[10px]" />
|
||||
<div className="content-stretch flex flex-col gap-[22px] items-start px-[8px] py-[12px] relative size-full">
|
||||
<Frame17 />
|
||||
<Frame18 />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame36() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center relative shrink-0">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[18px] not-italic relative shrink-0 text-[#212121] text-[16px] whitespace-nowrap">Day 3:</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group4() {
|
||||
return (
|
||||
<div className="absolute inset-[12.5%_8.33%_8.33%_8.33%]" data-name="Group">
|
||||
<div className="absolute inset-[-5.26%_0_0_0]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 11.6667 11.6667">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p34b84f00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p219a1600} id="Vector_2" stroke="var(--stroke-0, #F95F62)" strokeLinecap="round" strokeWidth="1.16667" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LetsIconsDateFill3() {
|
||||
return (
|
||||
<div className="relative shrink-0 size-[14px]" data-name="lets-icons:date-fill">
|
||||
<Group4 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Adults4() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[4px] h-[16px] items-center justify-center relative shrink-0" data-name="adults">
|
||||
<LetsIconsDateFill3 />
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[#f95f62] text-[12px] whitespace-nowrap">21/02/2025</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame22() {
|
||||
return (
|
||||
<div className="content-stretch flex items-center justify-between relative shrink-0 w-full">
|
||||
<Frame36 />
|
||||
<Adults4 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame24() {
|
||||
return (
|
||||
<div className="bg-[#fee7e7] relative rounded-[5px] shrink-0 w-full">
|
||||
<div className="flex flex-row items-center size-full">
|
||||
<div className="content-stretch flex items-center justify-between px-[14px] py-[10px] relative size-full">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] not-italic relative shrink-0 text-[#212121] text-[14px] whitespace-nowrap">09:00 am: Queens Victoria Market</p>
|
||||
<div className="flex h-[5px] items-center justify-center relative shrink-0 w-[10px]" style={{ "--transform-inner-width": "1185", "--transform-inner-height": "21" } as React.CSSProperties}>
|
||||
<div className="-scale-y-100 flex-none rotate-90">
|
||||
<div className="h-[10px] relative w-[5px]">
|
||||
<div className="absolute inset-[-6.25%_-12.5%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 6.25 11.25">
|
||||
<path d={svgPaths.pec20f00} id="Vector 796" stroke="var(--stroke-0, black)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.25" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame25() {
|
||||
return (
|
||||
<div className="bg-[#fee7e7] relative rounded-[5px] shrink-0 w-full">
|
||||
<div className="flex flex-row items-center size-full">
|
||||
<div className="content-stretch flex items-center justify-between px-[14px] py-[10px] relative size-full">
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[1.6] not-italic relative shrink-0 text-[#212121] text-[14px] whitespace-nowrap">{`04:30 pm: St Kilda Beach & Pier`}</p>
|
||||
<div className="flex h-[5px] items-center justify-center relative shrink-0 w-[10px]" style={{ "--transform-inner-width": "1185", "--transform-inner-height": "21" } as React.CSSProperties}>
|
||||
<div className="-scale-y-100 flex-none rotate-90">
|
||||
<div className="h-[10px] relative w-[5px]">
|
||||
<div className="absolute inset-[-6.25%_-12.5%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 6.25 11.25">
|
||||
<path d={svgPaths.pec20f00} id="Vector 796" stroke="var(--stroke-0, black)" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.25" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame23() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[10px] items-start relative shrink-0 w-full">
|
||||
<Frame24 />
|
||||
<Frame25 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame21() {
|
||||
return (
|
||||
<div className="relative rounded-[10px] shrink-0 w-full">
|
||||
<div aria-hidden="true" className="absolute border border-[rgba(249,95,98,0.12)] border-solid inset-0 pointer-events-none rounded-[10px]" />
|
||||
<div className="content-stretch flex flex-col gap-[22px] items-start px-[8px] py-[12px] relative size-full">
|
||||
<Frame22 />
|
||||
<Frame23 />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame2() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[24px] items-center relative shrink-0 w-[353px]">
|
||||
<Frame12 />
|
||||
<Frame16 />
|
||||
<Frame21 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ItineraryDetails() {
|
||||
return (
|
||||
<div className="content-stretch flex flex-col gap-[24px] h-[1019px] items-center relative shrink-0 w-full" data-name="itinerary details">
|
||||
<Frame1 />
|
||||
<Frame2 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Content() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex flex-col gap-[24px] h-[1237px] items-center left-0 top-[172px] w-[392px]" data-name="content">
|
||||
<Overview />
|
||||
<ItineraryDetails />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ArrowRightLine() {
|
||||
return (
|
||||
<div className="relative size-[24px]" data-name="arrow-right-line">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 24 24">
|
||||
<g id="arrow-right-line">
|
||||
<path d={svgPaths.p15a24580} fill="var(--fill-0, #212121)" fillOpacity="0.4" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame8() {
|
||||
return (
|
||||
<div className="content-stretch flex gap-[8px] items-center py-[12px] relative shrink-0 w-[76px]">
|
||||
<div className="flex items-center justify-center relative shrink-0">
|
||||
<div className="flex-none rotate-180">
|
||||
<ArrowRightLine />
|
||||
</div>
|
||||
</div>
|
||||
<p className="font-['Poppins:Regular',sans-serif] leading-[16px] not-italic relative shrink-0 text-[12px] text-black whitespace-nowrap">Back</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Arrow() {
|
||||
return (
|
||||
<div className="absolute h-[3.274px] left-[10.64px] top-[11.11px] w-[3.293px]" data-name="arrow">
|
||||
<div className="absolute inset-[0_-3.43%_-8.67%_-8.62%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 3.69019 3.55896">
|
||||
<g id="arrow">
|
||||
<path d={svgPaths.p1a652670} fill="var(--stroke-0, #F95F62)" id="Line 13" />
|
||||
<path d={svgPaths.p28638300} fill="var(--stroke-0, #F95F62)" id="Line 14" />
|
||||
<path d={svgPaths.p2ce32bc0} fill="var(--stroke-0, #F95F62)" id="Line 15" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Logo() {
|
||||
return (
|
||||
<div className="absolute contents left-[4.57px] top-[4px]" data-name="logo">
|
||||
<div className="-translate-y-1/2 absolute flex flex-col font-['Chillax:Medium',sans-serif] h-[14.097px] justify-center leading-[0] left-[4.57px] not-italic text-[#f95f62] text-[19.657px] top-[12.95px] tracking-[-1.1794px] w-[13.265px]">
|
||||
<p className="leading-[normal]">C</p>
|
||||
</div>
|
||||
<div className="absolute left-[6.36px] size-[1.329px] top-[5.24px]">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1.3292 1.3292">
|
||||
<circle cx="0.6646" cy="0.6646" fill="var(--fill-0, #F95F62)" id="Ellipse 4" r="0.6646" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="absolute h-[1.411px] left-[8.64px] top-[4px] w-[1.329px]">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1.3292 1.41095">
|
||||
<ellipse cx="0.6646" cy="0.705473" fill="var(--fill-0, #F95F62)" id="Ellipse 5" rx="0.6646" ry="0.705473" />
|
||||
</svg>
|
||||
</div>
|
||||
<Arrow />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Frame6() {
|
||||
return (
|
||||
<div className="bg-[rgba(249,95,98,0.12)] content-stretch flex items-center justify-center relative rounded-[62px] shrink-0 size-[24px]">
|
||||
<Logo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
<div className="absolute content-stretch flex items-center justify-between left-[20px] top-[18px] w-[351.968px]" data-name="header">
|
||||
<Frame8 />
|
||||
<Frame6 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DietQuestion() {
|
||||
return (
|
||||
<div className="absolute bg-white h-[1163px] left-0 overflow-clip top-0 w-[393px]" data-name="DietQuestion">
|
||||
<Confetti />
|
||||
<Container />
|
||||
<HeaderText />
|
||||
<Frame35 />
|
||||
<Content />
|
||||
<Header />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MagicItinSummaryTab() {
|
||||
return (
|
||||
<div className="bg-white relative size-full" data-name="magic itin - summary tab">
|
||||
<DietQuestion />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/imports/NounRelax65970551-9919-186.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import svgPaths from "./svg-wjtpqfkmjn";
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="relative size-full" data-name="noun-relax-6597055 1">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 100 100">
|
||||
<g id="noun-relax-6597055 1">
|
||||
<path d={svgPaths.p1bfdb500} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #FFF3F3)" strokeWidth="0.4" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
32
src/imports/NounRelax65970551-9919-190.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import svgPaths from "./svg-3e945qfv3w";
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute inset-[19%_8.76%_19%_8.75%]">
|
||||
<div className="absolute inset-[-0.16%_0_-0.16%_-0.12%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 82.6407 62.2">
|
||||
<g id="Group 1000002086">
|
||||
<path d={svgPaths.p30d09400} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #FFF3F3)" strokeWidth="0.1" />
|
||||
<g id="Vector_2">
|
||||
<path d={svgPaths.p187f9700} fill="var(--fill-0, #F95F62)" />
|
||||
<path clipRule="evenodd" d={svgPaths.p3eab2600} fill="var(--fill-0, #F95F62)" fillRule="evenodd" />
|
||||
<path clipRule="evenodd" d={svgPaths.p3c4db480} fill="var(--fill-0, #F95F62)" fillRule="evenodd" />
|
||||
<path clipRule="evenodd" d={svgPaths.p35fc7b00} fill="var(--fill-0, #F95F62)" fillRule="evenodd" />
|
||||
<path clipRule="evenodd" d={svgPaths.p2c937b40} fill="var(--fill-0, #F95F62)" fillRule="evenodd" />
|
||||
<path clipRule="evenodd" d={svgPaths.p2dd59000} fill="var(--fill-0, #F95F62)" fillRule="evenodd" />
|
||||
<path d={svgPaths.p18421100} stroke="var(--stroke-0, #FFF3F3)" strokeWidth="0.2" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="overflow-clip relative rounded-[19.048px] size-full" data-name="noun-relax-6597055 1">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/imports/NounRelax65970551-9919-366.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import svgPaths from "./svg-nvjqsh4m9x";
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="relative size-full" data-name="noun-relax-6597055 1">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 62 62">
|
||||
<g id="noun-relax-6597055 1">
|
||||
<path d={svgPaths.pf613c00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/imports/NounRelax65970551-9919-370.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import svgPaths from "./svg-rpecqp6li2";
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="relative size-full" data-name="noun-relax-6597055 1">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 62 62">
|
||||
<g id="noun-relax-6597055 1">
|
||||
<path d={svgPaths.p32a3dd80} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
src/imports/NounRelax65970551-9919-374.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import svgPaths from "./svg-u2edxj8dia";
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute inset-[18.8%_13.05%]">
|
||||
<div className="absolute inset-[-0.52%_-0.44%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 46.2181 39.09">
|
||||
<g id="Group 1000002089">
|
||||
<path d={svgPaths.p1d2c1e00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p11d0e900} fill="var(--fill-0, #F95F62)" id="Vector_2" />
|
||||
<path d={svgPaths.p17868d00} fill="var(--fill-0, #F95F62)" id="Vector_3" />
|
||||
<path d={svgPaths.p1cd27a80} fill="var(--fill-0, #F95F62)" id="Vector_4" />
|
||||
<path d={svgPaths.p1df26700} fill="var(--fill-0, #F95F62)" id="Vector_5" />
|
||||
<path d={svgPaths.p15b7cd00} fill="var(--fill-0, #F95F62)" id="Vector_6" />
|
||||
<path d={svgPaths.p18f61d00} fill="var(--fill-0, #F95F62)" id="Vector_7" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="overflow-clip relative rounded-[8px] size-full" data-name="noun-relax-6597055 1">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/imports/NounRelax65970551-9919-378.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import svgPaths from "./svg-kj92221mds";
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="relative size-full" data-name="noun-relax-6597055 1">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 62 62">
|
||||
<g id="noun-relax-6597055 1">
|
||||
<path d={svgPaths.p2c67c800} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
src/imports/NounRelax65970551-9919-382.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import svgPaths from "./svg-qafz9g9knb";
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute inset-[18.8%_20.75%_18.8%_20.74%]">
|
||||
<div className="absolute inset-[-0.13%_-0.14%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 36.3759 38.7903">
|
||||
<g id="Group 1000002088">
|
||||
<path d={svgPaths.p25c1fb00} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p1315a800} fill="var(--fill-0, #F95F62)" id="Vector_2" />
|
||||
<path d={svgPaths.p5c4670} fill="var(--fill-0, #F95F62)" id="Vector_3" />
|
||||
<path d={svgPaths.p27c0e980} fill="var(--fill-0, #F95F62)" id="Vector_4" />
|
||||
<path d={svgPaths.p175044f0} fill="var(--fill-0, #F95F62)" id="Vector_5" />
|
||||
<path d={svgPaths.p1b76b0f0} fill="var(--fill-0, #F95F62)" id="Vector_6" />
|
||||
<path d={svgPaths.p6408e00} fill="var(--fill-0, #F95F62)" id="Vector_7" />
|
||||
<path d={svgPaths.p35ead8c0} fill="var(--fill-0, #F95F62)" id="Vector_8" />
|
||||
<path d={svgPaths.p8ca8800} fill="var(--fill-0, #F95F62)" id="Vector_9" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="overflow-clip relative rounded-[8px] size-full" data-name="noun-relax-6597055 1">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
553
src/imports/NounRelax65970551-9919-386.tsx
Normal file
@@ -0,0 +1,553 @@
|
||||
import svgPaths from "./svg-qnagtgq03x";
|
||||
|
||||
function Group1() {
|
||||
return (
|
||||
<div className="absolute inset-[59.5%_33.65%_22.05%_65.27%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.44%_-7.45%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 0.771057 11.5419">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p1b540400} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group2() {
|
||||
return (
|
||||
<div className="absolute inset-[23.07%_37.26%_18.8%_45.55%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.14%_-0.47%_-0.14%_-0.46%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 10.7594 36.1445">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p3450f000} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group3() {
|
||||
return (
|
||||
<div className="absolute inset-[23.07%_34.68%_50.35%_58.86%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.3%_-1.24%_-0.3%_-1.26%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 4.10206 16.5821">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p1b618330} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group4() {
|
||||
return (
|
||||
<div className="absolute inset-[18.87%_38.46%_75.85%_50.36%]" data-name="Group">
|
||||
<div className="absolute inset-[-1.53%_-0.72%_-1.52%_-0.73%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 7.02982 3.37534">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p35daba00} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group5() {
|
||||
return (
|
||||
<div className="absolute inset-[52.94%_36.78%_45.98%_60.13%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-2.61%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 2.01627 0.77106">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p2f242100} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group6() {
|
||||
return (
|
||||
<div className="absolute inset-[52.94%_42.21%_45.98%_45.71%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-0.67%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 7.5846 0.77106">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p21290430} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group7() {
|
||||
return (
|
||||
<div className="absolute inset-[67.24%_33.65%_31.68%_45.71%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.44%_-0.39%_-7.47%_-0.39%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 12.8961 0.771057">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p2f0cc900} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group9() {
|
||||
return (
|
||||
<div className="absolute inset-[45.7%_19.68%_35.61%_63.58%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.43%_-0.48%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 10.476 11.6873">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p3401a40} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group10() {
|
||||
return (
|
||||
<div className="absolute inset-[63.31%_27.51%_21.53%_71.41%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.53%_-7.51%_-0.53%_-7.39%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 0.771057 9.5045">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p206c6580} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group11() {
|
||||
return (
|
||||
<div className="absolute inset-[77.62%_20.78%_18.8%_64.72%]" data-name="Group">
|
||||
<div className="absolute inset-[-2.25%_-0.56%_-2.25%_-0.55%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 9.09225 2.32273">
|
||||
<g id="Group">
|
||||
<g id="Vector">
|
||||
<path d={svgPaths.p1dc35100} fill="var(--fill-0, #F95F62)" />
|
||||
<path d={svgPaths.p3ce0580} stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group12() {
|
||||
return (
|
||||
<div className="absolute inset-[54.66%_19.94%_41.64%_63.89%]" data-name="Group">
|
||||
<div className="absolute inset-[-2.18%_-0.5%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 10.1247 2.39298">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p2051aa80} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group8() {
|
||||
return (
|
||||
<div className="absolute contents inset-[45.7%_19.68%_18.8%_63.58%]" data-name="Group">
|
||||
<Group9 />
|
||||
<Group10 />
|
||||
<Group11 />
|
||||
<Group12 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group14() {
|
||||
return (
|
||||
<div className="absolute inset-[45.49%_63.28%_50.09%_19.68%]" data-name="Group">
|
||||
<div className="absolute inset-[-1.83%_-0.47%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 10.6625 2.83753">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p3c314100} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group15() {
|
||||
return (
|
||||
<div className="absolute inset-[48.83%_65.61%_36.3%_22.01%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.54%_-0.65%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 7.77719 9.32281">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p332aed00} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group16() {
|
||||
return (
|
||||
<div className="absolute inset-[62.4%_69.7%_24.8%_26.1%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.63%_-1.92%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 2.70671 8.03156">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p34c8570} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group17() {
|
||||
return (
|
||||
<div className="absolute inset-[52.66%_65.61%_46.25%_22.01%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-0.65%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 7.77598 0.771057">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p35856480} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group18() {
|
||||
return (
|
||||
<div className="absolute inset-[55.02%_70.37%_43.9%_22.01%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-1.06%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 4.82648 0.77106">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p119fe300} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group19() {
|
||||
return (
|
||||
<div className="absolute inset-[55.02%_65.61%_43.9%_31.59%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-2.88%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 1.83821 0.77106">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p1a7ca200} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group20() {
|
||||
return (
|
||||
<div className="absolute inset-[32.48%_67.46%_53.42%_23.85%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.57%_-0.94%_-0.57%_-0.92%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 5.48785 8.83708">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p39705400} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group21() {
|
||||
return (
|
||||
<div className="absolute inset-[29.67%_71.26%_66.43%_27.66%]" data-name="Group">
|
||||
<div className="absolute inset-[-2.07%_-7.4%_-2.06%_-7.5%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 0.771058 2.51654">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p2c63c800} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group22() {
|
||||
return (
|
||||
<div className="absolute inset-[74.12%_64.91%_18.8%_21.31%]" data-name="Group">
|
||||
<div className="absolute inset-[-1.14%_-0.59%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 8.63964 4.49337">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p1aa58800} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group23() {
|
||||
return (
|
||||
<div className="absolute inset-[77.66%_75.18%_21.26%_21.5%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-2.44%_-7.45%_-2.41%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 2.16405 0.771057">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p3263c200} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group24() {
|
||||
return (
|
||||
<div className="absolute inset-[77.66%_65.09%_21.26%_26.65%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-0.98%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 5.22015 0.771057">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p32a21d80} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group25() {
|
||||
return (
|
||||
<div className="absolute inset-[18.8%_69.1%_71.62%_25.5%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.84%_-1.5%_-0.84%_-1.49%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 3.4456 6.03777">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p200bb800} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group13() {
|
||||
return (
|
||||
<div className="absolute contents inset-[18.8%_63.28%_18.8%_19.68%]" data-name="Group">
|
||||
<Group14 />
|
||||
<Group15 />
|
||||
<Group16 />
|
||||
<Group17 />
|
||||
<Group18 />
|
||||
<Group19 />
|
||||
<Group20 />
|
||||
<Group21 />
|
||||
<Group22 />
|
||||
<Group23 />
|
||||
<Group24 />
|
||||
<Group25 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group27() {
|
||||
return (
|
||||
<div className="absolute inset-[48.83%_52.92%_50.09%_37.3%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.47%_-0.83%_-7.44%_-0.82%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 6.16132 0.77106">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p2ceb9900} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group28() {
|
||||
return (
|
||||
<div className="absolute inset-[45.49%_51.06%_53.42%_37.3%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-0.69%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 7.3169 0.771057">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p5cab80} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group29() {
|
||||
return (
|
||||
<div className="absolute inset-[48.83%_52.34%_36.3%_38.35%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.54%_-0.86%_-0.54%_-0.87%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 5.87061 9.32281">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p2040a800} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group30() {
|
||||
return (
|
||||
<div className="absolute inset-[62.4%_53.37%_24.8%_42.44%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.63%_-1.92%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 2.69823 8.03156">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p15afe300} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group31() {
|
||||
return (
|
||||
<div className="absolute inset-[52.66%_56.01%_46.25%_38.35%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-1.43%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 3.59701 0.771057">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p20329b80} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group32() {
|
||||
return (
|
||||
<div className="absolute inset-[55.02%_53.36%_43.9%_38.35%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-0.97%_-7.45%_-0.98%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 5.23953 0.77106">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.pcabba00} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group33() {
|
||||
return (
|
||||
<div className="absolute inset-[32.48%_51.12%_53.42%_40.19%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.57%_-0.92%_-0.57%_-0.94%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 5.48785 8.83708">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p88545c0} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group34() {
|
||||
return (
|
||||
<div className="absolute inset-[29.67%_54.92%_66.43%_44%]" data-name="Group">
|
||||
<div className="absolute inset-[-2.07%_-7.45%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 0.77106 2.51654">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p2b2e2f00} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group35() {
|
||||
return (
|
||||
<div className="absolute inset-[74.12%_53.36%_18.8%_37.65%]" data-name="Group">
|
||||
<div className="absolute inset-[-1.14%_-0.9%_-1.14%_-0.89%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 5.67317 4.49337">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p37797c00} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group36() {
|
||||
return (
|
||||
<div className="absolute inset-[77.66%_53.18%_21.26%_37.83%]" data-name="Group">
|
||||
<div className="absolute inset-[-7.45%_-0.9%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 5.67196 0.771057">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.pbe42b80} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group37() {
|
||||
return (
|
||||
<div className="absolute inset-[18.8%_52.76%_71.62%_41.84%]" data-name="Group">
|
||||
<div className="absolute inset-[-0.84%_-1.5%_-0.84%_-1.49%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 3.4456 6.03777">
|
||||
<g id="Group">
|
||||
<path d={svgPaths.p26102900} fill="var(--fill-0, #F95F62)" id="Vector" stroke="var(--stroke-0, #F95F62)" strokeWidth="0.1" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group26() {
|
||||
return (
|
||||
<div className="absolute contents inset-[18.8%_51.06%_18.8%_37.3%]" data-name="Group">
|
||||
<Group27 />
|
||||
<Group28 />
|
||||
<Group29 />
|
||||
<Group30 />
|
||||
<Group31 />
|
||||
<Group32 />
|
||||
<Group33 />
|
||||
<Group34 />
|
||||
<Group35 />
|
||||
<Group36 />
|
||||
<Group37 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute contents inset-[18.8%_19.68%]" data-name="Group">
|
||||
<Group1 />
|
||||
<Group2 />
|
||||
<Group3 />
|
||||
<Group4 />
|
||||
<Group5 />
|
||||
<Group6 />
|
||||
<Group7 />
|
||||
<Group8 />
|
||||
<Group13 />
|
||||
<Group26 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="overflow-clip relative rounded-[8px] size-full" data-name="noun-relax-6597055 1">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
src/imports/NounRelax65970551-9923-625.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import svgPaths from "./svg-bfd6jik9me";
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="relative size-full" data-name="noun-relax-6597055 1">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 62 62">
|
||||
<g id="noun-relax-6597055 1">
|
||||
<g id="Vector">
|
||||
<path clipRule="evenodd" d={svgPaths.p9958380} fill="var(--fill-0, #F95F62)" fillRule="evenodd" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
src/imports/NounRelax65970551-9923-629.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import svgPaths from "./svg-jhl6jizeq8";
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute inset-[18.8%_7.64%]">
|
||||
<div className="absolute inset-[-0.87%_-0.64%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 53.2001 39.3637">
|
||||
<g id="Group 1000002091">
|
||||
<path d={svgPaths.p1f010b80} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p3d025700} fill="var(--fill-0, #F95F62)" id="Vector_2" />
|
||||
<path d={svgPaths.p14022570} fill="var(--fill-0, #F95F62)" id="Vector_3" />
|
||||
<path d={svgPaths.p6506f00} fill="var(--fill-0, #F95F62)" id="Vector_4" />
|
||||
<path d={svgPaths.p294a4200} fill="var(--fill-0, #F95F62)" id="Vector_5" />
|
||||
<path d={svgPaths.p13d26c70} fill="var(--fill-0, #F95F62)" id="Vector_6" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="overflow-clip relative rounded-[8px] size-full" data-name="noun-relax-6597055 1">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
src/imports/NounRelax65970551-9923-633.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import svgPaths from "./svg-70pmm871bv";
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute inset-[18.8%_20.26%]">
|
||||
<div className="absolute inset-[-0.52%_-0.54%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 37.279 39.09">
|
||||
<g id="Group 1000002090">
|
||||
<path d={svgPaths.p2d70fa80} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p17231c80} fill="var(--fill-0, #F95F62)" id="Vector_2" />
|
||||
<path d={svgPaths.p2009e400} fill="var(--fill-0, #F95F62)" id="Vector_3" />
|
||||
<path d={svgPaths.p7bc2000} fill="var(--fill-0, #F95F62)" id="Vector_4" />
|
||||
<path d={svgPaths.pbcf8200} fill="var(--fill-0, #F95F62)" id="Vector_5" />
|
||||
<path d={svgPaths.p1e3ba380} fill="var(--fill-0, #F95F62)" id="Vector_6" />
|
||||
<path d={svgPaths.p34abd3f0} fill="var(--fill-0, #F95F62)" id="Vector_7" />
|
||||
<path d={svgPaths.p3f923a00} fill="var(--fill-0, #F95F62)" id="Vector_8" />
|
||||
<path d={svgPaths.p1b51ae80} fill="var(--fill-0, #F95F62)" id="Vector_9" />
|
||||
<path d={svgPaths.pfd3f900} fill="var(--fill-0, #F95F62)" id="Vector_10" />
|
||||
<path d={svgPaths.p3bcea400} fill="var(--fill-0, #F95F62)" id="Vector_11" />
|
||||
<path d={svgPaths.p1c5d9f00} fill="var(--fill-0, #F95F62)" id="Vector_12" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="overflow-clip relative rounded-[8px] size-full" data-name="noun-relax-6597055 1">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
src/imports/NounRelax65970551-9923-637.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import svgPaths from "./svg-5q6zbixxri";
|
||||
|
||||
function Group() {
|
||||
return (
|
||||
<div className="absolute inset-[18.8%_16.69%]">
|
||||
<div className="absolute inset-[-0.39%_-0.36%]">
|
||||
<svg className="block size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 41.6008 38.99">
|
||||
<g id="Group 1000002092">
|
||||
<path d={svgPaths.p19680b80} fill="var(--fill-0, #F95F62)" id="Vector" />
|
||||
<path d={svgPaths.p1ff00f00} fill="var(--fill-0, #F95F62)" id="Vector_2" />
|
||||
<path d={svgPaths.p24992980} fill="var(--fill-0, #F95F62)" id="Vector_3" />
|
||||
<path d={svgPaths.p37896700} fill="var(--fill-0, #F95F62)" id="Vector_4" />
|
||||
<path d={svgPaths.p2c621c00} fill="var(--fill-0, #F95F62)" id="Vector_5" />
|
||||
<path d={svgPaths.p351aec00} fill="var(--fill-0, #F95F62)" id="Vector_6" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="overflow-clip relative rounded-[8px] size-full" data-name="noun-relax-6597055 1">
|
||||
<Group />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/imports/NounRelax65970551.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import svgPaths from "./svg-s8leuc872s";
|
||||
|
||||
export default function NounRelax() {
|
||||
return (
|
||||
<div className="relative size-full" data-name="noun-relax-6597055 1">
|
||||
<svg className="absolute block inset-0 size-full" fill="none" preserveAspectRatio="none" viewBox="0 0 62 62">
|
||||
<g id="noun-relax-6597055 1">
|
||||
<path clipRule="evenodd" d={svgPaths.pc549500} fill="var(--fill-0, #F95F62)" fillRule="evenodd" id="Vector" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
BIN
src/imports/image-1.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/imports/image-10.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
src/imports/image-2.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src/imports/image-3.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/imports/image-4.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/imports/image-5.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
src/imports/image-6.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/imports/image-7.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/imports/image-8.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/imports/image-9.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
src/imports/image.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
10
src/imports/svg-3e945qfv3w.ts
Normal file
8
src/imports/svg-5q6zbixxri.ts
Normal file
14
src/imports/svg-70pmm871bv.ts
Normal file
107
src/imports/svg-8l5dq4lsp8.ts
Normal file
105
src/imports/svg-adqhdgpsh.ts
Normal file
4
src/imports/svg-bfd6jik9me.ts
Normal file
3
src/imports/svg-hgtc6d03x0.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
pc3f4d00: "M44.3466 22.4327C44.4716 22.4411 44.5949 22.4461 44.72 22.4461C46.0236 22.4461 47.2689 21.981 48.2608 21.1158C49.346 20.1689 49.9978 18.8536 50.0962 17.4166C50.1928 15.9796 49.7261 14.5893 48.7776 13.5024C47.8307 12.4155 46.5154 11.7654 45.0784 11.667C43.6447 11.5703 42.2511 12.0371 41.1642 12.9856C40.3257 13.7191 39.7489 14.671 39.4838 15.7296C39.0037 15.4378 38.4486 15.2728 37.8768 15.2728H26.4243C25.1957 15.2728 24.0405 15.7512 23.1703 16.6197L20.368 19.422C19.1494 20.6406 19.0561 22.5527 20.158 23.7745C20.7331 24.413 21.5216 24.7781 22.3784 24.7997C23.232 24.8297 24.0505 24.493 24.6489 23.8945L27.1545 21.389H33.1859L21.2268 33.3468L17.8527 29.9777C17.256 29.3809 16.4274 29.0558 15.5839 29.0725C14.7271 29.0959 13.9385 29.4593 13.3634 30.0977C12.2632 31.3197 12.3549 33.2318 13.5735 34.4503L18.7462 39.623C19.4247 40.3015 20.3199 40.6716 21.2751 40.6716H21.3535C22.3387 40.6499 23.2489 40.2398 23.9124 39.5163L27.7732 35.3537L32.2875 40.1531L32.2991 47.1131C32.2991 48.8368 33.5861 50.2538 35.2281 50.3405C35.2848 50.3438 35.3415 50.3455 35.3965 50.3455C36.1933 50.3455 36.9435 50.0471 37.5236 49.4953C38.1371 48.9135 38.4872 48.095 38.4872 47.2481L38.4738 40.0265C38.4738 38.4595 37.8821 36.9692 36.8068 35.8306L33.8362 32.6816L37.377 29.0475V30.3711C37.377 32.9317 39.4608 35.0155 42.0214 35.0155H46.9526C47.7978 35.0155 48.6146 34.6638 49.1964 34.052C49.7865 33.4302 50.0866 32.615 50.0433 31.7565C49.9566 30.1145 48.5396 28.8275 46.8159 28.8275H43.5685V22.3195C43.8219 22.3745 44.0803 22.4178 44.3454 22.4361L44.3466 22.4327ZM40.6591 16.773C40.7324 15.6911 41.2225 14.7026 42.0394 13.9891C42.7862 13.3373 43.7231 12.9872 44.7033 12.9872C44.7967 12.9872 44.89 12.9906 44.9851 12.9972C46.067 13.0706 47.0555 13.5607 47.769 14.3792C48.4825 15.1961 48.8342 16.2429 48.7609 17.3248C48.6875 18.4084 48.1974 19.3969 47.3805 20.1104C46.5637 20.8239 45.5235 21.174 44.4333 21.1039C42.1994 20.9522 40.5041 19.0102 40.6574 16.7762L40.6591 16.773ZM22.9319 38.6095C22.5135 39.0646 21.9434 39.323 21.3249 39.3363C20.7398 39.3463 20.1263 39.1162 19.6879 38.6795L14.5152 33.5067C13.81 32.8016 13.7367 31.673 14.3518 30.9895C14.6802 30.6261 15.1286 30.4177 15.6154 30.4044C16.0988 30.3977 16.5623 30.5744 16.9073 30.9195L20.7532 34.7603C21.0132 35.0204 21.435 35.0204 21.6967 34.7603L25.4875 30.9695C25.4692 32.1598 25.8976 33.3567 26.7811 34.2969L26.8578 34.3786L22.9319 38.6095ZM32.429 33.1282L35.8381 36.7423C36.6783 37.6342 37.1417 38.7994 37.1417 40.0247L37.1551 47.2463C37.1551 47.7347 36.96 48.1882 36.6066 48.5233C36.2532 48.86 35.7864 49.0284 35.2997 49.0034C34.3811 48.955 33.6343 48.1048 33.6343 47.1063L33.621 39.8813C33.621 39.7112 33.556 39.5495 33.4409 39.4262L27.7531 33.3798C26.4811 32.0279 26.5128 29.9407 27.8248 28.6287L35.2632 21.1903C35.4532 21.0003 35.5116 20.7135 35.4082 20.4635C35.3049 20.2134 35.0615 20.0517 34.7914 20.0517H26.8746C26.6979 20.0517 26.5279 20.1217 26.4028 20.2468L23.7022 22.9474C23.3555 23.2924 22.8721 23.4692 22.4103 23.4625C21.9236 23.4491 21.4735 23.2424 21.1467 22.879C20.5316 22.1955 20.605 21.0653 21.3101 20.3618L24.1124 17.5595C24.7292 16.9427 25.551 16.6026 26.4229 16.6026H37.8754C38.4289 16.6026 38.9573 16.8677 39.289 17.3111C39.3007 17.3278 39.3157 17.3311 39.3291 17.3411C39.4341 19.2666 40.5593 20.9519 42.2047 21.8204C42.1713 22.0838 42.0597 22.3305 41.8713 22.5239L32.439 32.2045C32.189 32.4612 32.1857 32.868 32.4307 33.128L32.429 33.1282ZM46.8172 30.1575C47.8141 30.1575 48.6643 30.9044 48.7126 31.8229C48.7393 32.313 48.5676 32.7764 48.2309 33.1298C47.8941 33.4832 47.4407 33.6783 46.9539 33.6783H42.0227C40.1973 33.6783 38.712 32.193 38.712 30.3676V27.6753L42.2328 24.0612V29.4892C42.2328 29.8576 42.5312 30.156 42.8996 30.156H46.8138L46.8172 30.1575ZM31.4522 48.3281C31.4522 48.6965 31.1538 48.9949 30.7854 48.9949H20.0665C19.698 48.9949 19.3996 48.6965 19.3996 48.3281C19.3996 47.9597 19.698 47.6613 20.0665 47.6613H30.7854C31.1538 47.6613 31.4522 47.9597 31.4522 48.3281ZM16.2822 48.3281C16.2822 48.6965 15.9838 48.9949 15.6154 48.9949H12.8848C12.5164 48.9949 12.218 48.6949 12.218 48.3281C12.218 47.9597 12.5164 47.6613 12.8848 47.6613H15.6154C15.9838 47.6613 16.2822 47.9614 16.2822 48.3281ZM27.121 43.9974C26.7526 43.9974 26.4542 43.699 26.4542 43.3305C26.4542 42.9621 26.7526 42.6637 27.121 42.6637H29.8383C30.2067 42.6637 30.5051 42.9621 30.5051 43.3305C30.5051 43.699 30.2067 43.9974 29.8383 43.9974H27.121ZM15.7287 42.6637H24.0022C24.3707 42.6637 24.6691 42.9621 24.6691 43.3305C24.6691 43.699 24.3707 43.9974 24.0022 43.9974H15.7287C15.3602 43.9974 15.0618 43.699 15.0618 43.3305C15.0618 42.9621 15.3602 42.6637 15.7287 42.6637ZM12.558 38.9996C12.1896 38.9996 11.8912 38.7012 11.8912 38.3328C11.8912 37.9644 12.1896 37.666 12.558 37.666H15.0668C15.4353 37.666 15.7337 37.9644 15.7337 38.3328C15.7337 38.7012 15.4353 38.9996 15.0668 38.9996H12.558Z",
|
||||
}
|
||||
8
src/imports/svg-jhl6jizeq8.ts
Normal file
3
src/imports/svg-kj92221mds.ts
Normal file
3
src/imports/svg-nvjqsh4m9x.ts
Normal file
11
src/imports/svg-qafz9g9knb.ts
Normal file
37
src/imports/svg-qnagtgq03x.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export default {
|
||||
p119fe300: "M4.44095 0.72106H0.385528C0.2002 0.72106 0.05 0.57086 0.05 0.385531C0.05 0.200203 0.2002 0.05 0.385528 0.05H4.44095C4.62628 0.05 4.77648 0.200203 4.77648 0.385531C4.77648 0.57086 4.62628 0.72106 4.44095 0.72106Z",
|
||||
p15afe300: "M2.31149 7.98157H0.385531C0.200203 7.98157 0.05 7.83136 0.05 7.64603V0.385528C0.05 0.2002 0.200203 0.05 0.385531 0.05C0.57086 0.05 0.72106 0.2002 0.72106 0.385528V7.31051H2.3127C2.49803 7.31051 2.64823 7.46071 2.64823 7.64603C2.64702 7.83136 2.49682 7.98157 2.31149 7.98157Z",
|
||||
p1a7ca200: "M1.45268 0.72106H0.385528C0.2002 0.72106 0.05 0.57086 0.05 0.385531C0.05 0.200203 0.2002 0.05 0.385528 0.05H1.45268C1.63801 0.05 1.78821 0.200203 1.78821 0.385531C1.78821 0.57086 1.6368 0.72106 1.45268 0.72106Z",
|
||||
p1aa58800: "M8.25411 4.44337H0.38553C0.200202 4.44337 0.05 4.29317 0.05 4.10784V3.18362C0.05 2.50408 0.391585 1.87906 0.965739 1.51325L3.17272 0.103296C3.22602 0.0693796 3.28901 0.05 3.3532 0.05H5.28764C5.35184 0.05 5.41362 0.0681683 5.46813 0.103296L7.6739 1.51203C8.24684 1.87784 8.58964 2.50287 8.58964 3.18241V4.10784C8.58964 4.29317 8.43944 4.44337 8.25411 4.44337ZM0.721058 3.77352H7.91979V3.18362C7.91979 2.73302 7.69328 2.31997 7.31414 2.07771L5.18953 0.719847H3.45132L1.32671 2.0765C0.947571 2.31876 0.721058 2.73302 0.721058 3.18241V3.77352Z",
|
||||
p1b540400: "M0.385528 11.4919C0.2002 11.4919 0.05 11.3417 0.05 11.1564V0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05C0.570857 0.05 0.721057 0.2002 0.721057 0.385528V11.1564C0.721057 11.3417 0.570857 11.4919 0.385528 11.4919Z",
|
||||
p1b618330: "M3.71538 16.5321C3.54822 16.5321 3.40287 16.4061 3.38349 16.2353C3.31565 15.6539 2.75603 14.634 2.48713 14.1434C2.43504 14.0489 2.39143 13.9678 2.35752 13.9048L1.62711 12.5142C1.11109 11.5331 0.716213 10.4902 0.449728 9.41332C0.184454 8.33769 0.05 7.22935 0.05 6.12101V0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05C0.570857 0.05 0.721057 0.2002 0.721057 0.385528V6.12101C0.721057 8.23109 1.23949 10.3339 2.22064 12.2029L2.95105 13.5935C2.98134 13.6516 3.02494 13.7304 3.07461 13.8212C3.38591 14.3893 3.96612 15.4456 4.0497 16.1578C4.0715 16.3419 3.93947 16.5079 3.75535 16.5297C3.74203 16.5309 3.72871 16.5321 3.71538 16.5321Z",
|
||||
p1dc35100: "M8.5434 2.27273H0.54885C0.306591 2.27273 0.105517 2.10678 0.0594875 1.86937C0.0134583 1.63195 0.13822 1.40302 0.363521 1.31338L2.56202 0.432771C3.19553 0.178399 3.86295 0.05 4.54613 0.05C5.2293 0.05 5.89672 0.178399 6.53023 0.432771L8.72873 1.31338C8.95403 1.40302 9.07879 1.63195 9.03276 1.86937C8.98673 2.10678 8.78566 2.27273 8.5434 2.27273ZM1.44157 1.60288H7.64946L6.2807 1.05416C5.16146 0.605984 3.92958 0.605984 2.81155 1.05416L1.44157 1.60288Z",
|
||||
p200bb800: "M1.72401 5.98777C0.801003 5.98777 0.05 5.23677 0.05 4.31376C0.05 3.90677 0.203834 2.90018 0.453361 1.98444C0.593871 1.46722 0.746495 1.04932 0.903963 0.74165C0.992388 0.570857 1.26008 0.05 1.7228 0.05C2.18551 0.05 2.45321 0.569646 2.54163 0.74165C2.70031 1.04811 2.85173 1.466 2.99224 1.98444C3.24176 2.90018 3.3956 3.90677 3.3956 4.31376C3.39802 5.23677 2.64702 5.98777 1.72401 5.98777ZM1.72401 0.731959C1.68283 0.764664 1.57987 0.868835 1.43814 1.17893C1.31096 1.45753 1.18256 1.84151 1.06628 2.28847C0.839765 3.15818 0.719846 4.02305 0.719846 4.31376C0.719846 4.86732 1.17045 5.31671 1.7228 5.31671C2.27636 5.31671 2.72575 4.86611 2.72575 4.31376C2.72575 4.02305 2.60583 3.15818 2.37932 2.28847C2.26304 1.84151 2.13464 1.45753 2.00745 1.17893C1.86937 0.868835 1.76641 0.764664 1.72401 0.731959Z",
|
||||
p20329b80: "M3.21148 0.721057H0.38553C0.200202 0.721057 0.05 0.570857 0.05 0.385528C0.05 0.2002 0.200202 0.05 0.38553 0.05H3.21148C3.39681 0.05 3.54701 0.2002 3.54701 0.385528C3.5458 0.570857 3.3956 0.721057 3.21148 0.721057Z",
|
||||
p2040a800: "M3.88738 9.27281C2.86263 9.27281 1.89844 8.87308 1.17408 8.14872C0.449728 7.42437 0.05 6.46018 0.05 5.43542V0.385531C0.05 0.200203 0.200202 0.05 0.38553 0.05H5.48509C5.67041 0.05 5.82061 0.200203 5.82061 0.385531C5.82061 0.57086 5.67041 0.72106 5.48509 0.72106H0.721058V5.43542C0.721058 7.18211 2.14191 8.60296 3.8886 8.60296C4.23987 8.60296 4.55481 8.56904 4.75225 8.5109C4.9291 8.4576 5.11564 8.55935 5.16894 8.7362C5.22223 8.91305 5.12049 9.09959 4.94364 9.15289C4.60932 9.25221 4.18536 9.27281 3.88738 9.27281Z",
|
||||
p2051aa80: "M7.44815 2.34298C6.38826 2.34298 5.59729 1.86937 4.89958 1.45147C4.26971 1.07476 3.67375 0.718635 2.93849 0.718635C2.12571 0.718635 1.53097 0.933034 1.17485 1.11231C0.807824 1.29763 0.624917 1.47691 0.622495 1.47933C0.491675 1.61015 0.279697 1.61136 0.148877 1.48175C0.0180568 1.35093 0.0168461 1.13896 0.146455 1.00814C0.185216 0.969375 1.11792 0.05 2.93728 0.05C3.85787 0.05 4.56163 0.471533 5.24238 0.878528C5.92676 1.28795 6.57238 1.67435 7.44693 1.67435C8.99739 1.67435 9.44436 0.962105 9.448 0.954837C9.53884 0.793735 9.74355 0.736805 9.90465 0.828864C10.0658 0.919711 10.1227 1.12442 10.0306 1.28552C10.0137 1.3158 9.85136 1.58835 9.44315 1.84756C8.92593 2.17583 8.25487 2.34298 7.44815 2.34298Z",
|
||||
p206c6580: "M0.385528 9.4545C0.2002 9.4545 0.05 9.3043 0.05 9.11897V0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05C0.570857 0.05 0.721057 0.2002 0.721057 0.385528V9.11897C0.719845 9.30551 0.570857 9.4545 0.385528 9.4545Z",
|
||||
p21290430: "M7.19907 0.72106H0.385528C0.2002 0.72106 0.05 0.57086 0.05 0.385531C0.05 0.200203 0.2002 0.05 0.385528 0.05H7.19907C7.3844 0.05 7.5346 0.200203 7.5346 0.385531C7.53338 0.57086 7.3844 0.72106 7.19907 0.72106Z",
|
||||
p26102900: "M1.72401 5.98777C0.801003 5.98777 0.05 5.23677 0.05 4.31376C0.05 3.90677 0.203835 2.90018 0.453362 1.98444C0.593872 1.46722 0.746495 1.04932 0.903963 0.74165C0.992388 0.570857 1.26009 0.05 1.7228 0.05C2.18552 0.05 2.45321 0.569646 2.54163 0.74165C2.70031 1.04811 2.85173 1.466 2.99224 1.98444C3.24177 2.90018 3.3956 3.90677 3.3956 4.31376C3.39802 5.23677 2.64702 5.98777 1.72401 5.98777ZM1.72401 0.731959C1.68283 0.764664 1.57987 0.868835 1.43815 1.17893C1.31096 1.45753 1.18256 1.84151 1.06628 2.28847C0.839767 3.15818 0.719847 4.02305 0.719847 4.31376C0.719847 4.86732 1.17045 5.31671 1.7228 5.31671C2.27636 5.31671 2.72575 4.86611 2.72575 4.31376C2.72575 4.02305 2.60583 3.15818 2.37932 2.28847C2.26304 1.84151 2.13464 1.45753 2.00746 1.17893C1.86937 0.868835 1.76641 0.764664 1.72401 0.731959Z",
|
||||
p2b2e2f00: "M0.385528 2.46654C0.2002 2.46654 0.05 2.31633 0.05 2.13101V0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05C0.570857 0.05 0.72106 0.2002 0.72106 0.385528V2.13101C0.72106 2.31754 0.570857 2.46654 0.385528 2.46654Z",
|
||||
p2c63c800: "M0.385528 2.46654C0.2002 2.46654 0.05 2.31633 0.05 2.13101V0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05C0.570857 0.05 0.721058 0.2002 0.721058 0.385528V2.13101C0.721058 2.31754 0.570857 2.46654 0.385528 2.46654Z",
|
||||
p2ceb9900: "M5.7758 0.72106H0.38553C0.200202 0.72106 0.05 0.57086 0.05 0.385531C0.05 0.200203 0.200202 0.05 0.38553 0.05H5.7758C5.96112 0.05 6.11132 0.200203 6.11132 0.385531C6.11132 0.57086 5.96112 0.72106 5.7758 0.72106Z",
|
||||
p2f0cc900: "M12.5106 0.721057H0.385528C0.2002 0.721057 0.05 0.570857 0.05 0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05H12.5106C12.6959 0.05 12.8461 0.2002 12.8461 0.385528C12.8461 0.572068 12.6959 0.721057 12.5106 0.721057Z",
|
||||
p2f242100: "M1.63074 0.72106H0.385528C0.2002 0.72106 0.05 0.57086 0.05 0.385531C0.05 0.200203 0.2002 0.05 0.385528 0.05H1.63074C1.81607 0.05 1.96627 0.200203 1.96627 0.385531C1.96506 0.57086 1.81607 0.72106 1.63074 0.72106Z",
|
||||
p3263c200: "M1.77852 0.721057H0.385528C0.2002 0.721057 0.05 0.570857 0.05 0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05H1.77852C1.96385 0.05 2.11405 0.2002 2.11405 0.385528C2.11405 0.570857 1.96385 0.721057 1.77852 0.721057Z",
|
||||
p32a21d80: "M4.83462 0.721057H0.38553C0.200202 0.721057 0.05 0.570857 0.05 0.385528C0.05 0.2002 0.200202 0.05 0.38553 0.05H4.83462C5.01995 0.05 5.17015 0.2002 5.17015 0.385528C5.17015 0.570857 5.01995 0.721057 4.83462 0.721057Z",
|
||||
p332aed00: "M3.88738 9.27281C2.86263 9.27281 1.89844 8.87308 1.17408 8.14872C0.449727 7.42437 0.05 6.46018 0.05 5.43542V0.385531C0.05 0.200203 0.2002 0.05 0.385528 0.05C0.570857 0.05 0.721057 0.200203 0.721057 0.385531V5.43542C0.721057 7.18211 2.14191 8.60296 3.8886 8.60296C5.63528 8.60296 7.05613 7.18211 7.05613 5.43542V0.385531C7.05613 0.200203 7.20633 0.05 7.39166 0.05C7.57699 0.05 7.72719 0.200203 7.72719 0.385531V5.43542C7.72719 6.46018 7.32746 7.42437 6.60311 8.14872C5.87633 8.87429 4.91214 9.27281 3.88738 9.27281Z",
|
||||
p3401a40: "M5.23919 11.6373C4.52695 11.6373 3.83772 11.4592 3.18968 11.1067C2.57071 10.7712 2.01714 10.2915 1.54232 9.68344C0.579336 8.44791 0.05 6.81024 0.05 5.07203C0.05 4.27137 0.161437 3.3532 0.362512 2.48592C0.516346 1.82334 0.794945 0.889428 1.24676 0.201412C1.30853 0.106931 1.41392 0.05 1.52657 0.05H8.94939C9.06204 0.05 9.16742 0.106931 9.2292 0.201412C9.68101 0.889428 9.95961 1.82334 10.1134 2.48592C10.3157 3.3532 10.426 4.27137 10.426 5.07203C10.426 6.81024 9.89541 8.44791 8.93364 9.68344C8.45881 10.2927 7.90525 10.7712 7.28628 11.1067C6.63945 11.458 5.95022 11.6373 5.23919 11.6373ZM1.71432 0.719847C1.06749 1.82697 0.721057 3.75293 0.721057 5.07203C0.721057 8.32194 2.74877 10.9662 5.2404 10.9662C7.73204 10.9662 9.75974 8.32194 9.75974 5.07203C9.75974 3.75414 9.4121 1.82697 8.76648 0.719847H1.71432Z",
|
||||
p3450f000: "M10.3739 36.0945H2.62037C1.20315 36.0945 0.05 34.9414 0.05 33.5242V20.6663C0.05 19.4404 0.198989 18.2146 0.492123 17.0239C0.785257 15.8332 1.22375 14.6788 1.79427 13.5935L2.52468 12.2029C3.50583 10.3351 4.02426 8.2323 4.02426 6.12101V0.385528C4.02426 0.2002 4.17446 0.05 4.35979 0.05C4.54512 0.05 4.69532 0.2002 4.69532 0.385528V6.12101C4.69532 7.22935 4.56087 8.33769 4.29559 9.41332C4.03032 10.4889 3.63422 11.5331 3.11821 12.5142L2.3878 13.9048C1.29642 15.9822 0.719847 18.32 0.719847 20.665V33.5217C0.719847 34.5695 1.5726 35.4223 2.62037 35.4223H10.3739C10.5592 35.4223 10.7094 35.5725 10.7094 35.7578C10.7094 35.9431 10.558 36.0945 10.3739 36.0945Z",
|
||||
p34c8570: "M2.31997 7.98157H0.38553C0.200202 7.98157 0.05 7.83136 0.05 7.64603V0.385528C0.05 0.2002 0.200202 0.05 0.38553 0.05C0.570858 0.05 0.721058 0.2002 0.721058 0.385528V7.31051H1.98565V0.385528C1.98565 0.2002 2.13585 0.05 2.32118 0.05C2.50651 0.05 2.65671 0.2002 2.65671 0.385528V7.64603C2.65429 7.83136 2.5053 7.98157 2.31997 7.98157Z",
|
||||
p35856480: "M7.39045 0.721057H0.385528C0.2002 0.721057 0.05 0.570857 0.05 0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05H7.39045C7.57578 0.05 7.72598 0.2002 7.72598 0.385528C7.72477 0.570857 7.57457 0.721057 7.39045 0.721057Z",
|
||||
p35daba00: "M6.04349 3.32534H3.89102C3.70569 3.32534 3.55549 3.17514 3.55549 2.98981C3.55549 2.80449 3.70569 2.65429 3.89102 2.65429H6.04349C6.19006 2.65429 6.30877 2.53558 6.30877 2.38901V0.986332C6.30877 0.839765 6.19006 0.721057 6.04349 0.721057H0.986332C0.839765 0.721057 0.72106 0.839765 0.72106 0.986332V2.38901C0.72106 2.53558 0.839765 2.65429 0.986332 2.65429H2.17461C2.35994 2.65429 2.51014 2.80449 2.51014 2.98981C2.51014 3.17514 2.35994 3.32534 2.17461 3.32534H0.986332C0.47032 3.32534 0.05 2.90502 0.05 2.38901V0.986332C0.05 0.47032 0.47032 0.05 0.986332 0.05H6.04349C6.5595 0.05 6.97982 0.47032 6.97982 0.986332V2.38901C6.97982 2.90502 6.5595 3.32534 6.04349 3.32534Z",
|
||||
p37797c00: "M4.31982 4.44337H0.38553C0.200202 4.44337 0.05 4.29317 0.05 4.10784V3.18362C0.05 2.50408 0.391585 1.87906 0.965739 1.51325L3.17272 0.103296C3.22602 0.0693795 3.28901 0.05 3.3532 0.05H5.28764C5.47297 0.05 5.62317 0.2002 5.62317 0.385528C5.62317 0.570857 5.47297 0.721057 5.28764 0.721057H3.45132L1.32671 2.0765C0.94757 2.31876 0.721058 2.73302 0.721058 3.18241V3.77231H4.31982C4.50515 3.77231 4.65535 3.92251 4.65535 4.10784C4.65535 4.29317 4.50515 4.44337 4.31982 4.44337Z",
|
||||
p39705400: "M5.10353 8.78708C4.9182 8.78708 4.768 8.63688 4.768 8.45155V0.721058H0.721058V8.45155C0.721058 8.63688 0.570858 8.78708 0.38553 8.78708C0.200202 8.78708 0.05 8.63688 0.05 8.45155V0.385528C0.05 0.2002 0.200202 0.05 0.38553 0.05H5.10231C5.28764 0.05 5.43784 0.2002 5.43784 0.385528V8.45155C5.43906 8.63688 5.28886 8.78708 5.10353 8.78708Z",
|
||||
p3c314100: "M9.24373 2.78753H4.81524C4.62991 2.78753 4.47971 2.63733 4.47971 2.452C4.47971 2.26667 4.62991 2.11647 4.81524 2.11647H9.24373C9.62893 2.11647 9.94144 1.80275 9.94144 1.41876C9.94144 1.03357 9.62772 0.721057 9.24373 0.721057H1.41876C1.03357 0.721057 0.721058 1.03478 0.721058 1.41876C0.721058 1.80396 1.03478 2.11647 1.41876 2.11647H2.93409C3.11942 2.11647 3.26962 2.26667 3.26962 2.452C3.26962 2.63733 3.11942 2.78753 2.93409 2.78753H1.41876C0.664127 2.78753 0.05 2.1734 0.05 1.41876C0.05 0.664127 0.664127 0.05 1.41876 0.05H9.24373C9.99837 0.05 10.6125 0.664127 10.6125 1.41876C10.6125 2.1734 9.99837 2.78753 9.24373 2.78753Z",
|
||||
p3ce0580: "M2.56202 0.432771L0.363521 1.31338C0.13822 1.40302 0.0134583 1.63195 0.0594875 1.86937C0.105517 2.10678 0.306591 2.27273 0.54885 2.27273H8.5434C8.78566 2.27273 8.98673 2.10678 9.03276 1.86937C9.07879 1.63195 8.95403 1.40302 8.72873 1.31338L6.53023 0.432771C5.89672 0.178399 5.2293 0.05 4.54613 0.05C3.86295 0.05 3.19553 0.178399 2.56202 0.432771ZM2.56202 0.432771L2.68679 0.744073M1.44157 1.60288H7.64946L6.2807 1.05416C5.16146 0.605984 3.92958 0.605984 2.81155 1.05416L1.44157 1.60288Z",
|
||||
p5cab80: "M6.93137 0.721057H0.385528C0.2002 0.721057 0.05 0.570857 0.05 0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05H6.93137C7.1167 0.05 7.2669 0.2002 7.2669 0.385528C7.2669 0.570857 7.1167 0.721057 6.93137 0.721057Z",
|
||||
p88545c0: "M5.10353 8.78708C4.9182 8.78708 4.768 8.63688 4.768 8.45155V0.721058H0.721058V8.45155C0.721058 8.63688 0.570857 8.78708 0.385528 8.78708C0.2002 8.78708 0.05 8.63688 0.05 8.45155V0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05H5.10231C5.28764 0.05 5.43784 0.2002 5.43784 0.385528V8.45155C5.43905 8.63688 5.28886 8.78708 5.10353 8.78708Z",
|
||||
pbe42b80: "M5.28643 0.721057H0.385528C0.2002 0.721057 0.05 0.570857 0.05 0.385528C0.05 0.2002 0.2002 0.05 0.385528 0.05H5.28643C5.47176 0.05 5.62196 0.2002 5.62196 0.385528C5.62075 0.570857 5.47055 0.721057 5.28643 0.721057Z",
|
||||
pcabba00: "M4.854 0.72106H0.38553C0.200202 0.72106 0.05 0.57086 0.05 0.385531C0.05 0.200203 0.200202 0.05 0.38553 0.05H4.854C5.03933 0.05 5.18953 0.200203 5.18953 0.385531C5.18953 0.57086 5.03933 0.72106 4.854 0.72106Z",
|
||||
}
|
||||
4
src/imports/svg-rf4f5wcll0.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
p1054ae00: "M53.2116 48.9121H38.9889L40.9593 33.2206L48.4109 24.4796C49.5519 23.1182 49.5376 21.1299 48.3751 19.7867L43.109 13.7681C42.86 13.4743 42.5018 13.2952 42.1184 13.2665C41.7333 13.2396 41.3553 13.3686 41.0669 13.6247L40.6012 14.0546C40.6621 13.5854 40.4884 13.116 40.1355 12.8008L39.2399 12.0126C38.6685 11.5092 37.8033 11.5397 37.2695 12.0842L31.7526 17.7447C31.7472 18.7048 31.5143 19.6506 31.0719 20.5032L38.3084 13.0874L39.204 13.8756L32.5049 22.4736C32.367 22.658 32.3276 22.8981 32.3974 23.1184C32.4333 23.2259 33.4005 26.0919 37.9144 27.5607C37.8427 28.3489 37.6636 29.2087 37.2695 29.4236C36.9238 29.6206 36.8038 30.063 37.0009 30.4087C37.1979 30.7545 37.6403 30.8745 37.986 30.6774C39.1324 30.0326 39.3115 28.2055 39.3474 27.3457L44.542 22.6886C45.0776 22.2175 45.185 21.4222 44.7927 20.8257L41.2102 15.3803L42.0342 14.6638L47.3003 20.7182C48.0186 21.5188 48.0348 22.7297 47.3362 23.5483L39.7415 32.4687C39.6484 32.569 39.5874 32.6926 39.5624 32.827L37.5562 48.9123H35.335V34.26C35.3332 33.9483 35.1308 33.6725 34.8334 33.5793C34.7618 33.5793 29.4238 31.8955 26.4147 26.6651C26.2947 26.4573 26.0798 26.323 25.8415 26.3069L14.1625 25.6262V24.4798L24.3727 23.8349H24.5877L24.731 23.7633C23.7135 23.6128 22.7535 23.2062 21.9366 22.5811L14.0549 23.0827C13.3062 23.1382 12.7276 23.7651 12.7294 24.5157V25.6621C12.7276 26.4269 13.3277 27.0575 14.0908 27.0951L25.3755 27.7041C27.3745 30.9211 30.3678 33.3985 33.9019 34.7618V48.9125H8.78843C8.39256 48.9125 8.07193 49.2331 8.07193 49.629C8.07193 50.0248 8.39256 50.3455 8.78843 50.3455H53.2114C53.6073 50.3455 53.9279 50.0248 53.9279 49.629C53.9279 49.2331 53.6075 48.9121 53.2116 48.9121ZM43.6103 21.6493L38.4873 26.2349C35.4421 25.3035 34.2957 23.6913 33.9017 23.0107L39.7767 15.416C39.7803 15.6973 39.8681 15.9713 40.0275 16.2042L43.6103 21.6493Z",
|
||||
p365acf00: "M20.9685 17.7085C20.9685 19.5929 22.1024 21.291 23.8434 22.011C25.5846 22.7329 27.5872 22.3335 28.9179 21.0008C30.2505 19.6699 30.65 17.6673 29.9281 15.9264C29.208 14.1853 27.51 13.0514 25.6255 13.0514C23.0533 13.0514 20.9685 15.1364 20.9685 17.7085ZM28.8502 17.7085C28.8502 19.0125 28.0639 20.1875 26.8602 20.6873C25.6547 21.1871 24.2683 20.9112 23.3457 19.9887C22.4232 19.0663 22.1474 17.6798 22.6472 16.4743C23.1469 15.2706 24.322 14.4842 25.626 14.4842C26.4876 14.4753 27.3169 14.8102 27.9295 15.4157C28.5421 16.0211 28.886 16.8469 28.886 17.7085H28.8502Z",
|
||||
}
|
||||
3
src/imports/svg-rfrt8mx1lu.ts
Normal file
3
src/imports/svg-rpecqp6li2.ts
Normal file
3
src/imports/svg-s8leuc872s.ts
Normal file
9
src/imports/svg-u2edxj8dia.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
p11d0e900: "M0.884818 27.4327C1.10144 27.7782 1.3664 28.0915 1.67254 28.3636C1.6994 28.1094 1.74773 27.8588 1.81576 27.6117C1.88737 27.2949 1.98405 26.9834 2.1022 26.6808C1.60091 25.8931 1.81576 24.819 2.20962 24.0671C2.66792 23.1702 2.58556 22.0907 1.99479 21.2743C1.37535 20.2825 1.66181 18.9792 2.63925 18.3384C4.03564 17.3716 5.3604 17.6223 6.57779 19.1261C6.73175 19.3391 6.86243 19.5665 6.97164 19.8064V19.878C7.58031 21.1312 10.0509 21.525 11.6619 21.6682C11.6279 21.1902 11.6386 20.7104 11.6977 20.236C10.0506 20.0928 8.47529 19.6989 8.22464 19.2335V19.1619C8.07963 18.8343 7.89882 18.521 7.68756 18.231C6.04052 16.1543 3.85645 15.7246 1.85157 17.121C0.884834 17.7297 -0.583147 19.5199 0.705822 21.9547L0.777431 22.0621C1.06387 22.447 1.10504 22.9608 0.884847 23.3869C0.204547 24.8191 0.168724 26.3228 0.884818 27.4327Z",
|
||||
p15b7cd00: "M41.0576 28.8649C41.0576 28.9007 39.8761 31.8725 35.0424 33.6986C34.7023 33.8096 34.4964 34.1533 34.5591 34.506C34.6217 34.8587 34.935 35.1093 35.2931 35.095H35.5437C40.986 33.0184 42.3467 29.5812 42.4182 29.4377C42.5202 29.2551 42.5381 29.0385 42.4665 28.8434C42.3949 28.6465 42.241 28.4925 42.0458 28.4191C41.8525 28.3439 41.6341 28.36 41.4515 28.4585C41.2689 28.5587 41.1382 28.7324 41.0934 28.9365L41.0576 28.8649Z",
|
||||
p17868d00: "M9.19108 25.2484H9.11947C8.79544 25.1177 8.46066 25.0229 8.11694 24.962C5.5748 24.5323 3.74879 25.6781 3.17601 27.9696C2.88957 29.0437 2.99699 31.2636 5.5033 32.1229H5.64652C6.08155 32.1945 6.43425 32.5186 6.54163 32.9464C6.93548 34.4144 7.90223 35.4886 9.11956 35.8108C9.47045 35.9039 9.83029 35.9522 10.1937 35.954C10.815 35.9576 11.4236 35.784 11.9481 35.4527C13.3803 34.5576 13.81 32.8032 13.9174 32.3019C13.9998 31.8526 14.0248 31.3961 13.989 30.9413C13.9711 30.3702 14.2074 29.8189 14.6335 29.4376L16.8176 27.3251C17.8398 27.9552 19.0178 28.29 20.219 28.2918C21.2788 28.2882 22.3225 28.0304 23.2624 27.5399L23.9427 27.1819C24.981 26.609 25.6971 26.2151 27.2725 26.1793C28.0173 26.1614 28.7602 26.2223 29.4924 26.3584C31.3901 26.6806 36.152 26.9312 39.6969 22.4915C45.0677 15.7242 43.5638 9.06465 40.3056 5.05453C38.0195 2.2886 34.7148 0.559249 31.1396 0.256588C27.3085 -0.0656522 20.4342 0.722036 17.6413 9.74477V9.85218C17.4265 11.3202 16.5313 15.366 15.135 16.5837C14.7447 16.9131 14.4063 17.2998 14.1324 17.7294C13.1442 19.2708 12.8703 21.1631 13.3805 22.9211L13.2731 22.9927C11.6261 24.3533 9.72812 25.4632 9.19108 25.2484ZM15.3135 18.4454C15.5104 18.1375 15.7521 17.86 16.0296 17.6219C17.9989 15.8675 18.8582 10.8904 19.0014 10.103C21.4003 2.47647 26.8425 1.33072 30.996 1.68911C34.197 1.95406 37.1547 3.50439 39.1955 5.98567C42.0956 9.53034 43.4204 15.5096 38.5868 21.6325C37.5574 22.9412 36.1879 23.9437 34.6286 24.5309C33.0711 25.118 31.3811 25.2667 29.7431 24.9623C28.9053 24.8137 28.0531 24.7529 27.201 24.7833C25.8064 24.8173 24.4458 25.2255 23.2624 25.9648L22.5821 26.3229C20.4482 27.44 17.8254 26.9172 16.2805 25.0697L15.8867 24.5684L15.7076 24.3178C13.7384 21.8473 14.6335 19.5918 15.3138 18.446L15.3135 18.4454ZM14.5974 25.1769L14.7048 25.356C14.8588 25.5815 15.0253 25.7964 15.2061 26.0004L15.6716 26.5017L13.6307 28.471C12.929 29.1405 12.5512 30.0804 12.5924 31.0489C12.6228 31.3837 12.6121 31.7202 12.5566 32.0514C12.4134 32.8033 11.9837 33.8059 11.2318 34.2713C10.7127 34.5775 10.0843 34.6437 9.5132 34.4504C8.68968 34.2355 8.1526 33.3404 7.93779 32.5885C7.68895 31.6308 6.90662 30.9039 5.93273 30.7267C4.21411 30.118 4.50054 28.6142 4.57213 28.292C4.96598 26.6808 6.0759 26.0363 7.90195 26.3227L8.61805 26.5375H8.68966C10.2293 27.1104 12.8788 25.0695 13.9528 24.1744C14.1425 24.5253 14.3575 24.8601 14.5974 25.1769Z",
|
||||
p18f61d00: "M32.4646 34.2716C32.0529 34.4291 31.6017 34.4542 31.1757 34.3432C30.7997 34.2143 30.3915 34.4148 30.2626 34.7908C30.1337 35.1667 30.3342 35.5749 30.7102 35.7038C31.5122 35.9526 32.3769 35.9007 33.1449 35.5606C33.5012 35.3833 33.6444 34.9501 33.4671 34.5938C33.2899 34.2376 32.8567 34.0944 32.5004 34.2716L32.4646 34.2716Z",
|
||||
p1cd27a80: "M36.0448 21.489C36.3527 21.7343 36.7985 21.6859 37.0474 21.3816C40.6636 16.8344 40.0907 13.1464 40.0549 13.0034C40.046 12.7957 39.9493 12.6042 39.7882 12.4735C39.6271 12.3446 39.4158 12.2909 39.2135 12.3267C39.0095 12.3643 38.8304 12.486 38.7248 12.6651C38.6192 12.8423 38.5941 13.0571 38.6586 13.2541C38.6586 13.2899 39.1599 16.4407 35.9374 20.4865C35.6922 20.7944 35.7405 21.2401 36.0448 21.489Z",
|
||||
p1d2c1e00: "M45.9985 25.5706C46.1471 22.7689 45.4489 19.9848 43.9934 17.5861C43.8019 18.184 43.5745 18.7694 43.3131 19.3405C44.2494 21.2525 44.6809 23.3721 44.5663 25.4991C44.2799 30.2971 41.1291 35.9539 33.1803 37.2788C28.1674 38.1381 25.1601 35.0589 24.1216 33.6625C23.5952 32.9572 22.9955 32.3091 22.3314 31.7291C20.8276 30.4401 19.7534 30.1895 18.5002 29.903L17.7484 29.724C17.4154 29.6363 17.0913 29.5163 16.7816 29.366L15.7075 30.4043C16.2338 30.7176 16.7995 30.9592 17.3903 31.1204L18.178 31.3352C19.3953 31.6216 20.1831 31.8365 21.4004 32.8748C21.984 33.3904 22.5121 33.9668 22.9758 34.5934C24.05 35.9898 26.8427 38.89 31.4617 38.89C32.1098 38.8846 32.756 38.8238 33.3952 38.711C42.239 37.2072 45.7121 30.9414 45.9985 25.5706Z",
|
||||
p1df26700: "M32.6077 23.6372C33.4473 23.6103 34.2493 23.2792 34.8634 22.7063C35.0693 22.4145 35.0263 22.0135 34.7649 21.77C34.5035 21.5265 34.1025 21.514 33.825 21.7396C33.4831 22.0314 33.057 22.2068 32.6077 22.2408C32.2121 22.2408 31.8916 22.5613 31.8916 22.9569C31.8916 23.3526 32.2121 23.673 32.6077 23.673L32.6077 23.6372Z",
|
||||
}
|
||||
3
src/imports/svg-wjtpqfkmjn.ts
Normal file
@@ -504,7 +504,7 @@ export function CartPage({
|
||||
<div className="mb-8">
|
||||
<h2 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
|
||||
<span className="font-light">Your</span>{' '}
|
||||
<span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent">Cart</span>
|
||||
<span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent pr-2">Cart</span>
|
||||
</h2>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1">
|
||||
{isEmpty ? 'Your cart is empty' : `${CartItems.length} ${CartItems.length === 1 ? 'item' : 'items'} in your cart`}
|
||||
|
||||
@@ -1,878 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import {
|
||||
Users, Baby, ShoppingBag, Trash2, Check, CreditCard, Mail,
|
||||
ChevronRight, ChevronDown, Minus, Plus, Calendar, ArrowLeft, MapPin,
|
||||
Zap, Shield, Clock, Percent, Sparkles
|
||||
} from 'lucide-react';
|
||||
import Navbar from '../components/Navbar';
|
||||
import { Footer } from '../components/Footer';
|
||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useGetCardsinCartQuery } from '../Redux/services/cards.service';
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
|
||||
/* ─── Types ─── */
|
||||
export interface CartItem {
|
||||
id: string;
|
||||
city: string;
|
||||
cardType: 'Flexi' | 'Unlimited';
|
||||
days: number;
|
||||
adults: number;
|
||||
children: number;
|
||||
quantity: number;
|
||||
pricePerUnit: number;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface Attraction {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
category: string;
|
||||
included: boolean;
|
||||
}
|
||||
|
||||
interface CartPageDesignProps {
|
||||
onBackClick: () => void;
|
||||
onHomeClick: () => void;
|
||||
onPassesClick: () => void;
|
||||
onCheckoutClick?: () => void;
|
||||
onSecureCheckoutClick?: (item: CartItem) => 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;
|
||||
onSuperSavingsClick?: () => void;
|
||||
onEsimsClick?: () => void;
|
||||
onHotelDiscountsClick?: () => void;
|
||||
onContactUsClick?: () => void;
|
||||
onCartClick?: () => void;
|
||||
currentPage?: string;
|
||||
user?: { email: string; name: string } | null;
|
||||
}
|
||||
|
||||
/* ─── Data ─── */
|
||||
const initialCartItems: CartItem[] = [
|
||||
{
|
||||
id: '1', city: 'Melbourne', cardType: 'Flexi', days: 3, adults: 3, children: 3, quantity: 2, pricePerUnit: 49.50,
|
||||
image: 'https://images.unsplash.com/photo-1655963754904-2cf2b562a681?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBmbGluZGVycyUyMHN0YXRpb24lMjBzdW5zZXR8ZW58MXx8fHwxNzc2MzE5NDgzfDA&ixlib=rb-4.1.0&q=80&w=1080',
|
||||
},
|
||||
{
|
||||
id: '2', city: 'Sydney', cardType: 'Flexi', days: 3, adults: 3, children: 3, quantity: 2, pricePerUnit: 49.50,
|
||||
image: 'https://images.unsplash.com/photo-1695018228065-2e0026c654af?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBvcGVyYSUyMGhvdXNlJTIwaGFyYm91ciUyMGJyaWRnZXxlbnwxfHx8fDE3NzYzMTk0ODN8MA&ixlib=rb-4.1.0&q=80&w=1080',
|
||||
},
|
||||
{
|
||||
id: '3', city: 'Melbourne', cardType: 'Unlimited', days: 6, adults: 2, children: 1, quantity: 1, pricePerUnit: 79.00,
|
||||
image: 'https://images.unsplash.com/photo-1705120624704-0970afc29fea?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBzdHJlZXQlMjBhcnQlMjBsYW5ld2F5c3xlbnwxfHx8fDE3NzYzMTk0ODR8MA&ixlib=rb-4.1.0&q=80&w=1080',
|
||||
},
|
||||
];
|
||||
|
||||
const dayOptions = [3, 6, 12, 18, 24];
|
||||
|
||||
const attractionsData: Record<string, Record<string, Attraction[]>> = {
|
||||
Melbourne: {
|
||||
Flexi: [
|
||||
{ id: 'mel-1', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-2', name: 'Melbourne Zoo', image: 'https://images.unsplash.com/photo-1730074888490-31239540bacf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjB6b28lMjB3aWxkbGlmZXxlbnwxfHx8fDE3NzYzMTk5NzB8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-3', name: 'Royal Botanic Gardens', image: 'https://images.unsplash.com/photo-1585894507208-eeead8cb9a56?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBib3RhbmljYWwlMjBnYXJkZW4lMjBncmVlbnxlbnwxfHx8fDE3NzYzMTk5NzF8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Nature', included: true },
|
||||
{ id: 'mel-4', name: 'NGV Art Gallery', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
|
||||
],
|
||||
Unlimited: [
|
||||
{ id: 'mel-1', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-2', name: 'Melbourne Zoo', image: 'https://images.unsplash.com/photo-1730074888490-31239540bacf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjB6b28lMjB3aWxkbGlmZXxlbnwxfHx8fDE3NzYzMTk5NzB8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-3', name: 'Royal Botanic Gardens', image: 'https://images.unsplash.com/photo-1585894507208-eeead8cb9a56?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBib3RhbmljYWwlMjBnYXJkZW4lMjBncmVlbnxlbnwxfHx8fDE3NzYzMTk5NzF8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Nature', included: true },
|
||||
{ id: 'mel-4', name: 'NGV Art Gallery', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
|
||||
{ id: 'mel-5', name: 'Melbourne Star Wheel', image: 'https://images.unsplash.com/photo-1769880659692-fa77e04c5ffa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxvYnNlcnZhdGlvbiUyMHdoZWVsJTIwYW11c2VtZW50JTIwbmlnaHR8ZW58MXx8fHwxNzc2MzE5OTc2fDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
|
||||
{ id: 'mel-6', name: 'Penguin Parade', image: 'https://images.unsplash.com/photo-1670391050251-d1cfbc3891c4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxwZW5ndWlucyUyMHdpbGRsaWZlJTIwbmF0dXJlfGVufDF8fHx8MTc3NjMxOTk3Nnww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-7', name: 'Yarra River Cruise', image: 'https://images.unsplash.com/photo-1562003914-018a4a6c2171?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyaXZlciUyMGNydWlzZSUyMGJvYXQlMjBjaXR5fGVufDF8fHx8MTc3NjMxOTk3M3ww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
|
||||
],
|
||||
},
|
||||
Sydney: {
|
||||
Flexi: [
|
||||
{ id: 'syd-1', name: 'Harbour Bridge Climb', image: 'https://images.unsplash.com/photo-1767974062666-2685a670e353?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBoYXJib3VyJTIwYnJpZGdlJTIwY2xpbWJ8ZW58MXx8fHwxNzc2MzE5OTcxfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Adventure', included: true },
|
||||
{ id: 'syd-2', name: 'Taronga Zoo', image: 'https://images.unsplash.com/photo-1704852168456-b70e08441917?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjB0YXJvbmdhJTIwem9vJTIwYW5pbWFsc3xlbnwxfHx8fDE3NzYzMTk5NzJ8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'syd-3', name: 'Art Gallery NSW', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
|
||||
],
|
||||
Unlimited: [
|
||||
{ id: 'syd-1', name: 'Harbour Bridge Climb', image: 'https://images.unsplash.com/photo-1767974062666-2685a670e353?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBoYXJib3VyJTIwYnJpZGdlJTIwY2xpbWJ8ZW58MXx8fHwxNzc2MzE5OTcxfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Adventure', included: true },
|
||||
{ id: 'syd-2', name: 'Taronga Zoo', image: 'https://images.unsplash.com/photo-1704852168456-b70e08441917?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjB0YXJvbmdhJTIwem9vJTIwYW5pbWFsc3xlbnwxfHx8fDE3NzYzMTk5NzJ8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'syd-3', name: 'Art Gallery NSW', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
|
||||
{ id: 'syd-4', name: 'Sydney Harbour Cruise', image: 'https://images.unsplash.com/photo-1562003914-018a4a6c2171?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyaXZlciUyMGNydWlzZSUyMGJvYXQlMjBjaXR5fGVufDF8fHx8MTc3NjMxOTk3M3ww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
|
||||
{ id: 'syd-5', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const offersData: Record<string, { title: string; description: string; image: string }[]> = {
|
||||
Flexi: [
|
||||
{ title: 'Astor Hotels Ultra Deluxe', description: '15% Discount on all treatments for first-time clients', image: 'https://images.unsplash.com/photo-1715191904112-4a5d9c3089fa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxsdXh1cnklMjBob3RlbCUyMHJlc29ydCUyMGV4dGVyaW9yfGVufDF8fHx8MTc3NjMyMTM2MXww&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Green Valley Spa Lux', description: '20% Off on membership plans for new members', image: 'https://images.unsplash.com/photo-1759216853079-831ef8c8b327?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzcGElMjB3ZWxsbmVzcyUyMHRyZWF0bWVudCUyMGludGVyaW9yfGVufDF8fHx8MTc3NjMyMTM2M3ww&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Harbour Dining Co.', description: '10% Off your first dining experience at waterfront', image: 'https://images.unsplash.com/photo-1676471932681-45fa972d848a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyZXN0YXVyYW50JTIwZmluZSUyMGRpbmluZ3xlbnwxfHx8fDE3NzYzMTkxNDl8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'National Gallery Exhibition', description: 'Free audio guide with every gallery visit', image: 'https://images.unsplash.com/photo-1569342380852-035f42d9ca41?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtdXNldW0lMjBnYWxsZXJ5JTIwZXhoaWJpdGlvbnxlbnwxfHx8fDE3NzYyNDYwMjh8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Sunset Harbour Cruise', description: 'Complimentary drink on every sunset cruise booking', image: 'https://images.unsplash.com/photo-1765783800962-83d99ff7b158?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjcnVpc2UlMjBib2F0JTIwaGFyYm9yJTIwdG91cnxlbnwxfHx8fDE3NzYzMjE2MDd8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
],
|
||||
Unlimited: [
|
||||
{ title: 'SkyView Ferris Wheel', description: 'Complimentary second ride for all pass holders', image: 'https://images.unsplash.com/photo-1626209025747-b41ee6ec191f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxmZXJyaXMlMjB3aGVlbCUyMGFtdXNlbWVudCUyMHBhcmt8ZW58MXx8fHwxNzc2MzE3NDI2fDA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'City Mall Boutique', description: '15% Off at select boutique stores with your pass', image: 'https://images.unsplash.com/photo-1567966689299-819568579d36?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzaG9wcGluZyUyMG1hbGwlMjBib3V0aXF1ZSUyMHJldGFpbHxlbnwxfHx8fDE3NzYzMjEzNjN8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Adventure Outfitters', description: 'Free gear rental on outdoor adventure bookings', image: 'https://images.unsplash.com/photo-1761131221577-0716baffc6ef?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhZHZlbnR1cmUlMjBzcG9ydHMlMjBvdXRkb29yJTIwYWN0aXZpdHl8ZW58MXx8fHwxNzc2MzIxMzYzfDA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Skyline Rooftop Lounge', description: 'Buy one get one free on signature cocktails', image: 'https://images.unsplash.com/photo-1642114955097-8f3d0e141641?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyb29mdG9wJTIwYmFyJTIwY2l0eSUyMHNreWxpbmUlMjBuaWdodHxlbnwxfHx8fDE3NzYyNDU2NTl8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Yarra Valley Wines', description: 'Exclusive wine tasting tour with pass holders discount', image: 'https://images.unsplash.com/photo-1764649841527-c8852b63cc53?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx3aW5lJTIwdGFzdGluZyUyMHZpbmV5YXJkJTIwY2VsbGFyfGVufDF8fHx8MTc3NjMyMTYwOHww&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
],
|
||||
};
|
||||
|
||||
const priceTable: Record<string, Record<number, number>> = {
|
||||
Flexi: { 3: 49.5, 6: 69, 12: 99, 18: 129, 24: 159 },
|
||||
Unlimited: { 3: 79, 6: 109, 12: 149, 18: 189, 24: 229 },
|
||||
};
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
FIGMA CARD TYPE COMPONENTS
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
function FlexiCardPreview({ city, adultPrice, childPrice, isSelected }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean }) {
|
||||
return (
|
||||
<div className={`relative h-[160px] w-full rounded-lg transition-all duration-200 ${
|
||||
isSelected ? 'ring-2 ring-[#F95F62] shadow-md shadow-[#F95F62]/10' : 'hover:shadow-md'
|
||||
}`}>
|
||||
{/* Card bg */}
|
||||
<div className="absolute inset-0 bg-white border border-[rgba(249,95,175,0.2)] rounded-lg shadow-[0px_4px_20px_0px_rgba(0,0,0,0.06)]" />
|
||||
{/* City image */}
|
||||
<div className="absolute h-[158px] left-[1px] top-[1px] w-[103px] rounded-bl-[7px] rounded-tl-[7px] overflow-hidden">
|
||||
{/* <img alt="" className="absolute inset-0 w-full h-full object-cover" src={imgRectangle26} /> */}
|
||||
</div>
|
||||
{/* City name - left aligned */}
|
||||
<div className="absolute left-[112px] top-[12px]">
|
||||
<p className="font-['Poppins',sans-serif] font-medium text-[16px] text-[#2a2a2a] leading-[22px] whitespace-nowrap">{city}</p>
|
||||
</div>
|
||||
{/* Pricing */}
|
||||
<div className="absolute left-[112px] top-[40px] flex flex-col gap-[6px]">
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">From</span>
|
||||
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${adultPrice}</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Adult</span>
|
||||
</div>
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">and</span>
|
||||
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${childPrice}</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Child</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Description */}
|
||||
<div className="absolute left-[112px] top-[112px] right-[44px]">
|
||||
<p className="font-['Poppins',sans-serif] text-[11px] text-left text-[rgba(0,0,0,0.4)] tracking-[0.06px] leading-[14px]">
|
||||
Dive into an extensive selection of thrilling destinations!
|
||||
</p>
|
||||
</div>
|
||||
{/* Side tab - Flexi (pink) */}
|
||||
<div className="absolute bg-[#f95faf] h-full right-0 top-0 w-[35px] rounded-br-lg rounded-tr-lg flex flex-col items-center justify-center gap-[2px]">
|
||||
<span className="font-['Poppins',sans-serif] text-[12px] text-white/70 [writing-mode:vertical-rl] rotate-180">Card</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[16px] text-white [writing-mode:vertical-rl] rotate-180">Flexi</span>
|
||||
</div>
|
||||
{/* Selected checkmark */}
|
||||
{isSelected && (
|
||||
<div className="absolute top-2 right-[44px] w-6 h-6 rounded-full bg-[#F95F62] flex items-center justify-center z-10">
|
||||
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UnlimitedCardPreview({ city, adultPrice, childPrice, isSelected }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean }) {
|
||||
return (
|
||||
<div className={`relative h-[160px] w-full rounded-lg transition-all duration-200 ${
|
||||
isSelected ? 'ring-2 ring-[#F95F62] shadow-md shadow-[#F95F62]/10' : 'hover:shadow-md'
|
||||
}`}>
|
||||
{/* Card bg */}
|
||||
<div className="absolute inset-0 bg-white border border-[rgba(0,0,0,0.2)] rounded-lg" />
|
||||
{/* City image */}
|
||||
<div className="absolute h-[158px] left-[1px] top-[1px] w-[103px] rounded-bl-[7px] rounded-tl-[7px] overflow-hidden">
|
||||
{/* <img alt="" className="absolute inset-0 w-full h-full object-cover" src={imgRectangle26} /> */}
|
||||
</div>
|
||||
{/* City name - left aligned */}
|
||||
<div className="absolute left-[112px] top-[12px]">
|
||||
<p className="font-['Poppins',sans-serif] font-medium text-[16px] text-[#2a2a2a] leading-[20px] whitespace-nowrap">{city}</p>
|
||||
</div>
|
||||
{/* Pricing */}
|
||||
<div className="absolute left-[112px] top-[40px] flex flex-col gap-[6px]">
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">From</span>
|
||||
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${adultPrice}</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Adult</span>
|
||||
</div>
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">and</span>
|
||||
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${childPrice}</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Child</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Description */}
|
||||
<div className="absolute left-[112px] top-[112px] right-[44px]">
|
||||
<p className="font-['Poppins',sans-serif] text-[11px] text-left text-[rgba(0,0,0,0.4)] tracking-[0.06px] leading-[14px]">
|
||||
Dive into an extensive selection of thrilling destinations!
|
||||
</p>
|
||||
</div>
|
||||
{/* Side tab - Unlimited (coral) */}
|
||||
<div className="absolute bg-[#f95f62] h-full right-0 top-0 w-[35px] rounded-br-lg rounded-tr-lg flex flex-col items-center justify-center gap-[2px]">
|
||||
<span className="font-['Poppins',sans-serif] text-[12px] text-white/70 [writing-mode:vertical-rl] rotate-180">Card</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[16px] text-white [writing-mode:vertical-rl] rotate-180">Unlimited</span>
|
||||
</div>
|
||||
{/* Selected checkmark */}
|
||||
{isSelected && (
|
||||
<div className="absolute top-2 right-[44px] w-6 h-6 rounded-full bg-[#F95F62] flex items-center justify-center z-10">
|
||||
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
CHECKOUT CONFIGURATION CARD (Mobile-first)
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
function CheckoutConfigCard({
|
||||
item,
|
||||
onChange,
|
||||
onProceed,
|
||||
}: {
|
||||
item: CartItem;
|
||||
onChange: (updates: Partial<CartItem>) => void;
|
||||
onProceed: () => void;
|
||||
}) {
|
||||
const [daysOpen, setDaysOpen] = useState(false);
|
||||
const originalPrice = (item.pricePerUnit * item.quantity * 1.35);
|
||||
const totalPrice = item.pricePerUnit * item.quantity;
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-2xl shadow-[0px_4px_24px_0px_rgba(0,0,0,0.06)] overflow-hidden w-full max-w-[400px]">
|
||||
{/* City header */}
|
||||
<div className="pt-6 pb-2 text-center">
|
||||
<h4 className="font-poppins text-lg leading-snug font-medium text-[#2a2a2a]">{item.city}</h4>
|
||||
<div className="mt-2 flex justify-center">
|
||||
<span className={`inline-flex items-center px-4 py-1 rounded-full font-poppins text-xs font-medium ${
|
||||
item.cardType === 'Flexi'
|
||||
? 'bg-[#f95faf]/10 text-[#f95faf]'
|
||||
: 'bg-[#f95f62]/10 text-[#f95f62]'
|
||||
}`}>
|
||||
{item.cardType} Card
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Configuration rows */}
|
||||
<div className="px-6 py-4 space-y-0">
|
||||
{/* No. of Adults */}
|
||||
<div className="flex items-center justify-between py-4 border-b border-gray-100">
|
||||
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">No. of Adults</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => item.adults > 1 && onChange({ adults: item.adults - 1 })}
|
||||
disabled={item.adults <= 1}
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${
|
||||
item.adults <= 1 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'
|
||||
}`}
|
||||
>
|
||||
<Minus className="w-4 h-4" />
|
||||
</button>
|
||||
<span className="font-poppins text-base font-medium text-[#2a2a2a] w-5 text-center tabular-nums">{item.adults}</span>
|
||||
<button
|
||||
onClick={() => onChange({ adults: item.adults + 1 })}
|
||||
className="w-8 h-8 rounded-full bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20 flex items-center justify-center transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* No. of Children */}
|
||||
<div className="flex items-center justify-between py-4 border-b border-gray-100">
|
||||
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">No. of Children</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => item.children > 0 && onChange({ children: item.children - 1 })}
|
||||
disabled={item.children <= 0}
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${
|
||||
item.children <= 0 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'
|
||||
}`}
|
||||
>
|
||||
<Minus className="w-4 h-4" />
|
||||
</button>
|
||||
<span className="font-poppins text-base font-medium text-[#2a2a2a] w-5 text-center tabular-nums">{item.children}</span>
|
||||
<button
|
||||
onClick={() => onChange({ children: item.children + 1 })}
|
||||
className="w-8 h-8 rounded-full bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20 flex items-center justify-center transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* No. of Days (dropdown) */}
|
||||
<div className="flex items-center justify-between py-4 border-b border-gray-100">
|
||||
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">
|
||||
{item.cardType === 'Flexi' ? 'No. of Attractions' : 'No. of Days'}
|
||||
</span>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setDaysOpen(!daysOpen)}
|
||||
className="flex items-center gap-2 border border-[#f95f62]/30 rounded-lg px-3 py-1.5 min-w-[72px] justify-between hover:border-[#f95f62] transition-colors"
|
||||
>
|
||||
<span className="font-poppins text-base font-medium text-[#f95f62] tabular-nums">{item.days}</span>
|
||||
<ChevronDown className={`w-4 h-4 text-[#f95f62] transition-transform ${daysOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{daysOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -4, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: -4, scale: 0.95 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-100 z-30 min-w-[72px] overflow-hidden"
|
||||
>
|
||||
{dayOptions.map((d) => (
|
||||
<button
|
||||
key={d}
|
||||
onClick={() => { onChange({ days: d }); setDaysOpen(false); }}
|
||||
className={`w-full px-3 py-2 text-left font-poppins text-sm transition-colors ${
|
||||
item.days === d
|
||||
? 'bg-[#f95f62]/10 text-[#f95f62] font-medium'
|
||||
: 'text-[#2a2a2a] hover:bg-gray-50 font-normal'
|
||||
}`}
|
||||
>
|
||||
{d}
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* You Pay */}
|
||||
<div className="flex items-center justify-between py-5">
|
||||
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">You Pay</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-poppins text-sm font-normal text-[#aaa] line-through">
|
||||
${originalPrice.toFixed(0)}
|
||||
</span>
|
||||
<span className="font-poppins text-2xl font-medium text-[#f95f62] tracking-tight">
|
||||
${totalPrice.toFixed(0)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Proceed button */}
|
||||
<div className="px-6 pb-6">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.01 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={onProceed}
|
||||
className="w-full py-4 rounded-full bg-[#f95f62] text-white font-poppins text-base font-medium hover:bg-[#e8545a] transition-colors shadow-lg shadow-[#f95f62]/20"
|
||||
>
|
||||
Proceed to Pay
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
MAIN CART PAGE
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
export function CartPageDesign({
|
||||
onBackClick,
|
||||
onHomeClick,
|
||||
onPassesClick,
|
||||
onCheckoutClick,
|
||||
onSecureCheckoutClick,
|
||||
onSignInClick,
|
||||
onSignOutClick,
|
||||
onAttractionsClick,
|
||||
onBlogsClick,
|
||||
onHowItWorksClick,
|
||||
onFAQClick,
|
||||
onPrivacyPolicyClick,
|
||||
onAboutUsClick,
|
||||
onProfileClick,
|
||||
onCityCardsClick,
|
||||
onMagicItineraryClick,
|
||||
onPostCardsClick,
|
||||
onOffersClick,
|
||||
onSuperSavingsClick,
|
||||
onEsimsClick,
|
||||
onHotelDiscountsClick,
|
||||
onContactUsClick,
|
||||
onCartClick,
|
||||
currentPage,
|
||||
user,
|
||||
}: CartPageDesignProps) {
|
||||
const [activeTab, setActiveTab] = useState<'cards' | 'postcards'>('cards');
|
||||
const [cartItems, setCartItems] = useState<CartItem[]>(initialCartItems);
|
||||
const [selectedCardId, setSelectedCardId] = useState<string | null>(null);
|
||||
const [view, setView] = useState<'cart' | 'checkout'>('cart');
|
||||
const [checkoutItem, setCheckoutItem] = useState<CartItem | null>(null);
|
||||
|
||||
const handleRemoveItem = (id: string) => {
|
||||
setCartItems(prev => prev.filter(item => item.id !== id));
|
||||
if (selectedCardId === id) setSelectedCardId(null);
|
||||
};
|
||||
|
||||
const handleSelectCard = (id: string) => {
|
||||
setSelectedCardId(prev => (prev === id ? null : id));
|
||||
};
|
||||
|
||||
const handleGoToCheckout = () => {
|
||||
const item = cartItems.find(i => i.id === selectedCardId);
|
||||
if (item) {
|
||||
setCheckoutItem({ ...item });
|
||||
setView('checkout');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackToCart = () => {
|
||||
setView('cart');
|
||||
setCheckoutItem(null);
|
||||
};
|
||||
|
||||
const handleCheckoutItemChange = (updates: Partial<CartItem>) => {
|
||||
if (!checkoutItem) return;
|
||||
const updated = { ...checkoutItem, ...updates };
|
||||
const prices = priceTable[updated.cardType];
|
||||
if (prices && prices[updated.days] !== undefined) {
|
||||
updated.pricePerUnit = prices[updated.days];
|
||||
}
|
||||
setCheckoutItem(updated);
|
||||
};
|
||||
|
||||
const isEmpty = cartItems.length === 0;
|
||||
const selectedItem = cartItems.find(i => i.id === selectedCardId);
|
||||
const attractions = checkoutItem ? (attractionsData[checkoutItem.city]?.[checkoutItem.cardType] || []) : [];
|
||||
const offers = checkoutItem ? (offersData[checkoutItem.cardType] || []) : [];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#fafafa] font-poppins">
|
||||
<Navbar
|
||||
activeCity="Melbourne" onCityChange={() => {}} onSignInClick={onSignInClick} onSignOutClick={onSignOutClick}
|
||||
onPassesClick={onPassesClick} onCheckoutClick={onCheckoutClick} onHomeClick={onHomeClick}
|
||||
onAttractionsClick={onAttractionsClick} onBlogsClick={onBlogsClick} onHowItWorksClick={onHowItWorksClick}
|
||||
onFAQClick={onFAQClick} onPrivacyPolicyClick={onPrivacyPolicyClick} onAboutUsClick={onAboutUsClick}
|
||||
onProfileClick={onProfileClick} onCityCardsClick={onCityCardsClick} onMagicItineraryClick={onMagicItineraryClick}
|
||||
onPostCardsClick={onPostCardsClick} onOffersClick={onOffersClick} onSuperSavingsClick={onSuperSavingsClick}
|
||||
onEsimsClick={onEsimsClick} onHotelDiscountsClick={onHotelDiscountsClick} onCartClick={onCartClick}
|
||||
currentPage={currentPage as any} user={user}
|
||||
/>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
{view === 'cart' ? (
|
||||
/* ─── CART VIEW ─── */
|
||||
<motion.div
|
||||
key="cart-view"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0, x: -30 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="w-full px-4 sm:px-6 lg:px-10 xl:px-16 pt-32 pb-24 max-w-[1440px] mx-auto"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h2 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
|
||||
<span className="font-light">Your</span>{' '}
|
||||
<span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent">Cart</span>
|
||||
</h2>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1">
|
||||
{isEmpty ? 'Your cart is empty' : `${cartItems.length} ${cartItems.length === 1 ? 'item' : 'items'} in your cart`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tab switcher */}
|
||||
{/* Cards listed directly below */}
|
||||
|
||||
{/* Content */}
|
||||
<AnimatePresence mode="wait">
|
||||
{activeTab === 'cards' ? (
|
||||
<motion.div key="cards-content" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }} transition={{ duration: 0.2 }}>
|
||||
{isEmpty ? (
|
||||
<EmptyState icon={<CreditCard className="w-16 h-16 text-[#F95F62]/20" strokeWidth={1.2} />} title="No cards in your cart" description="Browse our city passes to unlock amazing experiences and savings on your next adventure" actionLabel="Explore Passes" onAction={onPassesClick} />
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{/* Table header (desktop) */}
|
||||
<div className="md:grid md:grid-cols-12 gap-4 px-5 pb-2">
|
||||
<div className="col-span-5 font-poppins text-xs font-medium text-[#8e8e8e] uppercase tracking-wider">City Cards</div>
|
||||
<div className="col-span-2 font-poppins text-xs font-medium text-[#8e8e8e] uppercase tracking-wider text-center">Travellers</div>
|
||||
<div className="col-span-1 font-poppins text-xs font-medium text-[#8e8e8e] uppercase tracking-wider text-center">Qty</div>
|
||||
<div className="col-span-3 font-poppins text-xs font-medium text-[#8e8e8e] uppercase tracking-wider text-right">Price</div>
|
||||
<div className="col-span-1" />
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{cartItems.map((item) => {
|
||||
const isSelected = selectedCardId === item.id;
|
||||
const totalPrice = item.pricePerUnit * item.quantity;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
layout
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, x: -60, transition: { duration: 0.25 } }}
|
||||
onClick={() => handleSelectCard(item.id)}
|
||||
className={`relative bg-white rounded-2xl overflow-hidden cursor-pointer transition-all duration-300 ${
|
||||
isSelected ? 'ring-2 ring-[#F95F62] shadow-lg shadow-[#F95F62]/8' : 'ring-1 ring-gray-100 hover:ring-gray-200 hover:shadow-md'
|
||||
}`}
|
||||
>
|
||||
{/* Selected badge */}
|
||||
<AnimatePresence>
|
||||
{isSelected && (
|
||||
<motion.div initial={{ scale: 0 }} animate={{ scale: 1 }} exit={{ scale: 0 }} className="absolute top-3 left-3 z-20 w-6 h-6 rounded-full bg-[#F95F62] flex items-center justify-center shadow-md">
|
||||
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Mobile layout */}
|
||||
<div className="md:hidden flex gap-4 p-4">
|
||||
<div className="w-20 h-20 rounded-xl overflow-hidden flex-shrink-0 relative">
|
||||
<ImageWithFallback src={item.image} alt={item.city} className="absolute inset-0 w-full h-full object-cover" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h5 className="font-poppins text-base leading-snug font-medium text-[#2a2a2a]">{item.city}</h5>
|
||||
<div className="flex items-center gap-2 mt-0.5">
|
||||
<span className={`inline-flex px-2 py-0.5 rounded-full text-[10px] font-medium ${item.cardType === 'Flexi' ? 'bg-[#F95FAF]/10 text-[#F95FAF]' : 'bg-[#F95F62]/10 text-[#F95F62]'}`}>{item.cardType}</span>
|
||||
<span className="font-poppins text-xs font-normal text-[#8e8e8e]">{item.days}d</span>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={(e) => { e.stopPropagation(); handleRemoveItem(item.id); }} className="p-1.5 rounded-lg text-gray-300 hover:text-[#F95F62] hover:bg-red-50 transition-colors">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-2">
|
||||
<span className="font-poppins text-xs font-normal text-[#8e8e8e]">{item.adults}A · {item.children}C · Qty {item.quantity}</span>
|
||||
<div className="text-right">
|
||||
<span className="font-poppins text-base font-medium text-[#F95F62] tracking-tight">${totalPrice.toFixed(2)}</span>
|
||||
{item.quantity > 1 && <span className="block font-poppins text-[10px] font-normal text-[#aaa]">${item.pricePerUnit.toFixed(2)}/ea</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop layout */}
|
||||
<div className="md:grid md:grid-cols-12 gap-4 items-center p-5">
|
||||
<div className="col-span-5 flex items-center gap-4">
|
||||
<div className="w-16 h-16 rounded-xl overflow-hidden flex-shrink-0 relative">
|
||||
<ImageWithFallback src={item.image} alt={item.city} className="absolute inset-0 w-full h-full object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-poppins text-base leading-snug font-medium text-[#2a2a2a]">{item.city}</h5>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className={`inline-flex px-2.5 py-0.5 rounded-full text-xs font-medium ${item.cardType === 'Flexi' ? 'bg-[#F95FAF]/10 text-[#F95FAF]' : 'bg-[#F95F62]/10 text-[#F95F62]'}`}>{item.cardType} Card</span>
|
||||
<span className="flex items-center gap-1 font-poppins text-xs font-normal text-[#8e8e8e]"><Calendar className="w-3 h-3" />{item.days} days</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 text-center">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<span className="flex items-center gap-1 font-poppins text-sm font-normal text-[#555]"><Users className="w-3.5 h-3.5 text-[#8e8e8e]" />{item.adults}</span>
|
||||
<span className="flex items-center gap-1 font-poppins text-sm font-normal text-[#555]"><Baby className="w-3.5 h-3.5 text-[#8e8e8e]" />{item.children}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-center">
|
||||
<span className="font-poppins text-sm font-medium text-[#2a2a2a] bg-gray-50 px-4 py-1.5 rounded-full">{item.quantity}</span>
|
||||
</div>
|
||||
<div className="col-span-3 text-right">
|
||||
<span className="font-poppins text-lg font-medium text-[#F95F62] tracking-tight">${totalPrice.toFixed(2)}</span>
|
||||
{item.quantity > 1 && <span className="block font-poppins text-xs font-normal text-[#aaa] mt-0.5">${item.pricePerUnit.toFixed(2)} per unit</span>}
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-end">
|
||||
<button onClick={(e) => { e.stopPropagation(); handleRemoveItem(item.id); }} className="p-2 rounded-lg text-gray-300 hover:text-[#F95F62] hover:bg-red-50 transition-all" title="Remove from cart">
|
||||
<Trash2 className="w-4.5 h-4.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Bottom checkout bar */}
|
||||
<motion.div layout className="mt-6 bg-white rounded-2xl ring-1 ring-gray-100 p-5 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="text-center sm:text-left">
|
||||
{selectedItem ? (
|
||||
<>
|
||||
<p className="font-poppins text-xs font-normal text-[#8e8e8e]">
|
||||
Selected: {selectedItem.city} {selectedItem.cardType} · {selectedItem.days}d · Qty {selectedItem.quantity}
|
||||
</p>
|
||||
<p className="font-poppins text-2xl font-medium text-[#F95F62] tracking-tight mt-0.5">
|
||||
${(selectedItem.pricePerUnit * selectedItem.quantity).toFixed(2)}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="font-poppins text-sm font-normal text-[#8e8e8e]">Tap a card above to select it for checkout</p>
|
||||
)}
|
||||
</div>
|
||||
<motion.button
|
||||
whileHover={selectedItem ? { scale: 1.02 } : {}}
|
||||
whileTap={selectedItem ? { scale: 0.98 } : {}}
|
||||
onClick={handleGoToCheckout}
|
||||
disabled={!selectedItem}
|
||||
className={`w-full sm:w-auto px-8 py-3.5 rounded-xl font-poppins text-base font-medium flex items-center justify-center gap-2 transition-all duration-200 ${
|
||||
selectedItem ? 'bg-[#F95F62] text-white hover:bg-[#e8545a] shadow-lg shadow-[#F95F62]/20' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
Secure Checkout <ChevronRight className="w-4 h-4" />
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div key="postcards-content" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }} transition={{ duration: 0.2 }}>
|
||||
<EmptyState icon={<Mail className="w-16 h-16 text-[#F95F62]/20" strokeWidth={1.2} />} title="No post cards yet" description="Send beautiful digital post cards to friends and family from your favourite destinations around the world" actionLabel="Browse Post Cards" onAction={onPostCardsClick} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
) : (
|
||||
/* ─── CHECKOUT VIEW ─── */
|
||||
<motion.div
|
||||
key="checkout-view"
|
||||
initial={{ opacity: 0, x: 40 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 40 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="w-full px-4 sm:px-6 lg:px-10 xl:px-16 pt-32 pb-24 max-w-[1440px] mx-auto"
|
||||
>
|
||||
{checkoutItem && (
|
||||
<>
|
||||
{/* Back */}
|
||||
<button onClick={handleBackToCart} className="flex items-center gap-2 text-[#8e8e8e] hover:text-[#2a2a2a] transition-colors font-poppins text-sm font-normal mb-8">
|
||||
<ArrowLeft className="w-4 h-4" />Back to Cart
|
||||
</button>
|
||||
|
||||
{/* Stepper */}
|
||||
{/* <CheckoutStepper currentStep={2} /> */}
|
||||
|
||||
{/* Checkout heading */}
|
||||
<div className="mb-10">
|
||||
<h2 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
|
||||
<span className="font-light">Checkout</span>{' '}
|
||||
<span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent">{checkoutItem.city}</span>
|
||||
</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
{/* Left column */}
|
||||
<div className="flex-1 space-y-8">
|
||||
|
||||
{/* ── Card Type Selection (Figma cards) ── */}
|
||||
<div>
|
||||
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">
|
||||
Choose Your Card
|
||||
</h3>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
|
||||
Select the card type that best suits your travel style
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-[16px]">
|
||||
{/* Flexi */}
|
||||
<button
|
||||
onClick={() => handleCheckoutItemChange({ cardType: 'Flexi' })}
|
||||
className="relative transition-all duration-200"
|
||||
>
|
||||
<FlexiCardPreview
|
||||
city={checkoutItem.city}
|
||||
adultPrice={priceTable.Flexi[checkoutItem.days] || 80}
|
||||
childPrice={10}
|
||||
isSelected={checkoutItem.cardType === 'Flexi'}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Unlimited */}
|
||||
<button
|
||||
onClick={() => handleCheckoutItemChange({ cardType: 'Unlimited' })}
|
||||
className="relative transition-all duration-200"
|
||||
>
|
||||
<UnlimitedCardPreview
|
||||
city={checkoutItem.city}
|
||||
adultPrice={priceTable.Unlimited[checkoutItem.days] || 120}
|
||||
childPrice={20}
|
||||
isSelected={checkoutItem.cardType === 'Unlimited'}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* ── Config Card (mobile only) — right after card selection ── */}
|
||||
<div className="lg:hidden mt-6">
|
||||
<CheckoutConfigCard
|
||||
item={checkoutItem}
|
||||
onChange={handleCheckoutItemChange}
|
||||
onProceed={() => checkoutItem && onSecureCheckoutClick?.(checkoutItem)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Features Comparison */}
|
||||
<div className="mt-6 bg-[#f5f5f5] rounded-xl p-4">
|
||||
<div className="grid grid-cols-[1fr_70px_70px] gap-y-0 items-center">
|
||||
{/* Header */}
|
||||
<p className="font-poppins font-medium text-sm text-[#2a2a2a] py-3">Features</p>
|
||||
<p className="font-poppins font-medium text-sm text-[#2a2a2a] text-center py-3">Flexi</p>
|
||||
<p className="font-poppins font-medium text-sm text-[#2a2a2a] text-center py-3">Unlimited</p>
|
||||
{[
|
||||
{ feature: 'Access to attractions', flexi: true, unlimited: true },
|
||||
{ feature: 'Entry to attractions', flexi: true, unlimited: true },
|
||||
{ feature: 'Access to experiences', flexi: true, unlimited: true },
|
||||
{ feature: 'Entry to sites', flexi: false, unlimited: true },
|
||||
{ feature: 'Access to venues', flexi: true, unlimited: true },
|
||||
{ feature: 'Entry to events', flexi: true, unlimited: true },
|
||||
{ feature: 'Access to experiences', flexi: false, unlimited: true },
|
||||
{ feature: 'Access to Itinerary creation', flexi: false, unlimited: true },
|
||||
{ feature: 'Access to postcard creation', flexi: false, unlimited: true },
|
||||
].map((row, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<p className="font-poppins font-normal text-[13px] text-[#2a2a2a] py-2.5 border-t border-[rgba(0,0,0,0.08)] flex items-center gap-1.5">
|
||||
<span className="text-[#2a2a2a]">•</span> {row.feature}
|
||||
</p>
|
||||
<div className="flex justify-center py-2.5 border-t border-[rgba(0,0,0,0.08)]">
|
||||
{row.flexi ? (
|
||||
<div className="w-5 h-5 rounded-full bg-[#F95F62] flex items-center justify-center">
|
||||
<Check className="w-3 h-3 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
) : (
|
||||
<span className="font-poppins text-[13px] text-[rgba(0,0,0,0.3)]">–</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-center py-2.5 border-t border-[rgba(0,0,0,0.08)]">
|
||||
{row.unlimited ? (
|
||||
<div className="w-5 h-5 rounded-full bg-[#F95F62] flex items-center justify-center">
|
||||
<Check className="w-3 h-3 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
) : (
|
||||
<span className="font-poppins text-[13px] text-[rgba(0,0,0,0.3)]">–</span>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Offers ── */}
|
||||
<div>
|
||||
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">
|
||||
{checkoutItem.cardType} Card Offers
|
||||
</h3>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
|
||||
Exclusive deals and discounts included with your {checkoutItem.cardType} pass
|
||||
</p>
|
||||
<div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 snap-x snap-mandatory scrollbar-hide">
|
||||
{offers.map((offer, idx) => (
|
||||
<div key={idx} className="relative bg-white rounded-xl shrink-0 w-[180px] h-[260px] snap-start">
|
||||
<div className="flex flex-col gap-2 items-start overflow-hidden p-3 rounded-xl h-full">
|
||||
<div className="h-[120px] w-full rounded-lg overflow-hidden shrink-0 relative">
|
||||
<ImageWithFallback
|
||||
src={offer.image}
|
||||
alt={offer.title}
|
||||
className="absolute inset-0 w-full h-full object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full h-[44px] overflow-hidden">
|
||||
<p className="font-['Poppins',sans-serif] font-normal text-[18px] text-black tracking-[-0.72px] leading-[22px] line-clamp-2">
|
||||
{offer.title}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex-1">
|
||||
<p className="font-['Poppins',sans-serif] font-normal text-[12px] text-[rgba(0,0,0,0.6)] leading-[16px] line-clamp-3">
|
||||
{offer.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 border border-[rgba(249,95,98,0.24)] rounded-xl pointer-events-none" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Available Attractions ── */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">Available Attractions</h3>
|
||||
<span className="font-poppins text-xs font-medium text-[#F95F62] bg-[#F95F62]/10 px-3 py-1 rounded-full">{attractions.length} included</span>
|
||||
</div>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
|
||||
Explore all the experiences you can enjoy with your pass
|
||||
</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
{attractions.map((a) => (
|
||||
<div key={a.id} className="group relative rounded-xl overflow-hidden">
|
||||
<div className="aspect-[4/3] relative">
|
||||
<ImageWithFallback src={a.image} alt={a.name} className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/10 to-transparent" />
|
||||
<div className="absolute top-2 right-2">
|
||||
<span className="inline-flex px-2 py-0.5 rounded-full bg-white/90 backdrop-blur-sm text-[10px] font-poppins font-medium text-[#555]">{a.category}</span>
|
||||
</div>
|
||||
<div className="absolute bottom-2 left-2 right-2">
|
||||
<h6 className="font-poppins text-sm leading-snug font-medium text-white drop-shadow-sm">{a.name}</h6>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right column: Config card (desktop only, sticky) */}
|
||||
<div className="hidden lg:block lg:w-[420px] flex-shrink-0">
|
||||
<div className="lg:sticky lg:top-28">
|
||||
<CheckoutConfigCard
|
||||
item={checkoutItem}
|
||||
onChange={handleCheckoutItemChange}
|
||||
onProceed={() => checkoutItem && onSecureCheckoutClick?.(checkoutItem)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<Footer
|
||||
onHomeClick={onHomeClick} onPassesClick={onPassesClick} onAttractionsClick={onAttractionsClick}
|
||||
onBlogsClick={onBlogsClick} onHowItWorksClick={onHowItWorksClick} onFAQClick={onFAQClick}
|
||||
onPrivacyPolicyClick={onPrivacyPolicyClick} onAboutUsClick={onAboutUsClick} onContactUsClick={onContactUsClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Empty state ─── */
|
||||
function EmptyState({ icon, title, description, actionLabel, onAction }: {
|
||||
icon: React.ReactNode; title: string; description: string; actionLabel: string; onAction?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<motion.div initial={{ opacity: 0, scale: 0.98 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.4 }} className="flex flex-col items-center justify-center py-20 max-w-sm mx-auto text-center">
|
||||
<motion.div className="w-28 h-28 rounded-3xl bg-[#fee7e7]/50 flex items-center justify-center mb-6" animate={{ y: [0, -6, 0] }} transition={{ duration: 3, repeat: Infinity, ease: 'easeInOut' }}>{icon}</motion.div>
|
||||
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a] mb-2">{title}</h3>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mb-8">{description}</p>
|
||||
<motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} onClick={onAction} className="bg-[#F95F62] text-white font-poppins text-base font-medium px-8 py-3.5 rounded-xl hover:bg-[#e8545a] transition-colors shadow-lg shadow-[#F95F62]/15 w-full">{actionLabel}</motion.button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -1,886 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import {
|
||||
ArrowLeft, Check, Minus, Plus, ChevronDown
|
||||
} from 'lucide-react';
|
||||
import Navbar from '../components/Navbar';
|
||||
import { Footer } from '../components/Footer';
|
||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAddCardToCartMutation, useGetCheckoutPageDataQuery } from '../Redux/services/cards.service';
|
||||
import LoadingSpinner from '../components/LoadingSpinner';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
/* ─── Types ─── */
|
||||
export interface CartItem {
|
||||
id: string;
|
||||
city: string;
|
||||
cardType: 'Flexi' | 'Unlimited';
|
||||
days: number;
|
||||
adults: number;
|
||||
children: number;
|
||||
quantity: number;
|
||||
pricePerUnit: number;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface Attraction {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
category: string;
|
||||
included: boolean;
|
||||
}
|
||||
|
||||
/* ─── Data (Same as Original) ─── */
|
||||
const dayOptions = [3, 6, 12, 18, 24];
|
||||
|
||||
const priceTable: Record<string, Record<number, number>> = {
|
||||
Flexi: { 3: 49.5, 6: 69, 12: 99, 18: 129, 24: 159 },
|
||||
Unlimited: { 3: 79, 6: 109, 12: 149, 18: 189, 24: 229 },
|
||||
};
|
||||
|
||||
const attractionsData: Record<string, Record<string, Attraction[]>> = {
|
||||
Melbourne: {
|
||||
Flexi: [
|
||||
{ id: 'mel-1', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-2', name: 'Melbourne Zoo', image: 'https://images.unsplash.com/photo-1730074888490-31239540bacf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjB6b28lMjB3aWxkbGlmZXxlbnwxfHx8fDE3NzYzMTk5NzB8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-3', name: 'Royal Botanic Gardens', image: 'https://images.unsplash.com/photo-1585894507208-eeead8cb9a56?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBib3RhbmljYWwlMjBnYXJkZW4lMjBncmVlbnxlbnwxfHx8fDE3NzYzMTk5NzF8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Nature', included: true },
|
||||
{ id: 'mel-4', name: 'NGV Art Gallery', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
|
||||
],
|
||||
Unlimited: [
|
||||
{ id: 'mel-1', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-2', name: 'Melbourne Zoo', image: 'https://images.unsplash.com/photo-1730074888490-31239540bacf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjB6b28lMjB3aWxkbGlmZXxlbnwxfHx8fDE3NzYzMTk5NzB8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-3', name: 'Royal Botanic Gardens', image: 'https://images.unsplash.com/photo-1585894507208-eeead8cb9a56?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBib3RhbmljYWwlMjBnYXJkZW4lMjBncmVlbnxlbnwxfHx8fDE3NzYzMTk5NzF8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Nature', included: true },
|
||||
{ id: 'mel-4', name: 'NGV Art Gallery', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
|
||||
{ id: 'mel-5', name: 'Melbourne Star Wheel', image: 'https://images.unsplash.com/photo-1769880659692-fa77e04c5ffa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxvYnNlcnZhdGlvbiUyMHdoZWVsJTIwYW11c2VtZW50JTIwbmlnaHR8ZW58MXx8fHwxNzc2MzE5OTc2fDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
|
||||
{ id: 'mel-6', name: 'Penguin Parade', image: 'https://images.unsplash.com/photo-1670391050251-d1cfbc3891c4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxwZW5ndWlucyUyMHdpbGRsaWZlJTIwbmF0dXJlfGVufDF8fHx8MTc3NjMxOTk3Nnww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'mel-7', name: 'Yarra River Cruise', image: 'https://images.unsplash.com/photo-1562003914-018a4a6c2171?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyaXZlciUyMGNydWlzZSUyMGJvYXQlMjBjaXR5fGVufDF8fHx8MTc3NjMxOTk3M3ww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
|
||||
],
|
||||
},
|
||||
Sydney: {
|
||||
Flexi: [
|
||||
{ id: 'syd-1', name: 'Harbour Bridge Climb', image: 'https://images.unsplash.com/photo-1767974062666-2685a670e353?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBoYXJib3VyJTIwYnJpZGdlJTIwY2xpbWJ8ZW58MXx8fHwxNzc2MzE5OTcxfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Adventure', included: true },
|
||||
{ id: 'syd-2', name: 'Taronga Zoo', image: 'https://images.unsplash.com/photo-1704852168456-b70e08441917?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjB0YXJvbmdhJTIwem9vJTIwYW5pbWFsc3xlbnwxfHx8fDE3NzYzMTk5NzJ8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'syd-3', name: 'Art Gallery NSW', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
|
||||
],
|
||||
Unlimited: [
|
||||
{ id: 'syd-1', name: 'Harbour Bridge Climb', image: 'https://images.unsplash.com/photo-1767974062666-2685a670e353?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjBoYXJib3VyJTIwYnJpZGdlJTIwY2xpbWJ8ZW58MXx8fHwxNzc2MzE5OTcxfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Adventure', included: true },
|
||||
{ id: 'syd-2', name: 'Taronga Zoo', image: 'https://images.unsplash.com/photo-1704852168456-b70e08441917?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxTeWRuZXklMjB0YXJvbmdhJTIwem9vJTIwYW5pbWFsc3xlbnwxfHx8fDE3NzYzMTk5NzJ8MA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
{ id: 'syd-3', name: 'Art Gallery NSW', image: 'https://images.unsplash.com/photo-1752429242469-55ba7ec210d2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnQlMjBnYWxsZXJ5JTIwbXVzZXVtJTIwaW50ZXJpb3J8ZW58MXx8fHwxNzc2MzE5OTczfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Culture', included: true },
|
||||
{ id: 'syd-4', name: 'Sydney Harbour Cruise', image: 'https://images.unsplash.com/photo-1562003914-018a4a6c2171?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyaXZlciUyMGNydWlzZSUyMGJvYXQlMjBjaXR5fGVufDF8fHx8MTc3NjMxOTk3M3ww&ixlib=rb-4.1.0&q=80&w=1080', category: 'Experience', included: true },
|
||||
{ id: 'syd-5', name: 'SEA LIFE Aquarium', image: 'https://images.unsplash.com/photo-1536845111858-bb269af65cb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxNZWxib3VybmUlMjBhcXVhcml1bSUyMHVuZGVyd2F0ZXJ8ZW58MXx8fHwxNzc2MzE5OTcwfDA&ixlib=rb-4.1.0&q=80&w=1080', category: 'Wildlife', included: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const offersData: Record<string, { title: string; description: string; image: string }[]> = {
|
||||
Flexi: [
|
||||
{ title: 'Astor Hotels Ultra Deluxe', description: '15% Discount on all treatments for first-time clients', image: 'https://images.unsplash.com/photo-1715191904112-4a5d9c3089fa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxsdXh1cnklMjBob3RlbCUyMHJlc29ydCUyMGV4dGVyaW9yfGVufDF8fHx8MTc3NjMyMTM2MXww&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Green Valley Spa Lux', description: '20% Off on membership plans for new members', image: 'https://images.unsplash.com/photo-1759216853079-831ef8c8b327?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzcGElMjB3ZWxsbmVzcyUyMHRyZWF0bWVudCUyMGludGVyaW9yfGVufDF8fHx8MTc3NjMyMTM2M3ww&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Harbour Dining Co.', description: '10% Off your first dining experience at waterfront', image: 'https://images.unsplash.com/photo-1676471932681-45fa972d848a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyZXN0YXVyYW50JTIwZmluZSUyMGRpbmluZ3xlbnwxfHx8fDE3NzYzMTkxNDl8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'National Gallery Exhibition', description: 'Free audio guide with every gallery visit', image: 'https://images.unsplash.com/photo-1569342380852-035f42d9ca41?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtdXNldW0lMjBnYWxsZXJ5JTIwZXhoaWJpdGlvbnxlbnwxfHx8fDE3NzYyNDYwMjh8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Sunset Harbour Cruise', description: 'Complimentary drink on every sunset cruise booking', image: 'https://images.unsplash.com/photo-1765783800962-83d99ff7b158?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjcnVpc2UlMjBib2F0JTIwaGFyYm9yJTIwdG91cnxlbnwxfHx8fDE3NzYzMjE2MDd8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
],
|
||||
Unlimited: [
|
||||
{ title: 'SkyView Ferris Wheel', description: 'Complimentary second ride for all pass holders', image: 'https://images.unsplash.com/photo-1626209025747-b41ee6ec191f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxmZXJyaXMlMjB3aGVlbCUyMGFtdXNlbWVudCUyMHBhcmt8ZW58MXx8fHwxNzc2MzE3NDI2fDA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'City Mall Boutique', description: '15% Off at select boutique stores with your pass', image: 'https://images.unsplash.com/photo-1567966689299-819568579d36?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxzaG9wcGluZyUyMG1hbGwlMjBib3V0aXF1ZSUyMHJldGFpbHxlbnwxfHx8fDE3NzYzMjEzNjN8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Adventure Outfitters', description: 'Free gear rental on outdoor adventure bookings', image: 'https://images.unsplash.com/photo-1761131221577-0716baffc6ef?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhZHZlbnR1cmUlMjBzcG9ydHMlMjBvdXRkb29yJTIwYWN0aXZpdHl8ZW58MXx8fHwxNzc2MzIxMzYzfDA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Skyline Rooftop Lounge', description: 'Buy one get one free on signature cocktails', image: 'https://images.unsplash.com/photo-1642114955097-8f3d0e141641?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxyb29mdG9wJTIwYmFyJTIwY2l0eSUyMHNreWxpbmUlMjBuaWdodHxlbnwxfHx8fDE3NzYyNDU2NTl8MA&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
{ title: 'Yarra Valley Wines', description: 'Exclusive wine tasting tour with pass holders discount', image: 'https://images.unsplash.com/photo-1764649841527-c8852b63cc53?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx3aW5lJTIwdGFzdGluZyUyMHZpbmV5YXJkJTIwY2VsbGFyfGVufDF8fHx8MTc3NjMyMTYwOHww&ixlib=rb-4.1.0&q=80&w=1080' },
|
||||
],
|
||||
};
|
||||
|
||||
/* ─── FIGMA CARD PREVIEWS (Exact Copy) ─── */
|
||||
function FlexiCardPreview({ city, adultPrice, childPrice, isSelected, image }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean, image: string; }) {
|
||||
return (
|
||||
<div className={`relative h-[160px] w-full rounded-lg transition-all duration-200 ${isSelected ? 'ring-2 ring-[#F95F62] shadow-md shadow-[#F95F62]/10' : 'hover:shadow-md'
|
||||
}`}>
|
||||
{/* Card bg */}
|
||||
<div className="absolute inset-0 bg-white border border-[rgba(249,95,175,0.2)] rounded-lg shadow-[0px_4px_20px_0px_rgba(0,0,0,0.06)]" />
|
||||
{/* City image */}
|
||||
<div className="absolute h-[158px] left-[1px] top-[1px] w-[103px] rounded-bl-[7px] rounded-tl-[7px] overflow-hidden">
|
||||
<img alt="" className="absolute inset-0 w-full h-full object-cover" src={image} />
|
||||
</div>
|
||||
{/* City name - left aligned */}
|
||||
<div className="absolute left-[112px] top-[12px]">
|
||||
<p className="font-['Poppins',sans-serif] font-medium text-[16px] text-[#2a2a2a] leading-[22px] whitespace-nowrap">{city}</p>
|
||||
</div>
|
||||
{/* Pricing */}
|
||||
<div className="absolute left-[112px] top-[40px] flex flex-col gap-[6px]">
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">From</span>
|
||||
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${adultPrice}</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Adult</span>
|
||||
</div>
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">and</span>
|
||||
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${childPrice}</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Child</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Description */}
|
||||
<div className="absolute left-[112px] top-[112px] right-[44px]">
|
||||
<p className="font-['Poppins',sans-serif] text-[11px] text-left text-[rgba(0,0,0,0.4)] tracking-[0.06px] leading-[14px]">
|
||||
Dive into an extensive selection of thrilling destinations!
|
||||
</p>
|
||||
</div>
|
||||
{/* Side tab - Flexi (pink) */}
|
||||
<div className="absolute bg-[#f95faf] h-full right-0 top-0 w-[35px] rounded-br-lg rounded-tr-lg flex flex-col items-center justify-center gap-[2px]">
|
||||
<span className="font-['Poppins',sans-serif] text-[12px] text-white/70 [writing-mode:vertical-rl] rotate-180">Card</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[16px] text-white [writing-mode:vertical-rl] rotate-180">Flexi</span>
|
||||
</div>
|
||||
{/* Selected checkmark */}
|
||||
{isSelected && (
|
||||
<div className="absolute top-2 right-[44px] w-6 h-6 rounded-full bg-[#F95F62] flex items-center justify-center z-10">
|
||||
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UnlimitedCardPreview({ city, adultPrice, childPrice, isSelected, image }: { city: string; adultPrice: number; childPrice: number; isSelected: boolean, image: string; }) {
|
||||
return (
|
||||
<div className={`relative h-[160px] w-full rounded-lg transition-all duration-200 ${isSelected ? 'ring-2 ring-[#F95F62] shadow-md shadow-[#F95F62]/10' : 'hover:shadow-md'
|
||||
}`}>
|
||||
{/* Card bg */}
|
||||
<div className="absolute inset-0 bg-white border border-[rgba(0,0,0,0.2)] rounded-lg" />
|
||||
{/* City image */}
|
||||
<div className="absolute h-[158px] left-[1px] top-[1px] w-[103px] rounded-bl-[7px] rounded-tl-[7px] overflow-hidden">
|
||||
<img alt="" className="absolute inset-0 w-full h-full object-cover" src={image} />
|
||||
</div>
|
||||
{/* City name - left aligned */}
|
||||
<div className="absolute left-[112px] top-[12px]">
|
||||
<p className="font-['Poppins',sans-serif] font-medium text-[16px] text-[#2a2a2a] leading-[20px] whitespace-nowrap">{city}</p>
|
||||
</div>
|
||||
{/* Pricing */}
|
||||
<div className="absolute left-[112px] top-[40px] flex flex-col gap-[6px]">
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">From</span>
|
||||
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${adultPrice}</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Adult</span>
|
||||
</div>
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.6)] tracking-[0.06px]">and</span>
|
||||
<span className="font-['Poppins',sans-serif] font-medium text-[24px] text-[#f95f62] tracking-[-0.96px] leading-[1.3]">${childPrice}</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[11px] text-[rgba(0,0,0,0.8)] tracking-[0.06px]">/Child</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Description */}
|
||||
<div className="absolute left-[112px] top-[112px] right-[44px]">
|
||||
<p className="font-['Poppins',sans-serif] text-[11px] text-left text-[rgba(0,0,0,0.4)] tracking-[0.06px] leading-[14px]">
|
||||
Dive into an extensive selection of thrilling destinations!
|
||||
</p>
|
||||
</div>
|
||||
{/* Side tab - Unlimited (coral) */}
|
||||
<div className="absolute bg-[#f95f62] h-full right-0 top-0 w-[35px] rounded-br-lg rounded-tr-lg flex flex-col items-center justify-center gap-[2px]">
|
||||
<span className="font-['Poppins',sans-serif] text-[12px] text-white/70 [writing-mode:vertical-rl] rotate-180">Card</span>
|
||||
<span className="font-['Poppins',sans-serif] text-[16px] text-white [writing-mode:vertical-rl] rotate-180">Unlimited</span>
|
||||
</div>
|
||||
{/* Selected checkmark */}
|
||||
{isSelected && (
|
||||
<div className="absolute top-2 right-[44px] w-6 h-6 rounded-full bg-[#F95F62] flex items-center justify-center z-10">
|
||||
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── CheckoutConfigCard (Exact Copy) ─── */
|
||||
function CheckoutConfigCard({
|
||||
item,
|
||||
onProceed,
|
||||
}: {
|
||||
item: any;
|
||||
onProceed: () => void;
|
||||
}) {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [noOfAdults, setNoOfAdults] = useState(1)
|
||||
const [noOfChildren, setNoOfChildren] = useState(0)
|
||||
const [noOfAttractions, setNoOfAttractions] = useState(item?.minNumber);
|
||||
const [noOfDays, setNoOfDays] = useState(item?.minNumber)
|
||||
|
||||
const cityId = localStorage.getItem("cityId")
|
||||
const cityName = localStorage.getItem("cityName")
|
||||
const cardTypeId = item?.cardType?.id
|
||||
const cardId = item?.id
|
||||
const cardMode = item?.cardType?.name === "selective_pass" ? "flexi" : "unlimited"
|
||||
const adultPrice = item?.adultPrice * noOfAdults
|
||||
const childPrice = item?.childPrice * noOfChildren
|
||||
const basePrice = adultPrice + childPrice
|
||||
const taxAmount = basePrice * 0.1
|
||||
const strikedPrice = basePrice + 20
|
||||
|
||||
const [addCardToCart] = useAddCardToCartMutation()
|
||||
|
||||
useEffect(() => {
|
||||
setNoOfAttractions(item?.minNumber)
|
||||
setNoOfDays(item?.minNumber)
|
||||
}, [item])
|
||||
|
||||
const numberArray = Array.from(
|
||||
{ length: item?.maxNumber - item?.minNumber + 1 },
|
||||
(_, i) => item?.minNumber + i
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const cardBookingDetails = {
|
||||
cityXid: cityId,
|
||||
cardTypeXid: cardTypeId,
|
||||
cardXid: cardId,
|
||||
cardMode, // stays as-is
|
||||
totalAdult: noOfAdults,
|
||||
baseAmount: basePrice, // static value
|
||||
taxAmount,
|
||||
totalChild: noOfChildren,
|
||||
noOfAttractions,
|
||||
noOfDays
|
||||
};
|
||||
|
||||
const handleProceedToPayment = async () => {
|
||||
try {
|
||||
console.log("Adding card to cart", cardBookingDetails);
|
||||
const response = await addCardToCart(cardBookingDetails);
|
||||
console.log(response)
|
||||
const bookingId = response?.data?.id
|
||||
navigate(`/payment/${bookingId}`)
|
||||
} catch (error) {
|
||||
console.error("Error adding card to cart:", error);
|
||||
toast.error("Failed to move forward. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-2xl shadow-[0px_4px_24px_0px_rgba(0,0,0,0.06)] overflow-hidden w-full max-w-[400px]">
|
||||
<div className="pt-6 pb-2 text-center">
|
||||
<h4 className="font-poppins text-lg leading-snug font-medium text-[#2a2a2a]">{cityName}</h4>
|
||||
<div className="mt-2 flex justify-center">
|
||||
<span className={`inline-flex items-center px-4 py-1 rounded-full font-poppins text-xs font-medium ${item?.cardType?.name === 'selective_pass' ? 'bg-[#f95faf]/10 text-[#f95faf]' : 'bg-[#f95f62]/10 text-[#f95f62]'}`}>
|
||||
{item?.cardType?.displayName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 py-4 space-y-0">
|
||||
<div className="flex items-center justify-between py-4 border-b border-gray-100">
|
||||
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">No. of Adults</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={() => noOfAdults > 1 && setNoOfAdults((prev) => prev - 1)} disabled={noOfAdults <= 1} className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${noOfAdults <= 1 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'}`}>
|
||||
<Minus className="w-4 h-4" />
|
||||
</button>
|
||||
<span className="font-poppins text-base font-medium text-[#2a2a2a] w-5 text-center tabular-nums">{noOfAdults}</span>
|
||||
<button onClick={() => setNoOfAdults((prev) => prev + 1)} disabled={noOfAdults >= 15} className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${noOfAdults >= 15 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'}`}>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-4 border-b border-gray-100">
|
||||
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">No. of Children</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={() => noOfChildren > 0 && setNoOfChildren((prev) => prev - 1)} disabled={noOfChildren <= 0} className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${noOfChildren <= 0 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'}`}>
|
||||
<Minus className="w-4 h-4" />
|
||||
</button>
|
||||
<span className="font-poppins text-base font-medium text-[#2a2a2a] w-5 text-center tabular-nums">{noOfChildren}</span>
|
||||
<button onClick={() => setNoOfChildren((prev) => prev + 1)} disabled={noOfChildren >= 10} className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${noOfChildren >= 10 ? 'bg-gray-100 text-gray-300 cursor-not-allowed' : 'bg-[#f95f62]/10 text-[#f95f62] hover:bg-[#f95f62]/20'}`}>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-4 border-b border-gray-100">
|
||||
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">
|
||||
{item?.cardType?.name === 'selective_pass' ? 'No. of Attractions' : 'No. of Days'}
|
||||
</span>
|
||||
<div className="relative">
|
||||
<button onClick={() => setDropdownOpen(!dropdownOpen)} className="flex items-center gap-2 border border-[#f95f62]/30 rounded-lg px-3 py-1.5 min-w-[72px] justify-between hover:border-[#f95f62] transition-colors">
|
||||
<span className="font-poppins text-base font-medium text-[#f95f62] tabular-nums">{cardMode === "flexi" ? noOfAttractions : noOfDays}</span>
|
||||
<ChevronDown className={`w-4 h-4 text-[#f95f62] transition-transform ${dropdownOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{dropdownOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -4, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: -4, scale: 0.95 }}
|
||||
className="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-100 z-30 min-w-[72px]
|
||||
max-h-48 overflow-y-auto"
|
||||
>
|
||||
{numberArray.map((i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => {
|
||||
cardMode === "flexi" ? setNoOfAttractions(i) : setNoOfDays(i);
|
||||
setDropdownOpen(false);
|
||||
}}
|
||||
className={`w-full px-3 py-2 text-left font-poppins text-sm transition-colors ${(cardMode === "flexi" ? noOfAttractions === i : noOfDays === i)
|
||||
? "bg-[#f95f62]/10 text-[#f95f62] font-medium"
|
||||
: "text-[#2a2a2a] hover:bg-gray-50 font-normal"
|
||||
}`}
|
||||
|
||||
>
|
||||
{i}
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-5">
|
||||
<span className="font-poppins text-sm font-normal text-[#2a2a2a]">You Pay</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-poppins text-sm font-normal text-[#aaa] line-through">${strikedPrice}</span>
|
||||
<span className="font-poppins text-2xl font-medium text-[#f95f62] tracking-tight">${basePrice}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<motion.button whileHover={{ scale: 1.01 }} whileTap={{ scale: 0.98 }} onClick={handleProceedToPayment} className="w-full py-4 rounded-full bg-[#f95f62] text-white font-poppins text-base font-medium hover:bg-[#e8545a] transition-colors shadow-lg shadow-[#f95f62]/20 cursor-pointer">
|
||||
Proceed to Pay
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── MAIN CHECKOUT PAGE 2 ─── */
|
||||
export function CheckoutPage2({
|
||||
onHomeClick,
|
||||
onPassesClick,
|
||||
onAttractionsClick,
|
||||
onBlogsClick,
|
||||
onHowItWorksClick,
|
||||
onFAQClick,
|
||||
onPrivacyPolicyClick,
|
||||
onAboutUsClick,
|
||||
onContactUsClick,
|
||||
onSignInClick,
|
||||
onSignOutClick,
|
||||
onProfileClick,
|
||||
user,
|
||||
currentPage,
|
||||
}: any) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Default item (you can pass via props later)
|
||||
const baseUrl = import.meta.env.VITE_BASE_URL;
|
||||
|
||||
const cityId = localStorage.getItem("cityId")
|
||||
const { data: checkoutPageData, isLoading } = useGetCheckoutPageDataQuery(cityId)
|
||||
|
||||
const cityName = checkoutPageData?.city?.name ?? ""
|
||||
const cityImage = checkoutPageData?.city?.heroBanner?.image ?? ""
|
||||
const cards = checkoutPageData?.cards ?? []
|
||||
const flexiCard = checkoutPageData?.cards[0] ?? null
|
||||
const unlimitedCard = checkoutPageData?.cards[1] ?? null
|
||||
const attractions = checkoutPageData?.attractions ?? [];
|
||||
|
||||
const [checkoutItem, setCheckoutItem] = useState(flexiCard);
|
||||
|
||||
useEffect(() => {
|
||||
setCheckoutItem(flexiCard)
|
||||
}, [cards])
|
||||
|
||||
console.log(checkoutItem)
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner />
|
||||
} else {
|
||||
// console.log(flexiCard)
|
||||
}
|
||||
|
||||
const handleCheckoutItemChange = (cardObject: any) => {
|
||||
setCheckoutItem(cardObject);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#fafafa] font-poppins">
|
||||
<Navbar
|
||||
activeCity="Melbourne"
|
||||
onCityChange={() => { }}
|
||||
onSignInClick={onSignInClick}
|
||||
onSignOutClick={onSignOutClick}
|
||||
onPassesClick={onPassesClick}
|
||||
onCheckoutClick={() => { }}
|
||||
onHomeClick={onHomeClick}
|
||||
onAttractionsClick={onAttractionsClick}
|
||||
onBlogsClick={onBlogsClick}
|
||||
onHowItWorksClick={onHowItWorksClick}
|
||||
onFAQClick={onFAQClick}
|
||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
||||
onAboutUsClick={onAboutUsClick}
|
||||
onProfileClick={onProfileClick}
|
||||
onCityCardsClick={() => { }}
|
||||
onMagicItineraryClick={() => { }}
|
||||
onPostCardsClick={() => { }}
|
||||
onOffersClick={() => { }}
|
||||
onSuperSavingsClick={() => { }}
|
||||
onEsimsClick={() => { }}
|
||||
onHotelDiscountsClick={() => { }}
|
||||
onCartClick={() => { }}
|
||||
currentPage={currentPage}
|
||||
user={user}
|
||||
/>
|
||||
|
||||
<div className="w-full px-4 sm:px-6 lg:px-10 xl:px-16 pt-32 pb-24 max-w-[1440px] mx-auto">
|
||||
<button onClick={() => navigate(-1)} className="flex items-center gap-2 text-[#8e8e8e] hover:text-[#2a2a2a] transition-colors font-poppins text-sm font-normal mb-8">
|
||||
<ArrowLeft className="w-4 h-4" />Back
|
||||
</button>
|
||||
|
||||
<div className="mb-10">
|
||||
<h2 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
|
||||
<span className="font-light">Checkout</span>{' '}
|
||||
<span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent pr-2">{cityName}</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
{/* Left Column */}
|
||||
<div className="flex-1 space-y-8">
|
||||
{/* Card Type Selection */}
|
||||
<div>
|
||||
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">Choose Your Card</h3>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">Select the card type that best suits your travel style</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-[16px]">
|
||||
<button onClick={() => handleCheckoutItemChange(flexiCard)}>
|
||||
<FlexiCardPreview city={cityName} image={cityImage} adultPrice={flexiCard.adultPrice} childPrice={flexiCard.childPrice} isSelected={checkoutItem?.cardType.name === 'selective_pass'} />
|
||||
</button>
|
||||
<button onClick={() => handleCheckoutItemChange(unlimitedCard)}>
|
||||
<UnlimitedCardPreview city={cityName} image={cityImage} adultPrice={unlimitedCard.adultPrice} childPrice={unlimitedCard.childPrice} isSelected={checkoutItem?.cardType.name === 'unlimited_card'} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Features Comparison (Exact Copy) */}
|
||||
<div className="mt-6 bg-[#f5f5f5] rounded-xl p-4">
|
||||
<div className="grid grid-cols-[1fr_70px_70px] gap-y-0 items-center">
|
||||
<p className="font-poppins font-medium text-sm text-[#2a2a2a] py-3">Features</p>
|
||||
<p className="font-poppins font-medium text-sm text-[#2a2a2a] text-center py-3">Flexi</p>
|
||||
<p className="font-poppins font-medium text-sm text-[#2a2a2a] text-center py-3">Unlimited</p>
|
||||
{[
|
||||
{ feature: 'Access to attractions', flexi: true, unlimited: true },
|
||||
{ feature: 'Entry to attractions', flexi: true, unlimited: true },
|
||||
{ feature: 'Access to experiences', flexi: true, unlimited: true },
|
||||
{ feature: 'Entry to sites', flexi: false, unlimited: true },
|
||||
{ feature: 'Access to venues', flexi: true, unlimited: true },
|
||||
{ feature: 'Entry to events', flexi: true, unlimited: true },
|
||||
{ feature: 'Access to experiences', flexi: false, unlimited: true },
|
||||
{ feature: 'Access to Itinerary creation', flexi: false, unlimited: true },
|
||||
{ feature: 'Access to postcard creation', flexi: false, unlimited: true },
|
||||
].map((row, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<p className="font-poppins font-normal text-[13px] text-[#2a2a2a] py-2.5 border-t border-[rgba(0,0,0,0.08)] flex items-center gap-1.5">
|
||||
<span className="text-[#2a2a2a]">•</span> {row.feature}
|
||||
</p>
|
||||
<div className="flex justify-center py-2.5 border-t border-[rgba(0,0,0,0.08)]">
|
||||
{row.flexi ? <div className="w-5 h-5 rounded-full bg-[#F95F62] flex items-center justify-center"><Check className="w-3 h-3 text-white" strokeWidth={3} /></div> : <span className="font-poppins text-[13px] text-[rgba(0,0,0,0.3)]">–</span>}
|
||||
</div>
|
||||
<div className="flex justify-center py-2.5 border-t border-[rgba(0,0,0,0.08)]">
|
||||
{row.unlimited ? <div className="w-5 h-5 rounded-full bg-[#F95F62] flex items-center justify-center"><Check className="w-3 h-3 text-white" strokeWidth={3} /></div> : <span className="font-poppins text-[13px] text-[rgba(0,0,0,0.3)]">–</span>}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Offers Section (Exact) */}
|
||||
<div>
|
||||
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">{checkoutItem?.cardType?.displayName} Offers</h3>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">Exclusive deals and discounts included with your {checkoutItem?.cardType.displayName} pass</p>
|
||||
<div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 snap-x snap-mandatory scrollbar-hide">
|
||||
{checkoutItem?.offers.map((offer: any) => (
|
||||
<div key={offer.id} className="relative bg-white rounded-xl shrink-0 w-[180px] h-[260px] snap-start">
|
||||
<div className="flex flex-col gap-2 items-start overflow-hidden p-3 rounded-xl h-full">
|
||||
<div className="h-[120px] w-full rounded-lg overflow-hidden shrink-0 relative">
|
||||
<ImageWithFallback src={`${baseUrl}/${offer.websiteBannerImage}`} alt={offer.title} className="absolute inset-0 w-full h-full object-cover rounded-lg" />
|
||||
</div>
|
||||
<div className="w-full h-[44px] overflow-hidden">
|
||||
<p className="font-['Poppins',sans-serif] font-normal text-[18px] text-black tracking-[-0.72px] leading-[22px] line-clamp-2">{offer.title}</p>
|
||||
</div>
|
||||
<div className="w-full flex-1">
|
||||
<p className="font-['Poppins',sans-serif] font-normal text-[12px] text-[rgba(0,0,0,0.6)] leading-[16px] line-clamp-3">{offer.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 border border-[rgba(249,95,98,0.24)] rounded-xl pointer-events-none" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Attractions Section (Exact) */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">Available Attractions</h3>
|
||||
<span className="font-poppins text-xs font-medium text-[#F95F62] bg-[#F95F62]/10 px-3 py-1 rounded-full">{attractions.length} included</span>
|
||||
</div>
|
||||
<p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">Explore all the experiences you can enjoy with your pass</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
{attractions.map((a: any) => (
|
||||
<div key={a.id} className="group relative rounded-xl overflow-hidden">
|
||||
<div className="aspect-[4/3] relative">
|
||||
<ImageWithFallback src={a.thumbnail} alt={a.title} className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/10 to-transparent" />
|
||||
<div className="absolute top-2 right-2">
|
||||
<span className="inline-flex px-2 py-0.5 rounded-full bg-white/90 backdrop-blur-sm text-[10px] font-poppins font-medium text-[#555]">{a.category}</span>
|
||||
</div>
|
||||
<div className="absolute bottom-2 left-2 right-2">
|
||||
<h6 className="font-poppins text-sm leading-snug font-medium text-white drop-shadow-sm">{a.title}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Config Card */}
|
||||
<div className="hidden lg:block lg:w-[420px] flex-shrink-0">
|
||||
<div className="lg:sticky lg:top-28">
|
||||
<CheckoutConfigCard
|
||||
item={checkoutItem}
|
||||
onChange={handleCheckoutItemChange}
|
||||
onProceed={() => navigate("/payment")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Config Card */}
|
||||
{/* <div className="lg:hidden mt-6">
|
||||
<CheckoutConfigCard
|
||||
item={checkoutItem}
|
||||
onChange={handleCheckoutItemChange}
|
||||
onProceed={() => navigate("/payment")}
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer
|
||||
onHomeClick={onHomeClick}
|
||||
onPassesClick={onPassesClick}
|
||||
onAttractionsClick={onAttractionsClick}
|
||||
onBlogsClick={onBlogsClick}
|
||||
onHowItWorksClick={onHowItWorksClick}
|
||||
onFAQClick={onFAQClick}
|
||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
||||
onAboutUsClick={onAboutUsClick}
|
||||
onContactUsClick={onContactUsClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// export function CheckoutPage2({
|
||||
// onHomeClick,
|
||||
// onPassesClick,
|
||||
// onAttractionsClick,
|
||||
// onBlogsClick,
|
||||
// onHowItWorksClick,
|
||||
// onFAQClick,
|
||||
// onPrivacyPolicyClick,
|
||||
// onAboutUsClick,
|
||||
// onContactUsClick,
|
||||
// onSignInClick,
|
||||
// onSignOutClick,
|
||||
// onProfileClick,
|
||||
// user,
|
||||
// currentPage,
|
||||
// }: any) {
|
||||
// const navigate = useNavigate();
|
||||
|
||||
// const cityId = localStorage.getItem("cityId");
|
||||
// const { data: checkoutPageData, isLoading } = useGetCheckoutPageDataQuery(cityId);
|
||||
|
||||
// const city = checkoutPageData?.city;
|
||||
// const allCards = checkoutPageData?.cards ?? [];
|
||||
// const allAttractions = checkoutPageData?.attractions ?? [];
|
||||
// const allOffers = checkoutPageData?.offers ?? [];
|
||||
|
||||
// const baseUrl = import.meta.env.VITE_BASE_URL;
|
||||
|
||||
|
||||
// // Initialize with first card (Flexi) as default
|
||||
// const defaultCard = allCards[0] || null;
|
||||
|
||||
// const [checkoutItem, setCheckoutItem] = useState<CartItem>({
|
||||
// id: defaultCard?.id?.toString() || '1',
|
||||
// city: city?.name || 'Melbourne',
|
||||
// cardType: defaultCard?.cardType?.displayName || 'Flexi',
|
||||
// days: defaultCard?.validityDuration || 3,
|
||||
// adults: 2,
|
||||
// children: 1,
|
||||
// quantity: 1,
|
||||
// pricePerUnit: defaultCard?.adultPrice || 49.5,
|
||||
// image: city?.heroBanner?.image || '',
|
||||
// });
|
||||
|
||||
// if (isLoading) {
|
||||
// return <LoadingSpinner />;
|
||||
// }
|
||||
|
||||
// const handleCheckoutItemChange = (updates: Partial<CartItem>) => {
|
||||
// const updated = { ...checkoutItem, ...updates };
|
||||
|
||||
// // If card type changes, update with real card data
|
||||
// if (updates.cardType) {
|
||||
// const selectedCard = allCards.find(
|
||||
// c => c.cardType?.displayName === updates.cardType
|
||||
// );
|
||||
// if (selectedCard) {
|
||||
// updated.id = selectedCard.id.toString();
|
||||
// updated.days = selectedCard.validityDuration;
|
||||
// updated.pricePerUnit = selectedCard.adultPrice;
|
||||
// }
|
||||
// }
|
||||
|
||||
// setCheckoutItem(updated);
|
||||
// };
|
||||
|
||||
// // Get currently selected card
|
||||
// const selectedCard = allCards.find(c =>
|
||||
// c.cardType?.displayName === checkoutItem.cardType
|
||||
// ) || allCards[0];
|
||||
|
||||
// // Offers for selected card (fallback to global offers)
|
||||
// const currentOffers = selectedCard?.offers?.length
|
||||
// ? selectedCard.offers
|
||||
// : allOffers;
|
||||
|
||||
// return (
|
||||
// <div className="min-h-screen bg-[#fafafa] font-poppins">
|
||||
// <Navbar
|
||||
// activeCity={city?.name || "Melbourne"}
|
||||
// onCityChange={() => { }}
|
||||
// onSignInClick={onSignInClick}
|
||||
// onSignOutClick={onSignOutClick}
|
||||
// onPassesClick={onPassesClick}
|
||||
// onCheckoutClick={() => { }}
|
||||
// onHomeClick={onHomeClick}
|
||||
// onAttractionsClick={onAttractionsClick}
|
||||
// onBlogsClick={onBlogsClick}
|
||||
// onHowItWorksClick={onHowItWorksClick}
|
||||
// onFAQClick={onFAQClick}
|
||||
// onPrivacyPolicyClick={onPrivacyPolicyClick}
|
||||
// onAboutUsClick={onAboutUsClick}
|
||||
// onProfileClick={onProfileClick}
|
||||
// onCityCardsClick={() => { }}
|
||||
// onMagicItineraryClick={() => { }}
|
||||
// onPostCardsClick={() => { }}
|
||||
// onOffersClick={() => { }}
|
||||
// onSuperSavingsClick={() => { }}
|
||||
// onEsimsClick={() => { }}
|
||||
// onHotelDiscountsClick={() => { }}
|
||||
// onCartClick={() => { }}
|
||||
// currentPage={currentPage}
|
||||
// user={user}
|
||||
// />
|
||||
|
||||
// <div className="w-full px-4 sm:px-6 lg:px-10 xl:px-16 pt-32 pb-24 max-w-[1440px] mx-auto">
|
||||
// <button
|
||||
// onClick={() => navigate(-1)}
|
||||
// className="flex items-center gap-2 text-[#8e8e8e] hover:text-[#2a2a2a] transition-colors font-poppins text-sm font-normal mb-8"
|
||||
// >
|
||||
// <ArrowLeft className="w-4 h-4" />Back
|
||||
// </button>
|
||||
|
||||
// <div className="mb-10">
|
||||
// <h2 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight">
|
||||
// <span className="font-light">Checkout</span>{' '}
|
||||
// <span className="font-bold italic bg-gradient-to-r from-[#F95F62] to-[#F95FAF] bg-clip-text text-transparent pr-3">
|
||||
// {city?.name || checkoutItem.city}
|
||||
// </span>
|
||||
// </h2>
|
||||
// </div>
|
||||
|
||||
// <div className="flex flex-col lg:flex-row gap-10">
|
||||
// {/* Left Column */}
|
||||
// <div className="flex-1 space-y-8">
|
||||
// {/* Card Type Selection */}
|
||||
// <div>
|
||||
// <h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">Choose Your Card</h3>
|
||||
// <p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
|
||||
// Select the card type that best suits your travel style
|
||||
// </p>
|
||||
// <div className="grid grid-cols-1 sm:grid-cols-2 gap-[16px]">
|
||||
// {allCards.map((card) => (
|
||||
// <button
|
||||
// key={card.id}
|
||||
// onClick={() => handleCheckoutItemChange({
|
||||
// cardType: card.cardType?.displayName || 'Flexi'
|
||||
// })}
|
||||
// >
|
||||
// {card.cardType?.name === 'selective_pass' ? (
|
||||
// <FlexiCardPreview
|
||||
// city={city?.name || checkoutItem.city}
|
||||
// adultPrice={card.adultPrice}
|
||||
// childPrice={card.childPrice}
|
||||
// isSelected={checkoutItem.cardType === card.cardType?.displayName}
|
||||
// />
|
||||
// ) : (
|
||||
// <UnlimitedCardPreview
|
||||
// city={city?.name || checkoutItem.city}
|
||||
// adultPrice={card.adultPrice}
|
||||
// childPrice={card.childPrice}
|
||||
// isSelected={checkoutItem.cardType === card.cardType?.displayName}
|
||||
// />
|
||||
// )}
|
||||
// </button>
|
||||
// ))}
|
||||
// </div>
|
||||
|
||||
// {/* Features Comparison - Kept as is (no CSS change) */}
|
||||
// <div className="mt-6 bg-[#f5f5f5] rounded-xl p-4">
|
||||
// <div className="grid grid-cols-[1fr_70px_70px] gap-y-0 items-center">
|
||||
// {/* Header */}
|
||||
// <p className="font-poppins font-medium text-sm text-[#2a2a2a] py-3">Features</p>
|
||||
// <p className="font-poppins font-medium text-sm text-[#2a2a2a] text-center py-3">Flexi</p>
|
||||
// <p className="font-poppins font-medium text-sm text-[#2a2a2a] text-center py-3">Unlimited</p>
|
||||
// {[
|
||||
// { feature: 'Access to attractions', flexi: true, unlimited: true },
|
||||
// { feature: 'Entry to attractions', flexi: true, unlimited: true },
|
||||
// { feature: 'Access to experiences', flexi: true, unlimited: true },
|
||||
// { feature: 'Entry to sites', flexi: false, unlimited: true },
|
||||
// { feature: 'Access to venues', flexi: true, unlimited: true },
|
||||
// { feature: 'Entry to events', flexi: true, unlimited: true },
|
||||
// { feature: 'Access to experiences', flexi: false, unlimited: true },
|
||||
// { feature: 'Access to Itinerary creation', flexi: false, unlimited: true },
|
||||
// { feature: 'Access to postcard creation', flexi: false, unlimited: true },
|
||||
// ].map((row, i) => (
|
||||
// <React.Fragment key={i}>
|
||||
// <p className="font-poppins font-normal text-[13px] text-[#2a2a2a] py-2.5 border-t border-[rgba(0,0,0,0.08)] flex items-center gap-1.5">
|
||||
// <span className="text-[#2a2a2a]">•</span> {row.feature}
|
||||
// </p>
|
||||
// <div className="flex justify-center py-2.5 border-t border-[rgba(0,0,0,0.08)]">
|
||||
// {row.flexi ? (
|
||||
// <div className="w-5 h-5 rounded-full bg-[#F95F62] flex items-center justify-center">
|
||||
// <Check className="w-3 h-3 text-white" strokeWidth={3} />
|
||||
// </div>
|
||||
// ) : (
|
||||
// <span className="font-poppins text-[13px] text-[rgba(0,0,0,0.3)]">–</span>
|
||||
// )}
|
||||
// </div>
|
||||
// <div className="flex justify-center py-2.5 border-t border-[rgba(0,0,0,0.08)]">
|
||||
// {row.unlimited ? (
|
||||
// <div className="w-5 h-5 rounded-full bg-[#F95F62] flex items-center justify-center">
|
||||
// <Check className="w-3 h-3 text-white" strokeWidth={3} />
|
||||
// </div>
|
||||
// ) : (
|
||||
// <span className="font-poppins text-[13px] text-[rgba(0,0,0,0.3)]">–</span>
|
||||
// )}
|
||||
// </div>
|
||||
// </React.Fragment>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Offers Section */}
|
||||
// <div>
|
||||
// <h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">
|
||||
// {checkoutItem.cardType} Card Offers
|
||||
// </h3>
|
||||
// <p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
|
||||
// Exclusive deals and discounts included with your {checkoutItem.cardType} pass
|
||||
// </p>
|
||||
// <div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 snap-x snap-mandatory scrollbar-hide">
|
||||
// {currentOffers.map((offer, idx) => (
|
||||
// <div key={idx} className="relative bg-white rounded-xl shrink-0 w-[180px] h-[260px] snap-start">
|
||||
// <div className="flex flex-col gap-2 items-start overflow-hidden p-3 rounded-xl h-full">
|
||||
// <div className="h-[120px] w-full rounded-lg overflow-hidden shrink-0 relative">
|
||||
// <ImageWithFallback
|
||||
// src={`${baseUrl}/${offer.websiteBannerImage}` || offer.mobileBannerImage}
|
||||
// alt={offer.title}
|
||||
// className="absolute inset-0 w-full h-full object-cover rounded-lg"
|
||||
// />
|
||||
// </div>
|
||||
// <div className="w-full h-[44px] overflow-hidden">
|
||||
// <p className="font-['Poppins',sans-serif] font-normal text-[18px] text-black tracking-[-0.72px] leading-[22px] line-clamp-2">
|
||||
// {offer.title}
|
||||
// </p>
|
||||
// </div>
|
||||
// <div className="w-full flex-1">
|
||||
// <p className="font-['Poppins',sans-serif] font-normal text-[12px] text-[rgba(0,0,0,0.6)] leading-[16px] line-clamp-3">
|
||||
// {offer.description}
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className="absolute inset-0 border border-[rgba(249,95,98,0.24)] rounded-xl pointer-events-none" />
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Attractions Section */}
|
||||
// <div>
|
||||
// <div className="flex items-center justify-between">
|
||||
// <h3 className="font-poppins text-xl md:text-2xl leading-snug font-medium text-[#2a2a2a]">Available Attractions</h3>
|
||||
// <span className="font-poppins text-xs font-medium text-[#F95F62] bg-[#F95F62]/10 px-3 py-1 rounded-full">
|
||||
// {allAttractions.length} included
|
||||
// </span>
|
||||
// </div>
|
||||
// <p className="font-poppins text-sm leading-relaxed font-normal text-[#8e8e8e] mt-1 mb-4">
|
||||
// Explore all the experiences you can enjoy with your pass
|
||||
// </p>
|
||||
// <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
// {allAttractions.map((attraction) => (
|
||||
// <div key={attraction.id} className="group relative rounded-xl overflow-hidden">
|
||||
// <div className="aspect-[4/3] relative">
|
||||
// <ImageWithFallback
|
||||
// src={attraction.thumbnail}
|
||||
// alt={attraction.title}
|
||||
// className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
// />
|
||||
// <div className="absolute inset-0 bg-gradient-to-t from-black/60 via-black/10 to-transparent" />
|
||||
// <div className="absolute bottom-2 left-2 right-2">
|
||||
// <h6 className="font-poppins text-sm leading-snug font-medium text-white drop-shadow-sm">
|
||||
// {attraction.title}
|
||||
// </h6>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Right Column - Config Card */}
|
||||
// <div className="hidden lg:block lg:w-[420px] flex-shrink-0">
|
||||
// <div className="lg:sticky lg:top-28">
|
||||
// <CheckoutConfigCard
|
||||
// item={checkoutItem}
|
||||
// onChange={handleCheckoutItemChange}
|
||||
// onProceed={() => navigate("/payment")}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Mobile Config Card */}
|
||||
// <div className="lg:hidden mt-6">
|
||||
// <CheckoutConfigCard
|
||||
// item={checkoutItem}
|
||||
// onChange={handleCheckoutItemChange}
|
||||
// onProceed={() => navigate("/payment")}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <Footer
|
||||
// onHomeClick={onHomeClick}
|
||||
// onPassesClick={onPassesClick}
|
||||
// onAttractionsClick={onAttractionsClick}
|
||||
// onBlogsClick={onBlogsClick}
|
||||
// onHowItWorksClick={onHowItWorksClick}
|
||||
// onFAQClick={onFAQClick}
|
||||
// onPrivacyPolicyClick={onPrivacyPolicyClick}
|
||||
// onAboutUsClick={onAboutUsClick}
|
||||
// onContactUsClick={onContactUsClick}
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
1087
src/pages/CreateMagicIternaryPage.tsx
Normal file
340
src/pages/ItinerarySummaryPage.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
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';
|
||||
import Navbar from '../components/Navbar';
|
||||
import { Footer } from '../components/Footer';
|
||||
|
||||
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 (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Navbar */}
|
||||
<Navbar
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-8 max-w-3xl mx-auto mt-25"
|
||||
>
|
||||
{/* 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')}
|
||||
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>
|
||||
<Footer
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ItinerarySummaryPage
|
||||
@@ -1,12 +1,16 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowLeft, Calendar, Clock, MapPin, Users, Star, Heart, Share2, Download, CheckCircle, Navigation, Cloud, Sun } from 'lucide-react';
|
||||
import { ArrowLeft, Calendar, MapPin, Users, Star, Heart, Share2, Download, CheckCircle, Navigation, Cloud, Sun, Loader2 } from 'lucide-react';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Card, CardContent } from '../components/ui/card';
|
||||
import { Badge } from '../components/ui/badge';
|
||||
import Navbar from '../components/Navbar';
|
||||
import { Footer } from '../components/Footer';
|
||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
||||
import { useGetItineraryDetailsByIdQuery, useDownloadItineraryQuery } from '../Redux/services/itinerary.service';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import LoadingSpinner from '../components/LoadingSpinner';
|
||||
import { toast } from 'sonner'; // optional, install if not present
|
||||
|
||||
interface ItineraryViewPageProps {
|
||||
onBackClick: () => void;
|
||||
@@ -35,20 +39,7 @@ interface ItineraryViewPageProps {
|
||||
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 ItineraryViewPage({
|
||||
onBackClick,
|
||||
onHomeClick,
|
||||
onMelbourneClick,
|
||||
onPassesClick,
|
||||
@@ -68,310 +59,55 @@ export function ItineraryViewPage({
|
||||
onOffersClick,
|
||||
onCreateItineraryClick,
|
||||
onContactUsClick,
|
||||
onEsimsClick,
|
||||
onHotelDiscountsClick,
|
||||
currentPage,
|
||||
user
|
||||
}: ItineraryViewPageProps) {
|
||||
const [viewMode, setViewMode] = useState<'daily' | 'summary'>('daily');
|
||||
const [favorites, setFavorites] = useState<Set<string>>(new Set());
|
||||
const navigate = useNavigate();
|
||||
|
||||
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'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
// Download logic using standard query with manual trigger
|
||||
const [shouldDownload, setShouldDownload] = useState(false);
|
||||
const { data: pdfBlob, isFetching: isDownloading, refetch } = useDownloadItineraryQuery(itineraryId!, {
|
||||
skip: !shouldDownload || !itineraryId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldDownload && pdfBlob) {
|
||||
// Create download link
|
||||
const url = window.URL.createObjectURL(pdfBlob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `itinerary-${itineraryId}.pdf`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
toast.success('Itinerary downloaded successfully!');
|
||||
setShouldDownload(false); // reset trigger
|
||||
}
|
||||
}, [pdfBlob, shouldDownload, itineraryId]);
|
||||
|
||||
const handleDownloadItinerary = useCallback(() => {
|
||||
if (!itineraryId) {
|
||||
toast.error('Itinerary ID not found');
|
||||
return;
|
||||
}
|
||||
setShouldDownload(true);
|
||||
refetch(); // manually trigger the download query
|
||||
}, [itineraryId, refetch]);
|
||||
|
||||
const generatedItinerary = itineraryDetails ?? null;
|
||||
const days = generatedItinerary?.days ?? [];
|
||||
const summaries = generatedItinerary?.summary ?? [];
|
||||
|
||||
// ── Loading State ─────────────────────────────────────────────────────────────
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
@@ -401,7 +137,7 @@ export function ItineraryViewPage({
|
||||
/>
|
||||
|
||||
{/* Header Section */}
|
||||
<section className="pt-32 pb-8 bg-gradient-to-br from-primary/5 to-secondary/5">
|
||||
<section className="pb-8 bg-gradient-to-br from-primary/5 to-secondary/5">
|
||||
<div className="container mx-auto px-4 pt-32">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
@@ -411,8 +147,8 @@ export function ItineraryViewPage({
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onBackClick}
|
||||
className="mb-6 hover:bg-primary/5 font-poppins font-medium"
|
||||
onClick={() => navigate(-1)}
|
||||
className="mb-6 hover:bg-primary/5 font-poppins font-medium cursor-pointer"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Back to Magic Itinerary
|
||||
@@ -422,12 +158,14 @@ export function ItineraryViewPage({
|
||||
<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 +175,7 @@ export function ItineraryViewPage({
|
||||
<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 +198,45 @@ export function ItineraryViewPage({
|
||||
</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>
|
||||
<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,29 +244,24 @@ export function ItineraryViewPage({
|
||||
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 location changes */}
|
||||
{(dayIndex === 0 ||
|
||||
(dayIndex > 0 &&
|
||||
generatedItinerary.dailyPlans[dayIndex - 1] &&
|
||||
(generatedItinerary.dailyPlans[dayIndex - 1] as any).destination?.name !== generatedItinerary.destination.name)) && (
|
||||
|
||||
{/* 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-merchant text-3xl md:text-4xl leading-tight mb-2">
|
||||
{generatedItinerary.destination.name}, {generatedItinerary.destination.country}
|
||||
<h2 className="font-poppins text-3xl md:text-4xl leading-tight mb-2">
|
||||
{generatedItinerary?.city}, Australia
|
||||
</h2>
|
||||
<p className="font-poppins text-base text-primary font-medium">{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>
|
||||
)}
|
||||
@@ -526,122 +274,118 @@ export function ItineraryViewPage({
|
||||
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);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={actIndex}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.6 + dayIndex * 0.1 + actIndex * 0.05 }}
|
||||
className="flex gap-6"
|
||||
>
|
||||
{/* 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>
|
||||
{day.items?.map((activity: any, actIndex: number) => (
|
||||
<motion.div
|
||||
key={actIndex}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.6 + dayIndex * 0.1 + actIndex * 0.05 }}
|
||||
className="flex gap-6"
|
||||
>
|
||||
{/* Time Column */}
|
||||
<div className="flex-shrink-0 w-24 pt-2">
|
||||
<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 */}
|
||||
<div className="relative h-64 md:h-72 bg-gray-200">
|
||||
<ImageWithFallback
|
||||
src={activity.image}
|
||||
alt={activity.activity}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
{/* Favorite Heart Button - Top Right */}
|
||||
<div className="absolute top-4 right-4">
|
||||
<Button
|
||||
size="icon"
|
||||
{/* 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 */}
|
||||
<div className="relative h-64 md:h-72 bg-gray-200">
|
||||
<ImageWithFallback
|
||||
src={activity.imageUrl}
|
||||
alt={activity.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
{/* Get Directions */}
|
||||
<div className="absolute bottom-4 left-4">
|
||||
<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
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-merchant text-xl md:text-2xl leading-snug font-semibold text-gray-900">
|
||||
{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.locationName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Badges */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activity.categories?.map((cat: string, ci: number) => (
|
||||
<Badge
|
||||
key={ci}
|
||||
variant="secondary"
|
||||
className="bg-white/95 hover:bg-white shadow-lg backdrop-blur-sm rounded-full w-12 h-12"
|
||||
onClick={() => toggleFavorite(activityKey)}
|
||||
className="font-poppins font-normal text-sm bg-primary/10 text-primary hover:bg-primary/20 px-3 py-1"
|
||||
>
|
||||
<Heart className={`w-5 h-5 ${isFavorite ? 'fill-primary text-primary' : 'text-gray-700'}`} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Get Directions Button - Bottom Left */}
|
||||
<div className="absolute bottom-4 left-4">
|
||||
<Button
|
||||
className="bg-primary hover:bg-primary/90 text-white font-poppins font-semibold shadow-lg px-6 py-3 rounded-xl"
|
||||
>
|
||||
<Navigation className="w-4 h-4 mr-2" />
|
||||
Get Directions
|
||||
</Button>
|
||||
</div>
|
||||
{cat}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Content Section */}
|
||||
<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}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Badges */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activity.categories.map((category, catIndex) => (
|
||||
<Badge
|
||||
key={catIndex}
|
||||
variant="secondary"
|
||||
className="font-poppins font-normal text-sm bg-primary/10 text-primary hover:bg-primary/20 px-3 py-1"
|
||||
>
|
||||
{category}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Activity Details - Bullet Points */}
|
||||
<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>
|
||||
))}
|
||||
{/* Description */}
|
||||
<div className="space-y-2 pt-2">
|
||||
<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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Summary View */}
|
||||
{/* ── Summary View ─────────────────────────────────────────────────── */}
|
||||
{viewMode === 'summary' && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@@ -649,32 +393,55 @@ export function ItineraryViewPage({
|
||||
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) => {
|
||||
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>
|
||||
@@ -689,22 +456,27 @@ export function ItineraryViewPage({
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onCreateItineraryClick}
|
||||
onClick={() => navigate(`/create-itinerary`)}
|
||||
className="font-poppins font-medium px-8 py-3 text-lg"
|
||||
>
|
||||
<Heart className="w-5 h-5 mr-2" />
|
||||
Create Another
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleDownloadItinerary}
|
||||
disabled={isDownloading}
|
||||
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" />
|
||||
{isDownloading ? (
|
||||
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
|
||||
) : (
|
||||
<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>
|
||||
@@ -714,7 +486,7 @@ export function ItineraryViewPage({
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<Footer
|
||||
<Footer
|
||||
onHomeClick={onHomeClick}
|
||||
onMelbourneClick={onMelbourneClick}
|
||||
onPassesClick={onPassesClick}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { HeroBannerCarousel } from '../components/HeroBannerCarousel';
|
||||
import { HotelEsimOffers } from '../components/HotelEsimOffers';
|
||||
import { useGetSelectedCityDetailsQuery } from '../Redux/services/cities.service';
|
||||
import LoadingSpinner from '../components/LoadingSpinner';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
@@ -147,8 +148,10 @@ export function MelbournePage({
|
||||
// Magic Itinerary state
|
||||
const [currentCardIndex, setCurrentCardIndex] = useState(0);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const navigate= useNavigate();
|
||||
|
||||
const cityId = localStorage.getItem("cityId")
|
||||
const cityName = localStorage.getItem("cityName")
|
||||
|
||||
const { data: cityDetails, isLoading: loadingCityDetails } = useGetSelectedCityDetailsQuery(cityId)
|
||||
|
||||
@@ -170,7 +173,7 @@ export function MelbournePage({
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Navigation */}
|
||||
<Layout
|
||||
activeCity="Melbourne"
|
||||
// activeCity="Melbourne"
|
||||
onSignInClick={onSignInClick}
|
||||
onSignOutClick={onSignOutClick}
|
||||
user={user}
|
||||
@@ -231,7 +234,7 @@ export function MelbournePage({
|
||||
{[
|
||||
{
|
||||
title: "50+ Top Attractions",
|
||||
description: "Unlimited access to Melbourne's finest museums, zoos, and observation decks.",
|
||||
description: `Unlimited access to ${cityName}'s finest museums, zoos, and observation decks.`,
|
||||
icon: MapPin,
|
||||
color: "text-blue-500",
|
||||
bg: "bg-blue-50"
|
||||
@@ -779,7 +782,7 @@ export function MelbournePage({
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<Button
|
||||
onClick={onCreateItineraryClick}
|
||||
onClick={() => {navigate('/create-itinerary');}}
|
||||
className="font-poppins py-7 px-16 rounded-full text-xl font-bold bg-gradient-to-r from-primary via-orange-500 to-rose-500 hover:from-primary/90 hover:via-orange-500/90 hover:to-rose-500/90 shadow-2xl hover:shadow-primary/50 transition-all hover:scale-105 hover:-translate-y-1"
|
||||
>
|
||||
<span className="flex items-center gap-3">
|
||||
|
||||
@@ -227,10 +227,10 @@ export function PassesPage({
|
||||
|
||||
<CardHeader className="text-center pb-4 pt-8 flex-shrink-0">
|
||||
<CardTitle className="font-merchant text-2xl leading-tight mb-3 text-gray-900">
|
||||
{cards[0].title}
|
||||
{cards[0]?.title}
|
||||
</CardTitle>
|
||||
<CardDescription className="font-poppins text-sm text-gray-600 leading-relaxed font-normal min-h-[48px] flex items-center justify-center px-4">
|
||||
{cards[0].description}
|
||||
{cards[0]?.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
@@ -238,23 +238,23 @@ export function PassesPage({
|
||||
<div className="px-6 pb-6 flex-shrink-0">
|
||||
<div className="flex items-baseline justify-center gap-2 mb-2">
|
||||
<span className="text-5xl font-bold text-gray-900 font-poppins">
|
||||
${cards[0].adultPrice}
|
||||
${cards[0]?.adultPrice}
|
||||
</span>
|
||||
<span className="text-gray-500 font-poppins text-base">
|
||||
/ {passTypes[0].period}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-5 flex items-center justify-center">
|
||||
{cards[0].adultPrice && (
|
||||
{cards[0]?.adultPrice && (
|
||||
<div className="text-sm text-gray-500 font-poppins">
|
||||
{/* Strikethrough price = originalPrice + $5 */}
|
||||
<span className="line-through mr-2">
|
||||
${parseFloat(cards[0].adultPrice) + 5}
|
||||
${parseFloat(cards[0]?.adultPrice) + 5}
|
||||
</span>
|
||||
<span className="text-green-600 font-medium">
|
||||
Save{" "}
|
||||
{Math.round(
|
||||
((5) / (parseFloat(cards[0].adultPrice) + 5)) * 100
|
||||
((5) / (parseFloat(cards[0]?.adultPrice) + 5)) * 100
|
||||
)}
|
||||
%
|
||||
</span>
|
||||
@@ -299,14 +299,14 @@ export function PassesPage({
|
||||
{/* Unlimited Pass Card */}
|
||||
<div className="relative h-full">
|
||||
<Card
|
||||
className={`relative h-full flex flex-col transition-all duration-300 cursor-pointer ${selectedPass === passTypes[1].id
|
||||
className={`relative h-full flex flex-col transition-all duration-300 cursor-pointer ${selectedPass === passTypes[1]?.id
|
||||
? "ring-2 ring-red-500 shadow-lg" // 🔴 red border when selected
|
||||
: "border-gray-200 shadow-md hover:shadow-lg hover:border-primary/30"
|
||||
}`}
|
||||
onClick={() => setSelectedPass(passTypes[1].id)}
|
||||
onClick={() => setSelectedPass(passTypes[1]?.id)}
|
||||
|
||||
>
|
||||
{passTypes[1].popular && (
|
||||
{passTypes[1]?.popular && (
|
||||
<div className="absolute -top-3 left-1/2 transform -translate-x-1/2 z-10">
|
||||
<Badge className="bg-gradient-to-r from-yellow-400 to-orange-500 text-black px-6 py-1.5 font-semibold shadow-lg font-poppins">
|
||||
Most Popular
|
||||
@@ -320,30 +320,30 @@ export function PassesPage({
|
||||
|
||||
<CardHeader className="text-center pb-4 pt-8 flex-shrink-0">
|
||||
<CardTitle className="font-merchant text-2xl leading-tight mb-3 text-gray-900">
|
||||
{cards[1].title}
|
||||
{cards[1]?.title}
|
||||
</CardTitle>
|
||||
<CardDescription className="font-poppins text-sm text-gray-600 leading-relaxed font-normal min-h-[48px] flex items-center justify-center px-4">
|
||||
{cards[1].description}
|
||||
{cards[1]?.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
{/* Pricing */}
|
||||
<div className="px-6 pb-6 flex-shrink-0">
|
||||
<div className="flex items-baseline justify-center gap-2 mb-2">
|
||||
<span className="text-5xl font-bold text-gray-900 font-poppins">${cards[1].adultPrice}</span>
|
||||
<span className="text-gray-500 font-poppins text-base">/ {passTypes[1].period}</span>
|
||||
<span className="text-5xl font-bold text-gray-900 font-poppins">${cards[1]?.adultPrice}</span>
|
||||
<span className="text-gray-500 font-poppins text-base">/ {passTypes[1]?.period}</span>
|
||||
</div>
|
||||
<div className="h-5 flex items-center justify-center">
|
||||
{cards[1].adultPrice && (
|
||||
{cards[1]?.adultPrice && (
|
||||
<div className="text-sm text-gray-500 font-poppins">
|
||||
{/* Strikethrough price = originalPrice + $5 */}
|
||||
<span className="line-through mr-2">
|
||||
${parseFloat(cards[1].adultPrice) + 5}
|
||||
${parseFloat(cards[1]?.adultPrice) + 5}
|
||||
</span>
|
||||
<span className="text-green-600 font-medium">
|
||||
Save{" "}
|
||||
{Math.round(
|
||||
((5) / (parseFloat(cards[1].adultPrice) + 5)) * 100
|
||||
((5) / (parseFloat(cards[1]?.adultPrice) + 5)) * 100
|
||||
)}
|
||||
%
|
||||
</span>
|
||||
@@ -355,7 +355,7 @@ export function PassesPage({
|
||||
<CardContent className="pt-0 pb-6 px-6 flex-grow flex flex-col">
|
||||
<div className="flex-grow mb-6">
|
||||
<div className="space-y-3">
|
||||
{passTypes[1].features.map((feature, index) => (
|
||||
{passTypes[1]?.features.map((feature, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<Check className="w-4 h-4 text-green-500 mt-1 flex-shrink-0" />
|
||||
<span className="text-sm text-gray-700 font-poppins leading-relaxed font-normal">{feature}</span>
|
||||
|
||||
@@ -123,7 +123,7 @@ function Field({
|
||||
? 'border-[#F95F62] ring-2 ring-[#F95F62]/10'
|
||||
: prefilled
|
||||
? 'border-[#F95F62]/25 bg-[#F95F62]/[0.02]'
|
||||
: 'border-gray-200'
|
||||
: 'border-[#E4AFB1] bg-[#FFF5F5]'
|
||||
}`}
|
||||
/>
|
||||
{prefilled && !focused && !disabled && (
|
||||
@@ -448,7 +448,7 @@ export function PaymentDetailsPage({
|
||||
transition={{ duration: 0.25 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="bg-[#F95F62]/[0.03] border border-[#F95F62]/15 rounded-xl px-5 py-4 space-y-4">
|
||||
<div className="border border-[#F95F62]/15 rounded-xl px-5 py-4 space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Gift className="w-4 h-4 text-[#F95F62]" />
|
||||
<h3 className="font-poppins text-base font-semibold text-[#2a2a2a]">Gift Recipient Details</h3>
|
||||
|
||||
@@ -39,18 +39,22 @@ export function PaymentSuccessPage({
|
||||
|
||||
useEffect(() => {
|
||||
const confirm = async () => {
|
||||
// Try all possible sources
|
||||
// 1. Retrieve bookingId from storage (cookie, localStorage, sessionStorage, or query param)
|
||||
let bookingId = getCookie('pendingBookingId');
|
||||
if (!bookingId) bookingId = localStorage.getItem('pendingBookingId');
|
||||
if (!bookingId) bookingId = sessionStorage.getItem('pendingBookingId');
|
||||
if (!bookingId) bookingId = searchParams.get('bookingId');
|
||||
|
||||
console.log('Retrieved bookingId from sources:', {
|
||||
// 2. Get checkoutSessionId from URL query parameter
|
||||
const checkoutSessionId = searchParams.get('session_id');
|
||||
|
||||
console.log('Retrieved data:', {
|
||||
bookingId: bookingId,
|
||||
checkoutSessionId: checkoutSessionId,
|
||||
cookie: getCookie('pendingBookingId'),
|
||||
localStorage: localStorage.getItem('pendingBookingId'),
|
||||
sessionStorage: sessionStorage.getItem('pendingBookingId'),
|
||||
queryParam: searchParams.get('bookingId'),
|
||||
final: bookingId,
|
||||
queryBookingId: searchParams.get('bookingId'),
|
||||
});
|
||||
|
||||
if (!bookingId) {
|
||||
@@ -62,10 +66,20 @@ export function PaymentSuccessPage({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkoutSessionId) {
|
||||
setStatus('error');
|
||||
setErrorMsg(
|
||||
'Missing session ID. Please contact support with your order details.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await confirmPayment(bookingId).unwrap();
|
||||
// Call API with both id and checkoutSessionId
|
||||
await confirmPayment({ id: bookingId, checkoutSessionId }).unwrap();
|
||||
setStatus('success');
|
||||
toast.success('Payment confirmed! Your order is complete.');
|
||||
|
||||
// Clean up all storage
|
||||
document.cookie = 'pendingBookingId=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||
localStorage.removeItem('pendingBookingId');
|
||||
|
||||
@@ -26,10 +26,11 @@ import { Badge } from '../components/ui/badge';
|
||||
import Navbar from '../components/Navbar';
|
||||
import { Footer } from '../components/Footer';
|
||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
||||
import { useGetUserProfileDetailsQuery, useUpdateUserProfileDetailsMutation } from '../Redux/services/profile.service';
|
||||
import { useGetUserCardsQuery, useGetUserProfileDetailsQuery, useUpdateUserProfileDetailsMutation } from '../Redux/services/profile.service';
|
||||
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;
|
||||
@@ -59,71 +60,6 @@ interface ProfilePageProps {
|
||||
currentPage: string;
|
||||
}
|
||||
|
||||
// Mock passes data
|
||||
const mockPasses = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Melbourne Unlimited Card',
|
||||
city: 'Melbourne',
|
||||
type: 'Unlimited Pass',
|
||||
status: 'active',
|
||||
price: 149.00,
|
||||
originalPrice: 249.00,
|
||||
discount: 40,
|
||||
attractions: 25,
|
||||
validFrom: '2024-01-15',
|
||||
validUntil: '2024-01-22',
|
||||
daysRemaining: 3,
|
||||
image: 'https://images.unsplash.com/photo-1514395462725-fb4566210144?w=400',
|
||||
usedAttractions: 8
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Melbourne Selective Card',
|
||||
city: 'Melbourne',
|
||||
type: 'Flexi Pass',
|
||||
status: 'active',
|
||||
price: 89.00,
|
||||
originalPrice: 149.00,
|
||||
discount: 40,
|
||||
attractions: 12,
|
||||
validFrom: '2024-02-01',
|
||||
validUntil: '2024-02-08',
|
||||
daysRemaining: 12,
|
||||
image: 'https://images.unsplash.com/photo-1502602898536-47ad22581b52?w=400',
|
||||
usedAttractions: 3
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Sydney Explorer Pass',
|
||||
city: 'Sydney',
|
||||
type: 'Standard Pass',
|
||||
status: 'expired',
|
||||
price: 89.00,
|
||||
originalPrice: 149.00,
|
||||
discount: 40,
|
||||
attractions: 15,
|
||||
validFrom: '2023-12-01',
|
||||
validUntil: '2023-12-08',
|
||||
daysRemaining: 0,
|
||||
image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400',
|
||||
usedAttractions: 12
|
||||
}
|
||||
];
|
||||
|
||||
// Mock itineraries data
|
||||
const mockItineraries = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Melbourne Unlimited Card',
|
||||
city: 'Melbourne',
|
||||
duration: '7 days',
|
||||
attractions: 25,
|
||||
createdDate: '2024-01-15',
|
||||
status: 'active'
|
||||
}
|
||||
];
|
||||
|
||||
export function ProfilePage({
|
||||
onBackClick,
|
||||
onHomeClick,
|
||||
@@ -164,11 +100,17 @@ export function ProfilePage({
|
||||
postalCode: ''
|
||||
});
|
||||
|
||||
const [sort, setSort] = useState("latest")
|
||||
const navigate = useNavigate()
|
||||
const userId = localStorage.getItem("userId")
|
||||
const cityId = localStorage.getItem("cityId")
|
||||
const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId)
|
||||
const [updateUserProfileDetails, { isLoading: savingChanges }] = useUpdateUserProfileDetailsMutation();
|
||||
const { data: passes, isLoading: loadingPasses } = useGetUserProfileDetailsQuery(userId)
|
||||
const { data, isLoading: loadingCards } = useGetUserCardsQuery({sort,cityId})
|
||||
const { data: userItineraries, isLoading: loadingItineraries } = useGetUserItinerariesQuery(cityId)
|
||||
|
||||
const cards = data ?? []
|
||||
const itineraries = userItineraries?.itineraries ?? []
|
||||
|
||||
useEffect(() => {
|
||||
if (userDetails) {
|
||||
@@ -204,12 +146,12 @@ export function ProfilePage({
|
||||
}
|
||||
};
|
||||
|
||||
const activePasses = mockPasses.filter(pass => pass.status === 'active');
|
||||
const expiredPasses = mockPasses.filter(pass => pass.status === 'expired');
|
||||
const activeCards = cards.filter((card: any) => card.isActive === true);
|
||||
const expiredCards = cards.filter((card: any) => card.isActive === false);
|
||||
|
||||
if (isLoading && loadingPasses) {
|
||||
if (isLoading && loadingCards) {
|
||||
return (
|
||||
<LoadingSpinner/>
|
||||
<LoadingSpinner />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -252,7 +194,7 @@ export function ProfilePage({
|
||||
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pr-2">Profile</span>
|
||||
</h1>
|
||||
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600">
|
||||
Manage your account, passes, and travel itineraries
|
||||
Manage your account, cards, and travel itineraries
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
@@ -265,7 +207,7 @@ export function ProfilePage({
|
||||
{/* Tab Navigation */}
|
||||
<TabsList className="grid w-full grid-cols-3 lg:w-[400px]">
|
||||
<TabsTrigger value="profile" className="font-poppins font-light">My Profile</TabsTrigger>
|
||||
<TabsTrigger value="passes" className="font-poppins font-light">My Passes</TabsTrigger>
|
||||
<TabsTrigger value="passes" className="font-poppins font-light">My Cards</TabsTrigger>
|
||||
<TabsTrigger value="itineraries" className="font-poppins font-light">My Itineraries</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@@ -336,21 +278,6 @@ export function ProfilePage({
|
||||
|
||||
<div>
|
||||
<Label htmlFor="country" className="font-poppins font-light">Country</Label>
|
||||
{/* <Select value={formData.country} onValueChange={(value) => handleInputChange('country', value)}>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue placeholder="Select country" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="us">United States</SelectItem>
|
||||
<SelectItem value="au">Australia</SelectItem>
|
||||
<SelectItem value="uk">United Kingdom</SelectItem>
|
||||
<SelectItem value="ca">Canada</SelectItem>
|
||||
<SelectItem value="de">Germany</SelectItem>
|
||||
<SelectItem value="fr">France</SelectItem>
|
||||
<SelectItem value="in">India</SelectItem>
|
||||
<SelectItem value="jp">Japan</SelectItem>
|
||||
</SelectContent>
|
||||
</Select> */}
|
||||
<Input
|
||||
id="country"
|
||||
value={formData.country}
|
||||
@@ -424,8 +351,8 @@ export function ProfilePage({
|
||||
<CardContent className="p-8 space-y-6">
|
||||
{(() => {
|
||||
// Determine which pass type to show
|
||||
const hasUnlimitedPass = activePasses.some(pass => pass.type === 'Unlimited Pass');
|
||||
const hasSelectivePass = activePasses.some(pass => pass.type === 'Flexi Pass');
|
||||
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 (
|
||||
@@ -602,55 +529,71 @@ export function ProfilePage({
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* My Passes Tab */}
|
||||
{/* My Cards Tab */}
|
||||
<TabsContent value="passes" className="space-y-8">
|
||||
{/* Active Passes */}
|
||||
{/* Active Cards */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<h2 className="font-poppins text-2xl mb-6 font-normal">Active Passes</h2>
|
||||
<h2 className="font-poppins text-2xl mb-6 font-normal">Active Cards</h2>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{activePasses.map((pass) => (
|
||||
<Card key={pass.id} className="overflow-hidden">
|
||||
{activeCards.map((card: any) => (
|
||||
<Card key={card.id} className="overflow-hidden">
|
||||
<div
|
||||
className="flex cursor-pointer hover:bg-gray-50 transition-colors duration-200 rounded-lg p-2 -m-2"
|
||||
onClick={() => onDownloadAppClick?.()}
|
||||
onClick={() => navigate(`/view-card-details/${card.id}`)}
|
||||
>
|
||||
<div className="w-32 h-32 flex-shrink-0">
|
||||
<ImageWithFallback
|
||||
src={pass.image}
|
||||
alt={pass.city}
|
||||
className="w-full h-full object-cover"
|
||||
src={card.city.bannerImage}
|
||||
alt={card.city.name}
|
||||
className="w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 p-6">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<h3 className="font-normal font-poppins">{pass.name}</h3>
|
||||
<Badge variant={pass.status === 'active' ? 'default' : 'secondary'} className="mt-1">
|
||||
{pass.status}
|
||||
<h3 className="font-normal font-poppins">{card.card.title}</h3>
|
||||
<Badge variant={card.isActive === true ? 'default' : 'secondary'} className="mt-1">
|
||||
{card.isActive && "Active"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-semibold text-lg font-poppins">${pass.price}</div>
|
||||
<div className="text-sm text-gray-500 line-through font-poppins font-light">${pass.originalPrice}</div>
|
||||
<div className="font-semibold text-lg font-poppins">${card.totalAmount}</div>
|
||||
<div className="text-sm text-gray-500 line-through font-poppins font-light">${card.totalAmount + 50}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-sm font-poppins font-light">
|
||||
<div className="flex justify-between">
|
||||
<span>Attractions:</span>
|
||||
<span>{pass.usedAttractions}/{pass.attractions}</span>
|
||||
{card.cardMode === "flexi" ? (
|
||||
<>
|
||||
<span>Attractions:</span>
|
||||
<span>{card.noOfAttractions}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>Days:</span>
|
||||
<span>{card.noOfDays}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Valid until:</span>
|
||||
<span>{new Date(pass.validUntil).toLocaleDateString()}</span>
|
||||
<span>{new Date(card.validUpto).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Days remaining:</span>
|
||||
<span className="text-primary font-normal">{pass.daysRemaining} days</span>
|
||||
<span className="text-primary font-normal">{Math.max(
|
||||
0,
|
||||
Math.ceil(
|
||||
(new Date(card.validUpto).getTime() - new Date().getTime()) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
)
|
||||
)} days</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -662,7 +605,7 @@ export function ProfilePage({
|
||||
{/* Offers Button */}
|
||||
<div className="mt-8 text-center">
|
||||
<Button
|
||||
onClick={onOffersClick}
|
||||
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" />
|
||||
@@ -671,46 +614,56 @@ export function ProfilePage({
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Expired Passes */}
|
||||
{expiredPasses.length > 0 && (
|
||||
{/* Expired Cards */}
|
||||
{expiredCards.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
>
|
||||
<h2 className="font-poppins text-2xl mb-6 font-normal">Expired Passes</h2>
|
||||
<h2 className="font-poppins text-2xl mb-6 font-normal">Expired Cards</h2>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{expiredPasses.map((pass) => (
|
||||
<Card key={pass.id} className="overflow-hidden opacity-60">
|
||||
{expiredCards.map((card: any) => (
|
||||
<Card key={card.id} className="overflow-hidden opacity-60">
|
||||
<div className="flex">
|
||||
<div className="w-32 h-32 flex-shrink-0">
|
||||
<ImageWithFallback
|
||||
src={pass.image}
|
||||
alt={pass.city}
|
||||
src={card.city.bannerImage}
|
||||
alt={card.city.name}
|
||||
className="w-full h-full object-cover grayscale"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 p-6">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<h3 className="font-normal font-poppins">{pass.name}</h3>
|
||||
<h3 className="font-normal font-poppins">{card.card.title}</h3>
|
||||
<Badge variant="secondary" className="mt-1">
|
||||
{pass.status}
|
||||
{!card.isActive && "Expired"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-semibold text-lg font-poppins">${pass.price}</div>
|
||||
<div className="font-semibold text-lg font-poppins">${card.price}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-sm font-poppins font-light">
|
||||
<div className="flex justify-between">
|
||||
<span>Attractions visited:</span>
|
||||
<span>{pass.usedAttractions}/{pass.attractions}</span>
|
||||
{card.cardMode === "flexi" ? (
|
||||
<>
|
||||
<span>Attractions:</span>
|
||||
<span>{card.noOfAttractions}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>Days:</span>
|
||||
<span>{card.noOfDays}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Expired on:</span>
|
||||
<span>{new Date(pass.validUntil).toLocaleDateString()}</span>
|
||||
<span>{new Date(card.validUpto).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -733,47 +686,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={onCreateItineraryClick}
|
||||
onClick={() => navigate("/create-itinerary")}
|
||||
>
|
||||
<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} Travel Plan</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(`/view-itinerary/${itinerary.id}`)}
|
||||
>
|
||||
View Itinerary
|
||||
</Button>
|
||||
@@ -793,7 +746,7 @@ export function ProfilePage({
|
||||
</p>
|
||||
<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={onCreateItineraryClick}
|
||||
onClick={() => navigate("/create-itinerary")}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Create Itinerary
|
||||
|
||||
@@ -1,527 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowLeft, Lock, Shield, CreditCard, Check, X, Tag } from 'lucide-react';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Input } from '../components/ui/input';
|
||||
import { Label } from '../components/ui/label';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
|
||||
import { Separator } from '../components/ui/separator';
|
||||
import { Badge } from '../components/ui/badge';
|
||||
import Navbar from '../components/Navbar';
|
||||
import { Footer } from '../components/Footer';
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface SecureCheckoutPageProps {
|
||||
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;
|
||||
onContactUsClick?: () => void;
|
||||
onEsimsClick?: () => void;
|
||||
onHotelDiscountsClick?: () => void;
|
||||
currentPage?: string;
|
||||
user: User | null;
|
||||
}
|
||||
|
||||
export function SecureCheckoutPage({
|
||||
onBackClick,
|
||||
onHomeClick,
|
||||
onMelbourneClick,
|
||||
onPassesClick,
|
||||
onCheckoutClick,
|
||||
onSignInClick,
|
||||
onSignOutClick,
|
||||
onAttractionsClick,
|
||||
onBlogsClick,
|
||||
onHowItWorksClick,
|
||||
onFAQClick,
|
||||
onPrivacyPolicyClick,
|
||||
onAboutUsClick,
|
||||
onProfileClick,
|
||||
onCityCardsClick,
|
||||
onMagicItineraryClick,
|
||||
onPostCardsClick,
|
||||
onOffersClick,
|
||||
onContactUsClick,
|
||||
onEsimsClick,
|
||||
onHotelDiscountsClick,
|
||||
currentPage,
|
||||
user
|
||||
}: SecureCheckoutPageProps) {
|
||||
const [selectedTab, setSelectedTab] = useState('myself');
|
||||
const [formData, setFormData] = useState({
|
||||
firstName: 'Frank',
|
||||
lastName: 'Adam S',
|
||||
email: 'frank2023@mail.com',
|
||||
phone: '',
|
||||
travelDate: '',
|
||||
billingAddress: '',
|
||||
city: '',
|
||||
country: 'England',
|
||||
state: '',
|
||||
zipCode: '000000'
|
||||
});
|
||||
const [discountCode, setDiscountCode] = useState('');
|
||||
const [isEmailVerified, setIsEmailVerified] = useState(false);
|
||||
const [showPaymentSuccess, setShowPaymentSuccess] = useState(false);
|
||||
|
||||
const orderSummary = {
|
||||
item: 'Melbourne - Unlimited Card',
|
||||
duration: '2 Days',
|
||||
adults: 3,
|
||||
kids: 3,
|
||||
quantity: 1,
|
||||
price: 49.80,
|
||||
discount: 7.20,
|
||||
tax: 2.24
|
||||
};
|
||||
|
||||
const subtotal = orderSummary.price;
|
||||
const discount = orderSummary.discount;
|
||||
const total = subtotal - discount;
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handlePayment = () => {
|
||||
setShowPaymentSuccess(true);
|
||||
setTimeout(() => {
|
||||
setShowPaymentSuccess(false);
|
||||
onHomeClick();
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Navbar
|
||||
onHomeClick={onHomeClick}
|
||||
onMelbourneClick={onMelbourneClick}
|
||||
onPassesClick={onPassesClick}
|
||||
onCheckoutClick={onCheckoutClick}
|
||||
onSignInClick={onSignInClick}
|
||||
onSignOutClick={onSignOutClick}
|
||||
onAttractionsClick={onAttractionsClick}
|
||||
onBlogsClick={onBlogsClick}
|
||||
onHowItWorksClick={onHowItWorksClick}
|
||||
onFAQClick={onFAQClick}
|
||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
||||
onAboutUsClick={onAboutUsClick}
|
||||
onProfileClick={onProfileClick}
|
||||
onCityCardsClick={onCityCardsClick}
|
||||
onMagicItineraryClick={onMagicItineraryClick}
|
||||
onPostCardsClick={onPostCardsClick}
|
||||
onOffersClick={onOffersClick}
|
||||
currentPage={currentPage}
|
||||
isUserSignedIn={!!user}
|
||||
user={user}
|
||||
/>
|
||||
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onBackClick}
|
||||
className="mb-6 p-0 hover:bg-transparent"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 mr-2" />
|
||||
Back
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<h1 className="font-merchant text-3xl md:text-4xl leading-tight">
|
||||
Secure <span className="text-primary">Checkout</span>
|
||||
</h1>
|
||||
<div className="flex items-center gap-2 text-green-600">
|
||||
<Shield className="w-5 h-5" />
|
||||
<span className="font-poppins text-sm font-medium">SSL Secured</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground">
|
||||
Complete your purchase securely. Your payment information is protected.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-8">
|
||||
{/* Checkout Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="lg:col-span-2"
|
||||
>
|
||||
<Card className="shadow-lg border-0">
|
||||
<CardHeader className="pb-6">
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab} className="w-full">
|
||||
<TabsList className="grid w-fit grid-cols-2 bg-muted/50">
|
||||
<TabsTrigger value="myself" className="px-6">For myself</TabsTrigger>
|
||||
<TabsTrigger value="gift" className="px-6">To gift Someone</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-8">
|
||||
{/* Personal Information */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-primary text-primary-foreground rounded-full flex items-center justify-center font-medium">
|
||||
1
|
||||
</div>
|
||||
<h2 className="font-merchant text-xl font-medium leading-snug">Personal Information</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="firstName">First Name</Label>
|
||||
<Input
|
||||
id="firstName"
|
||||
value={formData.firstName}
|
||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="lastName">Last Name</Label>
|
||||
<Input
|
||||
id="lastName"
|
||||
value={formData.lastName}
|
||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email Address</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange('email', e.target.value)}
|
||||
className="h-12 pr-20"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={isEmailVerified ? "default" : "outline"}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 h-8"
|
||||
onClick={() => setIsEmailVerified(!isEmailVerified)}
|
||||
>
|
||||
{isEmailVerified ? (
|
||||
<>
|
||||
<Check className="w-3 h-3 mr-1" />
|
||||
Verified
|
||||
</>
|
||||
) : (
|
||||
'Verify'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">Phone Number</Label>
|
||||
<Input
|
||||
id="phone"
|
||||
type="tel"
|
||||
placeholder="Enter mobile number"
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleInputChange('phone', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="travelDate">Select Travel Date</Label>
|
||||
<Input
|
||||
id="travelDate"
|
||||
type="date"
|
||||
value={formData.travelDate}
|
||||
onChange={(e) => handleInputChange('travelDate', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Billing Information */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-primary text-primary-foreground rounded-full flex items-center justify-center font-medium">
|
||||
2
|
||||
</div>
|
||||
<h2 className="font-merchant text-xl font-medium leading-snug">Billing Information</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="billingAddress">Billing Address</Label>
|
||||
<Input
|
||||
id="billingAddress"
|
||||
placeholder="Enter Address"
|
||||
value={formData.billingAddress}
|
||||
onChange={(e) => handleInputChange('billingAddress', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="city">City</Label>
|
||||
<Input
|
||||
id="city"
|
||||
placeholder="Enter City"
|
||||
value={formData.city}
|
||||
onChange={(e) => handleInputChange('city', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="country">Country</Label>
|
||||
<Input
|
||||
id="country"
|
||||
value={formData.country}
|
||||
onChange={(e) => handleInputChange('country', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="state">State</Label>
|
||||
<Input
|
||||
id="state"
|
||||
placeholder="State"
|
||||
value={formData.state}
|
||||
onChange={(e) => handleInputChange('state', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="zipCode">Zip Code</Label>
|
||||
<Input
|
||||
id="zipCode"
|
||||
value={formData.zipCode}
|
||||
onChange={(e) => handleInputChange('zipCode', e.target.value)}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Order Summary */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="lg:col-span-1"
|
||||
>
|
||||
<Card className="shadow-lg border-0 sticky top-6">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CreditCard className="w-5 h-5" />
|
||||
Order Summary
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-6">
|
||||
{/* Item Details */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-poppins font-medium">{orderSummary.item}</h3>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>{orderSummary.duration}</span>
|
||||
<span>•</span>
|
||||
<span>Adults-{orderSummary.adults}</span>
|
||||
<span>•</span>
|
||||
<span>Kids-{orderSummary.kids}</span>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Qty: {orderSummary.quantity}
|
||||
</div>
|
||||
</div>
|
||||
<div className="font-poppins text-lg font-medium">
|
||||
${orderSummary.price.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Discount Code */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
placeholder="Gift or discount code"
|
||||
value={discountCode}
|
||||
onChange={(e) => setDiscountCode(e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button variant="outline" className="px-6">
|
||||
Apply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Price Breakdown */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<span>Subtotal</span>
|
||||
<span>${subtotal.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-green-600">
|
||||
<span>Discount</span>
|
||||
<span>-${discount.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Total */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<div className="font-medium">Total</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Including ${orderSummary.tax.toFixed(2)} in taxes
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-primary">
|
||||
${total.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment Button */}
|
||||
<Button
|
||||
onClick={handlePayment}
|
||||
className="w-full h-14 text-lg bg-gradient-to-r from-primary to-primary/90 hover:from-primary/90 hover:to-primary/80 relative overflow-hidden group"
|
||||
disabled={showPaymentSuccess}
|
||||
>
|
||||
{showPaymentSuccess ? (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Check className="w-5 h-5" />
|
||||
Payment Successful!
|
||||
</motion.div>
|
||||
) : (
|
||||
<>
|
||||
<Lock className="w-5 h-5 mr-2" />
|
||||
Pay ${total.toFixed(2)}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent transform -skew-x-12 -translate-x-full group-hover:translate-x-full transition-transform duration-700" />
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Security Notice */}
|
||||
<div className="text-xs text-muted-foreground leading-relaxed">
|
||||
Your personal data will be used to process your order, support your experience throughout this website, and for other purposes described in our privacy policy.
|
||||
</div>
|
||||
|
||||
{/* Trust Badges */}
|
||||
<div className="flex items-center justify-center gap-4 pt-4">
|
||||
<div className="flex items-center gap-2 text-green-600">
|
||||
<Shield className="w-4 h-4" />
|
||||
<span className="text-xs">256-bit SSL</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-blue-600">
|
||||
<Lock className="w-4 h-4" />
|
||||
<span className="text-xs">Secure Payment</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer
|
||||
onHomeClick={onHomeClick}
|
||||
onMelbourneClick={onMelbourneClick}
|
||||
onPassesClick={onPassesClick}
|
||||
onCheckoutClick={onCheckoutClick}
|
||||
onSignInClick={onSignInClick}
|
||||
onAttractionsClick={onAttractionsClick}
|
||||
onBlogsClick={onBlogsClick}
|
||||
onHowItWorksClick={onHowItWorksClick}
|
||||
onFAQClick={onFAQClick}
|
||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
||||
onAboutUsClick={onAboutUsClick}
|
||||
onProfileClick={onProfileClick}
|
||||
onCityCardsClick={onCityCardsClick}
|
||||
onMagicItineraryClick={onMagicItineraryClick}
|
||||
onPostCardsClick={onPostCardsClick}
|
||||
onOffersClick={onOffersClick}
|
||||
/>
|
||||
|
||||
{/* Success Overlay */}
|
||||
{showPaymentSuccess && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0, rotate: 180 }}
|
||||
animate={{ scale: 1, rotate: 0 }}
|
||||
className="bg-white p-8 rounded-2xl shadow-2xl text-center max-w-sm mx-4"
|
||||
>
|
||||
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Check className="w-8 h-8 text-green-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-2">Payment Successful!</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Your Melbourne CityCard has been purchased successfully.
|
||||
</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -188,10 +188,10 @@ export function SuperSavingsPage({
|
||||
</section>
|
||||
|
||||
{/* Trusted By Companies Section */}
|
||||
<section className="py-12 bg-background">
|
||||
<section className="py-10 bg-background">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-6xl mx-auto text-center">
|
||||
<div className="mb-10">
|
||||
<div className="mb-1">
|
||||
<h2 className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight mb-4">
|
||||
<span>Trusted by the </span>
|
||||
<span className="font-semibold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">world's best</span>
|
||||
@@ -206,7 +206,7 @@ export function SuperSavingsPage({
|
||||
</section>
|
||||
|
||||
{/* Featured Super Savings Section */}
|
||||
<section className="py-20">
|
||||
<section className="">
|
||||
<div className="container mx-auto px-4">
|
||||
<motion.div
|
||||
className="text-center mb-12"
|
||||
@@ -225,7 +225,7 @@ export function SuperSavingsPage({
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="container mx-auto px-4 pt-51 pb-16">
|
||||
<div className="container mx-auto px-4 pt-11 pb-16">
|
||||
<div className="flex gap-8">
|
||||
{/* Left Sidebar - Filters */}
|
||||
<div className="w-64 flex-shrink-0">
|
||||
@@ -269,7 +269,7 @@ export function SuperSavingsPage({
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
{/* Breadcrumb */}
|
||||
<div className="mb-8">
|
||||
{/* <div className="mb-8">
|
||||
<p className="font-poppins text-sm text-gray-800">
|
||||
{fromSource === 'passes' ? (
|
||||
<>
|
||||
@@ -283,7 +283,7 @@ export function SuperSavingsPage({
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Header Section */}
|
||||
<div className="mb-8">
|
||||
@@ -395,7 +395,7 @@ export function SuperSavingsPage({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
{/* <div className="text-center">
|
||||
<Button
|
||||
onClick={onSignInClick}
|
||||
variant="outline"
|
||||
@@ -403,7 +403,7 @@ export function SuperSavingsPage({
|
||||
>
|
||||
View All Super Savings
|
||||
</Button>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
417
src/pages/ViewCardDetailsPage.tsx
Normal file
@@ -0,0 +1,417 @@
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowLeft, ChevronRight, Calendar, MapPin, Star, CheckCircle, Sparkles, Users, Clock, Gift, Ticket } from 'lucide-react';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Card, CardContent } from '../components/ui/card';
|
||||
import { Badge } from '../components/ui/badge';
|
||||
import Navbar from '../components/Navbar';
|
||||
import { Footer } from '../components/Footer';
|
||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
||||
import { useGetUserCardDetailsQuery } from '../Redux/services/profile.service';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import LoadingSpinner from '../components/LoadingSpinner';
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ViewCardDetailsPageProps {
|
||||
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;
|
||||
onContactUsClick?: () => void;
|
||||
onEsimsClick?: () => void;
|
||||
onHotelDiscountsClick?: () => void;
|
||||
currentPage: string;
|
||||
user: User | null;
|
||||
}
|
||||
|
||||
export function ViewCardDetailsPage({
|
||||
onBackClick,
|
||||
onHomeClick,
|
||||
onMelbourneClick,
|
||||
onPassesClick,
|
||||
onCheckoutClick,
|
||||
onSignInClick,
|
||||
onSignOutClick,
|
||||
onAttractionsClick,
|
||||
onBlogsClick,
|
||||
onHowItWorksClick,
|
||||
onFAQClick,
|
||||
onPrivacyPolicyClick,
|
||||
onAboutUsClick,
|
||||
onProfileClick,
|
||||
onCityCardsClick,
|
||||
onMagicItineraryClick,
|
||||
onPostCardsClick,
|
||||
onOffersClick,
|
||||
onContactUsClick,
|
||||
currentPage,
|
||||
user
|
||||
}: ViewCardDetailsPageProps) {
|
||||
// Card type state
|
||||
const { cardId } = useParams()
|
||||
const { data, isLoading } = useGetUserCardDetailsQuery(cardId)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const baseUrl = import.meta.env.VITE_BASE_URL;
|
||||
|
||||
const cardDetails = data?.bookingDetails ?? {}
|
||||
|
||||
const attractions = data?.attractions ?? []
|
||||
const offers = data?.offers ?? []
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingSpinner />
|
||||
)
|
||||
}
|
||||
|
||||
// Generate QR code pattern
|
||||
const generateQRPattern = () => {
|
||||
const size = 17;
|
||||
const pattern = [];
|
||||
|
||||
for (let i = 0; i < size * size; i++) {
|
||||
const row = Math.floor(i / size);
|
||||
const col = i % size;
|
||||
|
||||
// Corner squares (5x5 for smaller QR)
|
||||
const isCornerSquare =
|
||||
(row < 5 && col < 5) || // Top-left
|
||||
(row < 5 && col >= 12) || // Top-right
|
||||
(row >= 12 && col < 5); // Bottom-left
|
||||
|
||||
// Finder patterns within corner squares
|
||||
const isFinderPattern = isCornerSquare && (
|
||||
(row === 0 || row === 4 || col === 0 || col === 4) ||
|
||||
(row >= 2 && row <= 2 && col >= 2 && col <= 2)
|
||||
);
|
||||
|
||||
// Timing patterns
|
||||
const isTimingPattern = (row === 4 && col >= 6 && col <= 10) || (col === 4 && row >= 6 && row <= 10);
|
||||
|
||||
// Random data pattern for other areas
|
||||
const isDataPattern = !isCornerSquare && !isTimingPattern && Math.random() > 0.4;
|
||||
|
||||
pattern.push(isFinderPattern || isTimingPattern || isDataPattern);
|
||||
}
|
||||
|
||||
return pattern;
|
||||
};
|
||||
|
||||
const qrPattern = generateQRPattern();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#f8f8f8]">
|
||||
<Navbar
|
||||
onHomeClick={onHomeClick}
|
||||
onMelbourneClick={onMelbourneClick}
|
||||
onPassesClick={onPassesClick}
|
||||
onCheckoutClick={onCheckoutClick}
|
||||
onSignInClick={onSignInClick}
|
||||
onAttractionsClick={onAttractionsClick}
|
||||
onBlogsClick={onBlogsClick}
|
||||
onHowItWorksClick={onHowItWorksClick}
|
||||
onFAQClick={onFAQClick}
|
||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
||||
onAboutUsClick={onAboutUsClick}
|
||||
onProfileClick={onProfileClick}
|
||||
onCityCardsClick={onCityCardsClick}
|
||||
onMagicItineraryClick={onMagicItineraryClick}
|
||||
onPostCardsClick={onPostCardsClick}
|
||||
onOffersClick={onOffersClick}
|
||||
currentPage={currentPage}
|
||||
isUserSignedIn={!!user}
|
||||
user={user}
|
||||
/>
|
||||
|
||||
<div className="container mx-auto px-4 pt-40 pb-6">
|
||||
{/* Back Button */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="mb-6"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate(-1)}
|
||||
className="p-0 hover:bg-transparent"
|
||||
>
|
||||
<div className="text-gray-600 hover:text-primary transition-colors duration-200 flex items-center gap-2">
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back</span>
|
||||
</div>
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
{/* Pass Card - Redesigned */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<Card className="border-0 shadow-lg overflow-hidden">
|
||||
{/* Gradient Header Bar */}
|
||||
<div className="h-2 bg-gradient-to-r from-primary via-secondary to-primary"></div>
|
||||
|
||||
<CardContent className="p-6">
|
||||
{/* Card Header */}
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Icon */}
|
||||
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-primary/10 to-secondary/10 flex items-center justify-center flex-shrink-0">
|
||||
<Ticket className="w-7 h-7 text-primary" />
|
||||
</div>
|
||||
|
||||
{/* Title and Status */}
|
||||
<div>
|
||||
<h2 className="font-merchant text-xl font-semibold text-gray-900 mb-2">
|
||||
{cardDetails.name}
|
||||
</h2>
|
||||
<Badge className="bg-gradient-to-r from-green-500 to-green-600 text-white px-3 py-1 rounded-full">
|
||||
<div className="w-2 h-2 bg-white rounded-full mr-2 animate-pulse"></div>
|
||||
{cardDetails.isActive ? "Active" : "Inactive"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date Badge */}
|
||||
<div className="bg-gradient-to-br from-primary/5 to-secondary/5 px-4 py-2 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-primary">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span className="font-poppins font-medium text-sm">{cardDetails.validUpto}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{/* Duration */}
|
||||
<div className="bg-gray-50 rounded-lg p-4 text-center">
|
||||
<div className="w-10 h-10 rounded-full bg-white shadow-sm flex items-center justify-center mx-auto mb-2">
|
||||
<Clock className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div className="font-poppins text-xs text-gray-500 mb-1">Duration</div>
|
||||
<div className="font-poppins font-semibold text-gray-900">{cardDetails.noOfDays}</div>
|
||||
</div>
|
||||
|
||||
{/* Adults */}
|
||||
<div className="bg-gray-50 rounded-lg p-4 text-center">
|
||||
<div className="w-10 h-10 rounded-full bg-white shadow-sm flex items-center justify-center mx-auto mb-2">
|
||||
<Users className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div className="font-poppins text-xs text-gray-500 mb-1">Adults</div>
|
||||
<div className="font-poppins font-semibold text-gray-900">{cardDetails.totalAdult}</div>
|
||||
</div>
|
||||
|
||||
{/* Kids */}
|
||||
<div className="bg-gray-50 rounded-lg p-4 text-center">
|
||||
<div className="w-10 h-10 rounded-full bg-white shadow-sm flex items-center justify-center mx-auto mb-2">
|
||||
<Star className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div className="font-poppins text-xs text-gray-500 mb-1">Kids</div>
|
||||
<div className="font-poppins font-semibold text-gray-900">{cardDetails.totalChild}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Attractions Section - Redesigned */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<Card className="border-0 shadow-lg">
|
||||
<CardContent className="p-6">
|
||||
{/* Section Header */}
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-primary to-secondary flex items-center justify-center">
|
||||
<MapPin className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-merchant text-lg font-semibold text-gray-900">
|
||||
Included Attractions
|
||||
</h3>
|
||||
<p className="font-poppins text-xs text-gray-500">
|
||||
Explore amazing places
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className="bg-primary/10 text-primary border-0 px-3 py-1">
|
||||
<span className="font-poppins font-medium">{attractions.length} {attractions.length > 1 ? "Places" : "Place"}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Attractions Scroll */}
|
||||
<div className="flex items-center gap-4 overflow-x-auto pb-2 scrollbar-hide mb-4">
|
||||
{attractions.map((attraction, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.1 * index }}
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
<Card className="w-40 hover:shadow-lg transition-all duration-300 border-0 bg-gradient-to-br from-gray-50 to-white cursor-pointer group">
|
||||
<CardContent className="p-3">
|
||||
{/* Image */}
|
||||
<div className="w-full h-24 bg-gray-200 rounded-lg mb-3 overflow-hidden">
|
||||
<ImageWithFallback
|
||||
src={attraction.image}
|
||||
alt={attraction.title}
|
||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div>
|
||||
<h4 className="font-poppins font-semibold text-sm text-gray-900 mb-1 truncate">
|
||||
{attraction.title}
|
||||
</h4>
|
||||
<div className="flex items-center gap-1 text-xs text-primary">
|
||||
<Ticket className="w-3 h-3" />
|
||||
{/* <span className="font-poppins font-medium">{attraction.tours}</span> */}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* View All Button */}
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full border-2 border-dashed border-primary/30 hover:border-primary hover:bg-primary/5 text-primary font-poppins font-medium transition-all duration-300"
|
||||
onClick={() => navigate("/attractions")}
|
||||
>
|
||||
<span>View All Attractions</span>
|
||||
<ChevronRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Special Benefits & Offers - Redesigned */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<Card className="border-0 shadow-lg overflow-hidden bg-gradient-to-br from-primary/5 via-white to-secondary/5">
|
||||
<CardContent className="p-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-primary to-secondary flex items-center justify-center">
|
||||
<Gift className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-merchant text-xl font-semibold text-gray-900">
|
||||
Exclusive Offers
|
||||
</h3>
|
||||
<p className="font-poppins text-sm text-gray-600 font-normal">
|
||||
Special deals for cardholders
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className="bg-gradient-to-r from-green-500 to-green-600 text-white border-0 px-3 py-1">
|
||||
<Sparkles className="w-3 h-3 mr-1" />
|
||||
<span className="font-poppins font-medium">Limited Time</span>
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Featured Offers Grid */}
|
||||
<div className="space-y-4 mb-5">
|
||||
{
|
||||
offers.map((offer: any) => (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-gradient-to-br from-orange-50 to-red-50 rounded-xl p-4 border border-orange-200 hover:border-orange-300 transition-all duration-300 cursor-pointer group hover:shadow-lg"
|
||||
onClick={() => navigate(`/super-savings/${offer.id}`)}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-orange-400 to-red-500 flex items-center justify-center">
|
||||
<Gift className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-poppins font-semibold text-base text-gray-900">
|
||||
{offer.title}
|
||||
</h4>
|
||||
{/* <p className="font-poppins text-xs text-gray-600 font-normal">
|
||||
{offer.description}
|
||||
</p> */}
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="bg-white rounded-lg px-3 py-2 shadow-sm">
|
||||
<span className="font-merchant text-2xl font-semibold text-orange-600">25%</span>
|
||||
<span className="font-poppins text-xs text-gray-600 ml-1">OFF</span>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-poppins text-xs text-gray-700 font-normal">
|
||||
{offer.description}
|
||||
</p>
|
||||
<ChevronRight className="w-4 h-4 text-orange-600 group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
</motion.div>
|
||||
))
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<Footer
|
||||
onHomeClick={onHomeClick}
|
||||
onMelbourneClick={onMelbourneClick}
|
||||
onPassesClick={onPassesClick}
|
||||
onCheckoutClick={onCheckoutClick}
|
||||
onSignInClick={onSignInClick}
|
||||
onAttractionsClick={onAttractionsClick}
|
||||
onBlogsClick={onBlogsClick}
|
||||
onHowItWorksClick={onHowItWorksClick}
|
||||
onFAQClick={onFAQClick}
|
||||
onPrivacyPolicyClick={onPrivacyPolicyClick}
|
||||
onAboutUsClick={onAboutUsClick}
|
||||
onProfileClick={onProfileClick}
|
||||
onCityCardsClick={onCityCardsClick}
|
||||
onMagicItineraryClick={onMagicItineraryClick}
|
||||
onPostCardsClick={onPostCardsClick}
|
||||
onOffersClick={onOffersClick}
|
||||
onContactUsClick={onContactUsClick}
|
||||
currentPage={currentPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
export const footerSections = {
|
||||
explore: {
|
||||
title: 'Explore',
|
||||
links: ['Home', 'My Adventures', 'Cancellation policy']
|
||||
links: ['Home',
|
||||
'Cancellation policy'
|
||||
]
|
||||
},
|
||||
learn: {
|
||||
title: 'Learn',
|
||||
links: ['How It Works', 'Safety Tips', 'FAQ', 'Blog']
|
||||
},
|
||||
community: {
|
||||
title: 'Community',
|
||||
links: ['Testimonials', 'Partner Stories', 'Events & Meetups', 'Newsletter']
|
||||
links: ['How It Works',
|
||||
// 'Safety Tips',
|
||||
'FAQ', 'Blog']
|
||||
},
|
||||
// community: {
|
||||
// title: 'Community',
|
||||
// links: ['Testimonials', 'Partner Stories', 'Events & Meetups', 'Newsletter']
|
||||
// },
|
||||
support: {
|
||||
title: 'Support',
|
||||
links: ['Contact Us', 'Privacy Policy', 'Terms of Service']
|
||||
|
||||