113 Commits

Author SHA1 Message Date
2918cfd33c Merge pull request 'main' (#42) from main into testing
Some checks failed
CityCards-Website / Build-CityCards-Website (push) Failing after 2m26s
Reviewed-on: #42
2026-04-27 12:01:33 +00:00
5abda5b6cb Merge pull request 'arya-branch' (#41) from arya-branch into main
Reviewed-on: #41
2026-04-27 11:59:50 +00:00
aryabenade
a03d1999bf remove the vertical white spaces from Melbourne page 2026-04-27 16:33:04 +05:30
aryabenade
201e8b86d4 reduce the size of the testimonial cards in LandingTrustSection component 2026-04-27 16:23:40 +05:30
aryabenade
39e63deca2 change the css of LandingMagicItinerary Component
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 16:23:10 +05:30
aryabenade
c7af20fac7 change the color to red when attraction category selected on home page 2026-04-27 15:58:56 +05:30
aryabenade
e0c314d3af show validation errors below the fields instead of toasters 2026-04-27 15:48:34 +05:30
aryabenade
4c985e5177 remove ai word from magic itinerary from city home page 2026-04-27 14:57:51 +05:30
d271b3b64c Merge pull request 'main' (#39) from main into testing
Some checks failed
CityCards-Website / Build-CityCards-Website (push) Failing after 2m27s
Reviewed-on: #39
2026-04-27 06:17:15 +00:00
7fc7f1b433 Merge pull request 'push code after npm install' (#38) from arya-branch into main
Reviewed-on: #38
2026-04-27 06:16:20 +00:00
aryabenade
34223f1c81 push code after npm install 2026-04-27 11:43:32 +05:30
eea8eb52f3 Merge pull request 'main' (#36) from main into testing
Some checks failed
CityCards-Website / Build-CityCards-Website (push) Has been cancelled
Reviewed-on: #36
2026-04-27 06:07:32 +00:00
3a5d6b0724 Merge pull request 'arya-branch' (#35) from arya-branch into main
Reviewed-on: #35
2026-04-27 06:07:12 +00:00
aryabenade
05f134fdba change the padding of navbar 2026-04-27 11:22:54 +05:30
aryabenade
13780803ba add the ScrollToTop Component
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 23:43:33 +05:30
aryabenade
0dbba7f80e select the same card on checkout page which we select on the buy cards page
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 19:52:31 +05:30
7cdaa43e5b Merge pull request 'main' (#34) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 22s
Reviewed-on: #34
2026-04-24 16:42:03 +00:00
7b3833e5f7 Merge pull request 'arya-branch' (#32) from arya-branch into main
Reviewed-on: #32
2026-04-24 16:40:46 +00:00
aryabenade
d8976a29b4 remove the booking section in supersavings details page 2026-04-24 22:03:36 +05:30
aryabenade
1be37e098b show login modal when clicked on ctabutton if logged out 2026-04-24 22:03:11 +05:30
aryabenade
e3fde4bb17 add validations for spaces in the forms 2026-04-24 21:58:34 +05:30
aryabenade
33a782ca54 show cityName on basis of citySelected 2026-04-24 21:45:49 +05:30
aryabenade
a651186276 add validations in forms 2026-04-24 21:30:04 +05:30
cc9bc18bef Merge pull request 'main' (#30) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 24s
Reviewed-on: #30
2026-04-24 14:32:20 +00:00
b276dec0f5 Merge pull request 'arya-branch' (#29) from arya-branch into main
Reviewed-on: #29
2026-04-24 14:30:49 +00:00
aryabenade
e9ccc78bb0 add protected routes 2026-04-24 19:59:02 +05:30
aryabenade
962d4283e6 remove commented code 2026-04-24 19:09:20 +05:30
aryabenade
67d7f977b7 show attractions from backend on cityHomePage 2026-04-24 19:02:51 +05:30
566afcfd75 Merge pull request 'main' (#28) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 22s
Reviewed-on: #28
2026-04-24 12:41:52 +00:00
e9f404e4df Merge pull request 'fix issues according to client feedback' (#27) from arya-branch into main
Reviewed-on: #27
2026-04-24 12:41:32 +00:00
aryabenade
848c33edbd fix issues according to client feedback 2026-04-24 18:10:03 +05:30
1438178535 Merge pull request 'main' (#26) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 22s
Reviewed-on: #26
2026-04-24 11:26:40 +00:00
c8729848fb Merge pull request 'enable the download itinerary feature in itinerary summary page' (#25) from arya-branch into main
Reviewed-on: #25
2026-04-24 11:26:03 +00:00
aryabenade
668a183123 enable the download itinerary feature in itinerary summary page 2026-04-24 16:44:43 +05:30
d0c02f4fb9 Merge pull request 'main' (#24) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 21s
Reviewed-on: #24
2026-04-24 11:10:06 +00:00
dd5e49bcc1 Merge pull request 'arya-branch' (#23) from arya-branch into main
Reviewed-on: #23
2026-04-24 11:09:38 +00:00
aryabenade
c3d3d0c751 Merge branch 'hemant' of http://git.wdipl.com/CityCards/CityCards-Website into arya-branch 2026-04-24 16:38:03 +05:30
aryabenade
340de94a5d add blogs from backend in the homepage after city selected 2026-04-24 16:37:32 +05:30
Hemant Vishwakarma
54f33c2d34 Implement download itinerary pdf in my profile 2026-04-24 16:32:35 +05:30
ebff7f1887 Merge pull request 'main' (#22) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 24s
Reviewed-on: #22
2026-04-24 10:00:23 +00:00
103511eadb Merge pull request 'arya-branch' (#21) from arya-branch into main
Reviewed-on: #21
2026-04-24 09:59:15 +00:00
aryabenade
8a462b599e change the border and field colors of recipient details in payment page 2026-04-24 15:11:44 +05:30
aryabenade
627137427d add proper navigations in melbourneAttractions and Profile Page 2026-04-24 14:51:27 +05:30
aryabenade
f01a5e0630 remove blank spaces 2026-04-24 14:41:58 +05:30
aryabenade
40ff761104 add navbar and footer in itinerary summary page 2026-04-24 14:23:24 +05:30
aryabenade
ff76d9a370 navigate to create-itinerary 2026-04-24 14:23:01 +05:30
aryabenade
ecd2fb2719 add optional operators to prevent crashing when no cards for a city 2026-04-24 13:52:56 +05:30
aryabenade
c0a2c448e5 replace melbourne with the selected cityname 2026-04-24 13:52:27 +05:30
a2e0eb5e14 Merge pull request 'main' (#20) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 22s
Reviewed-on: #20
2026-04-23 14:44:18 +00:00
aryabenade
191aa3fd54 Merge branch 'hemant' of http://git.wdipl.com/CityCards/CityCards-Website into arya-branch 2026-04-23 20:12:44 +05:30
1575690684 Merge pull request 'change card service again' (#19) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 21s
Reviewed-on: #19
2026-04-23 14:26:01 +00:00
Hemant Vishwakarma
6a23429131 change card service again 2026-04-23 19:55:21 +05:30
132ceccbdd Merge pull request 'change card service file' (#18) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 23s
Reviewed-on: #18
2026-04-23 14:21:57 +00:00
aryabenade
2c9bf7da83 show toast error when not able to create itinerary with the error msg from backend 2026-04-23 19:50:48 +05:30
Hemant Vishwakarma
8279715f2c change card service file 2026-04-23 19:50:43 +05:30
aryabenade
37601dd51d round off the basePrice and strikedPrice 2026-04-23 19:31:26 +05:30
aryabenade
16fe56913d debug the cityId issue in getting user itineraries 2026-04-23 19:29:18 +05:30
46f5533028 Merge pull request 'change payment success page and endpoint' (#17) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 22s
Reviewed-on: #17
2026-04-23 13:59:11 +00:00
Hemant Vishwakarma
a1a5c839dd change payment success page and endpoint 2026-04-23 19:28:13 +05:30
87a8749f10 Merge pull request 'Again rechange in payment success' (#16) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 23s
Reviewed-on: #16
2026-04-23 13:35:28 +00:00
Hemant Vishwakarma
d3bedeb56d Again rechange in payment success 2026-04-23 19:02:42 +05:30
c1b50de2b6 Merge pull request 'change payment success file and cards endpoints' (#15) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 23s
Reviewed-on: #15
2026-04-23 13:04:26 +00:00
Hemant Vishwakarma
cd7c6ffbaf change payment success file and cards endpoints 2026-04-23 18:33:33 +05:30
aryabenade
cdfcc70e45 integrate api to show itineraries in profile acc to the city selected 2026-04-23 18:24:34 +05:30
aryabenade
977eecd4d8 add indentation in register page 2026-04-23 17:51:12 +05:30
aryabenade
420d2038f2 add validations in register page 2026-04-23 17:48:48 +05:30
aryabenade
27382c45e3 round off the tax amount before adding to cart 2026-04-23 17:00:41 +05:30
aryabenade
0e8045d9a2 add navigation to the footer links 2026-04-23 16:50:09 +05:30
5efc22d150 Merge pull request 'main' (#14) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 25s
Reviewed-on: #14
2026-04-23 10:01:47 +00:00
b3bb9f08cd Merge pull request 'arya-branch' (#13) from arya-branch into main
Reviewed-on: #13
2026-04-23 10:01:05 +00:00
aryabenade
b1fb01a2a6 refactor the api call by passing cityId when getting user city cards 2026-04-23 15:25:52 +05:30
aryabenade
ef1408fe2a change the label of passes route to Buy Cards 2026-04-23 14:43:07 +05:30
aryabenade
23d1eaa43a add favicon to the app 2026-04-23 14:02:57 +05:30
aryabenade
cee671cf32 replace passes with cards in profile page 2026-04-23 13:56:30 +05:30
aryabenade
517a7c0446 remove the register button from logout modal 2026-04-23 13:34:58 +05:30
aryabenade
0d83118938 remove from local and session storage when signing out 2026-04-23 13:31:46 +05:30
aryabenade
0cb3d6c326 replace the registration modal with the registration page 2026-04-23 12:38:39 +05:30
aryabenade
0c7667bf26 remove console logs from checkout page 2026-04-23 00:13:37 +05:30
aryabenade
4c15fa597d remove the unused secure checkout file 2026-04-22 23:52:06 +05:30
aryabenade
617b494249 rename and replace the design files with actual page names 2026-04-22 23:49:59 +05:30
aryabenade
351c767104 replace the citySelected condition from local to session storage 2026-04-22 23:20:26 +05:30
2fa8f86d62 Merge pull request 'main' (#12) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 25s
Reviewed-on: #12
2026-04-22 14:26:44 +00:00
aryabenade
61f8202fe1 Merge branch 'main' of http://git.wdipl.com/CityCards/CityCards-Website 2026-04-22 19:55:31 +05:30
aryabenade
ef15941484 Merge branch 'hemant' of http://git.wdipl.com/CityCards/CityCards-Website into arya-branch 2026-04-22 19:54:37 +05:30
aryabenade
8f3698b41f show purchased card details using api in the profile page 2026-04-22 19:44:21 +05:30
aryabenade
beae316bc0 integrate api in viewIternary page 2026-04-22 18:33:09 +05:30
3ee552dccc Merge pull request 'main' (#11) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 23s
Reviewed-on: #11
2026-04-22 12:31:47 +00:00
Hemant Vishwakarma
16c6161520 Merge branch 'hemant' of http://git.wdipl.com/CityCards/CityCards-Website 2026-04-22 18:01:03 +05:30
Hemant Vishwakarma
03dabde979 New changes in paymentsuccesspage 2026-04-22 18:00:33 +05:30
11714bc1e4 Merge pull request 'main' (#10) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 22s
Reviewed-on: #10
2026-04-22 12:21:36 +00:00
Hemant Vishwakarma
d8d3ac1bfb Merge branch 'hemant' of http://git.wdipl.com/CityCards/CityCards-Website 2026-04-22 17:50:55 +05:30
Hemant Vishwakarma
b7767053e7 Stripe again changes 2026-04-22 17:50:30 +05:30
c9ed8f5628 Merge pull request 'main' (#9) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 21s
Reviewed-on: #9
2026-04-22 12:15:33 +00:00
Hemant Vishwakarma
690a59d8a9 Merge branch 'hemant' of http://git.wdipl.com/CityCards/CityCards-Website 2026-04-22 17:44:57 +05:30
Hemant Vishwakarma
043b575a7b change cancel page of stripe and navbar profile changes 2026-04-22 17:44:34 +05:30
Hemant Vishwakarma
65e7b53a84 Again changes in success page 2026-04-22 17:37:24 +05:30
473902a2ae Merge pull request 'main' (#8) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 22s
Reviewed-on: #8
2026-04-22 11:47:22 +00:00
Hemant Vishwakarma
1691396eea Merge branch 'hemant' of http://git.wdipl.com/CityCards/CityCards-Website 2026-04-22 17:16:32 +05:30
Hemant Vishwakarma
7455f8afb5 change stripe success page 2026-04-22 17:15:55 +05:30
aryabenade
adbf30a0c2 show the created itinerary summary on the summary page 2026-04-22 17:04:34 +05:30
fdd86a44b7 Merge pull request 'main' (#7) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 25s
Reviewed-on: #7
2026-04-22 11:23:27 +00:00
Hemant Vishwakarma
7394025add Merge branch 'hemant' of http://git.wdipl.com/CityCards/CityCards-Website 2026-04-22 16:52:16 +05:30
Hemant Vishwakarma
205b19ae50 Implement stripe and add success and cancel page 2026-04-22 16:51:15 +05:30
aryabenade
e3ee51f70b replace all the files in imports folder from the design code 2026-04-21 19:15:57 +05:30
aryabenade
7aa9f833c0 add the view card details design code 2026-04-21 19:14:39 +05:30
aryabenade
37f50275a3 integrate the show purchased passes api in the profile page 2026-04-21 19:14:21 +05:30
aryabenade
4c199f721a integrate the create iternary api in the CreateIternaryPage 2026-04-21 19:12:52 +05:30
943bdd6407 Merge pull request 'main' (#6) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 33s
Reviewed-on: #6
2026-04-14 12:25:05 +00:00
Hemant Vishwakarma
be70d1c65f Merge branch 'main' of http://git.wdipl.com/CityCards/CityCards-Website 2026-04-14 17:53:48 +05:30
Hemant Vishwakarma
0cb969df65 Merge branch 'arya-branch' of http://git.wdipl.com/CityCards/CityCards-Website 2026-04-14 17:53:43 +05:30
652d5e8a33 Merge pull request 'main' (#4) from main into testing
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 29s
Reviewed-on: #4
2026-04-02 06:40:37 +00:00
dfb1b83352 Merge pull request 'main branch merge to testing' (#1) from main into testing
Some checks failed
CityCards-Website / Build-CityCards-Website (push) Failing after 10s
Reviewed-on: #1
2026-03-24 07:22:43 +00:00
e4796f862d Add .gitea/workflows/deploy.yml
All checks were successful
CityCards-Website / Build-CityCards-Website (push) Successful in 24s
2026-03-20 09:54:23 +00:00
110 changed files with 9131 additions and 7905 deletions

View File

@@ -0,0 +1,67 @@
name: CityCards-Website
on:
push:
branches:
- main
- beta
- testing
- uat-beta
- staging
- production
jobs:
Build-CityCards-Website:
runs-on: ubuntu-latest
steps:
- name: Checkout Code in Runner
uses: actions/checkout@v3
- name: Branch and Folder Selection for Deployment
run: |
BRANCH_NAME=${{ gitea.ref_name }}
case $BRANCH_NAME in
#beta)
#echo "PROJECT_FOLDER=/home/citycards/citycards-superadmin" >> $GITHUB_ENV
#PROJECT_FOLDER="/home/citycards/citycards-superadmin"
#;;
testing)
echo "PROJECT_FOLDER=/home/citycards/Test-Release/citycards-frontend/CityCards-Website" >> $GITHUB_ENV
PROJECT_FOLDER="/home/citycards/Test-Release/citycards-frontend/CityCards-Website"
;;
#client)
#echo "PROJECT_FOLDER=/home/citycards/Client-Release/citycards-frontend/CityCards-AdminPanel" >> $GITHUB_ENV
#PROJECT_FOLDER="/home/citycards/Client-Release/citycards-frontend/CityCards-AdminPanel"
#;;
#uat-beta)
#echo "PROJECT_FOLDER=/home/citycards/UAT-Release/citycards-frontend/CityCards-AdminPanel" >> $GITHUB_ENV
#PROJECT_FOLDER="/home/citycards/UAT-Release/citycards-frontend/CityCards-AdminPanel"
#;;
*)
echo "Unknown Branch"
exit 1
;;
esac
echo "BRANCH_NAME=${{ gitea.ref_name }}" >> $GITHUB_ENV
echo "SELECTED BRANCH : $BRANCH_NAME"
echo "SELECTED FOLDER : $PROJECT_FOLDER"
- name: Deployment to Server SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.BETA_SERVER_HOST }}
username: ${{ secrets.BETA_SERVER_USERNAME }}
password: ${{ secrets.BETA_SERVER_PASSWORD }}
port: ${{ secrets.BETA_SERVER_PORT }}
envs: BRANCH_NAME,PROJECT_FOLDER
script: |
set -xe
echo $BRANCH_NAME
echo $PROJECT_FOLDER
cd $PROJECT_FOLDER
git fetch
git reset --hard origin/$BRANCH_NAME
git pull origin $BRANCH_NAME
echo "BUILDING..... "
npm i
npm run build

View File

@@ -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>

54
package-lock.json generated
View File

@@ -35,12 +35,15 @@
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@reduxjs/toolkit": "^2.11.2",
"@stripe/react-stripe-js": "^6.2.0",
"@stripe/stripe-js": "^9.2.0",
"@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/vite": "^4.1.14",
"class-variance-authority": "^0.7.1",
"clsx": "*",
"cmdk": "^1.1.1",
"embla-carousel-react": "^8.6.0",
"i18n-iso-countries": "^7.14.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.487.0",
"motion": "*",
@@ -2237,6 +2240,27 @@
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
"node_modules/@stripe/react-stripe-js": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-6.2.0.tgz",
"integrity": "sha512-GSCErjljZEQv9LaxP30xGOwstcMyyUzb5JyihXwvjOU95yrfhbiPG4K2KkwxYxn+WY0/AyHsRhPPoGRw7urBzg==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"@stripe/stripe-js": ">=9.2.0 <10.0.0",
"react": ">=16.8.0 <20.0.0",
"react-dom": ">=16.8.0 <20.0.0"
}
},
"node_modules/@stripe/stripe-js": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-9.2.0.tgz",
"integrity": "sha512-YSzLC0t6VS9MDdPTynSMqU8IxrItFUjkDORALFT6sSMR/XZ5Vgm3RDp/Gk7z727MC4A9s4MFVel0gF0c7+kdrg==",
"engines": {
"node": ">=12.16"
}
},
"node_modules/@swc/core": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz",
@@ -3073,7 +3097,6 @@
"integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -3084,7 +3107,6 @@
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -3095,7 +3117,6 @@
"integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.0.0"
}
@@ -3346,6 +3367,11 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
"node_modules/diacritics": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
"integrity": "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA=="
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -3360,8 +3386,7 @@
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/embla-carousel-react": {
"version": "8.6.0",
@@ -3527,6 +3552,17 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/i18n-iso-countries": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.14.0.tgz",
"integrity": "sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==",
"dependencies": {
"diacritics": "1.3.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/immer": {
"version": "11.1.4",
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
@@ -3945,7 +3981,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -4003,7 +4038,6 @@
"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"
},
@@ -4030,7 +4064,6 @@
"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"
@@ -4066,7 +4099,6 @@
"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"
@@ -4269,8 +4301,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -4533,7 +4564,6 @@
"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",

View File

@@ -30,12 +30,15 @@
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@reduxjs/toolkit": "^2.11.2",
"@stripe/react-stripe-js": "^6.2.0",
"@stripe/stripe-js": "^9.2.0",
"@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/vite": "^4.1.14",
"class-variance-authority": "^0.7.1",
"clsx": "*",
"cmdk": "^1.1.1",
"embla-carousel-react": "^8.6.0",
"i18n-iso-countries": "^7.14.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.487.0",
"motion": "*",

View File

@@ -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</>
) : (

View File

@@ -2,21 +2,17 @@ import { Routes, Route, useParams, useLocation, useNavigate } from 'react-router
import { motion, AnimatePresence } from 'motion/react';
// Import all your pages
import { ProtectedRoute } from './components/ProtectedRoute';
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 +20,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,9 +29,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 {
@@ -128,19 +129,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}>
@@ -191,20 +179,43 @@ export function AppRouter({
{/* User Routes */}
<Route path="/profile" element={
<motion.div key="profile" {...pageTransition}>
<ProfilePage {...commonNavHandlers} />
<ProtectedRoute>
<ProfilePage {...commonNavHandlers} />
</ProtectedRoute>
</motion.div>
} />
<Route path="/view-card-details/:cardId" element={
<motion.div key="profile" {...pageTransition}>
<ProtectedRoute>
<ViewCardDetailsPage {...commonNavHandlers} />
</ProtectedRoute>
</motion.div>
} />
{/* Itinerary Routes */}
<Route path="/create-itinerary" element={
<motion.div key="create-itinerary" {...pageTransition}>
<CreateMagicItineraryPage {...commonNavHandlers} />
<ProtectedRoute>
<CreateMagicItineraryPage {...commonNavHandlers} />
</ProtectedRoute>
</motion.div>
} />
<Route path="/itinerary-view" element={
<Route path="/view-itinerary/:itineraryId" element={
<motion.div key="itinerary-view" {...pageTransition}>
<ItineraryViewPage {...commonNavHandlers} />
<ProtectedRoute>
<ItineraryViewPage {...commonNavHandlers} />
</ProtectedRoute>
</motion.div>
} />
<Route path="/itinerary-summary/:itineraryId" element={
<motion.div key="itinerary-summary" {...pageTransition}>
<ProtectedRoute>
<ItinerarySummaryPage {...commonNavHandlers} />
</ProtectedRoute>
</motion.div>
} />
@@ -278,22 +289,30 @@ export function AppRouter({
<Route path="/cart" element={
<motion.div key="super-savings" {...pageTransition}>
<CartPage {...commonNavHandlers} />
<ProtectedRoute>
<CartPage {...commonNavHandlers} />
</ProtectedRoute>
</motion.div>
} />
<Route path="/checkout" element={
<motion.div key="super-savings" {...pageTransition}>
<CheckoutPage2 {...commonNavHandlers} />
<ProtectedRoute>
<CheckoutPage {...commonNavHandlers} />
</ProtectedRoute>
</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} />
<ProtectedRoute>
<PaymentDetailsPage {...commonNavHandlers} />
</ProtectedRoute>
</motion.div>
} />
<Route path="/super-savings/:id" element={
@@ -302,6 +321,38 @@ export function AppRouter({
onBackClick={() => navigate(-1)} />
</motion.div>
} />
<Route path="/success" element={
<motion.div key="super-savings" {...pageTransition}>
<ProtectedRoute>
<PaymentSuccessPage
// onHomeClick={onHomeClick}
// onPassesClick={onPassesClick}
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
currentPage="success"
user={user}
/>
</ProtectedRoute>
</motion.div>
} />
<Route path="/cancel" element={
<motion.div key="super-savings" {...pageTransition}>
<ProtectedRoute>
<PaymentCancelPage
// onHomeClick={onHomeClick}
// onPassesClick={onPassesClick}
onSignInClick={onSignInClick}
onSignOutClick={onSignOutClick}
currentPage="cancel"
user={user}
/>
</ProtectedRoute>
</motion.div>
} />
</Routes>
</AnimatePresence>
</>

View File

@@ -4,6 +4,8 @@ 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";
import { blogsApi } from "./services/blogs.service";
export const store = configureStore({
reducer: {
@@ -11,7 +13,9 @@ 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,
[blogsApi.reducerPath]:blogsApi.reducer
},
@@ -21,7 +25,9 @@ export const store = configureStore({
citiesApi.middleware,
authApi.middleware,
profileApi.middleware,
cardsApi.middleware
cardsApi.middleware,
itineraryApi.middleware,
blogsApi.middleware
),
});
export type RootState = ReturnType<typeof store.getState>;

View File

@@ -30,6 +30,20 @@ export const attractionsApi = createApi({
return `/attractions/customer/customer-attractions?${params.toString()}`;
},
}),
getAttractionsForHomePage: builder.query({
// cityId is required, others optional
query: ({ cityId, categoryId}) => {
const params = new URLSearchParams();
// required
params.append('cityXid', cityId);
// optional
if (categoryId) params.append('categoryXid', categoryId);
return `/attractions/list/city-attractions?${params.toString()}`;
},
}),
getAttractionDetailsById: builder.query({
query: (id: number) => `/attractions/customer/${id}`,
@@ -38,4 +52,4 @@ export const attractionsApi = createApi({
}),
});
export const { useGetAttractionFiltersQuery,useGetCustomerAttractionsQuery,useGetAttractionDetailsByIdQuery } = attractionsApi;
export const { useGetAttractionFiltersQuery,useGetCustomerAttractionsQuery,useGetAttractionDetailsByIdQuery,useGetAttractionsForHomePageQuery } = attractionsApi;

View File

@@ -0,0 +1,28 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { baseQuery } from "../baseQuery";
export const blogsApi = createApi({
reducerPath: 'blogsApi',
baseQuery,
endpoints: (builder) => ({
getBlogsForCity: builder.query({
// cityId is required, others optional
query: ({ cityId, categoryId }) => {
const params = new URLSearchParams();
// required
params.append('cityXid', cityId);
// optional
if (categoryId) params.append('categoryXid', categoryId);
return `/website/list/blogs?${params.toString()}`;
},
}),
}),
});
export const { useGetBlogsForCityQuery } = blogsApi;

View File

@@ -41,7 +41,28 @@ export const cardsApi = createApi({
body: cardBookingDetails
}),
}),
})
payForCard: builder.mutation({
query: (id) => ({
url: `/website/passes/${id}/pay`,
method: "POST",
body: {},
}),
}),
confirmCardPayment: builder.mutation({
query: (payload: { id: string; checkoutSessionId: string }) => ({
url: `/website/passes/${payload.id}/${payload.checkoutSessionId}/confirm-payment/`,
method: "POST",
}),
}),
}),
});
export const {
@@ -49,5 +70,8 @@ export const {
useGetCheckoutPageDataQuery,
useGetCardBookingDetailsQuery,
useStoreRecipientDetailsMutation,
useAddCardToCartMutation
useAddCardToCartMutation,
usePayForCardMutation,
useConfirmCardPaymentMutation
} = cardsApi;

View File

@@ -1,11 +1,8 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { createApi } from '@reduxjs/toolkit/query/react';
import { baseQuery } from "../baseQuery";
export const citiesApi = createApi({
reducerPath: 'citiesApi',
// baseQuery: fetchBaseQuery({
// baseUrl: 'https://testingapi.citycards.betadelivery.com',
// }),
baseQuery,
endpoints: (builder) => ({

View 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;

View File

@@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -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();
};

View File

@@ -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"
@@ -126,7 +126,7 @@ export function EnhancedTestimonials() {
style={{
transform: `rotate(${cardRotation}deg) translateY(${cardOffset}px)`,
transformOrigin: 'center center',
minHeight: '480px',
minHeight: '360px',
background: `
radial-gradient(circle at 20% 80%, rgba(255, 248, 235, 0.8) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(250, 245, 230, 0.6) 0%, transparent 50%),

View File

@@ -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">

View File

@@ -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">

View File

@@ -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>
))}

View File

@@ -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 Sydneys 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

View File

@@ -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"

File diff suppressed because it is too large Load Diff

View File

@@ -259,7 +259,7 @@ export function LandingBookAttractionSection() {
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={`px-6 py-4 h-14 rounded-full font-medium transition-all duration-300 ${activeCategory === category
? 'bg-warm-coral text-white shadow-xl shadow-warm-coral/25 ring-2 ring-warm-coral/20'
? 'bg-red-400 text-white shadow-xl shadow-warm-coral/25 ring-2 ring-warm-coral/20'
: 'bg-white/80 backdrop-blur-sm text-gray-700 hover:text-gray-900 hover:shadow-lg border border-gray-200/50 hover:border-warm-coral/20 hover:bg-white'
}`}
>

View File

@@ -6,6 +6,7 @@ import { Button } from './ui/button';
// Import your video from assets
import cityTourVideo from '../assets/itinenary-animation-vid.mp4';
import { useNavigate } from 'react-router-dom';
interface ItineraryCard {
id: number;
@@ -22,6 +23,8 @@ export function LandingMagicItinerary() {
const [isPlaying, setIsPlaying] = useState(true);
const [videoLoaded, setVideoLoaded] = useState(false);
const navigate = useNavigate()
const handleVideoLoad = () => {
setVideoLoaded(true);
};
@@ -31,7 +34,7 @@ export function LandingMagicItinerary() {
};
return (
<section className="relative py-20 lg:py-32 overflow-hidden -mt-20 pt-32 z-[49]">
<section className="relative py-20 lg:py-15 overflow-hidden -mt-20 z-[49]">
{/* Dynamic Background */}
<div className="absolute inset-0 overflow-hidden pointer-events-none z-[5]">
{/* Background Image as fallback */}
@@ -97,7 +100,7 @@ export function LandingMagicItinerary() {
{/* Header */}
<div className="text-center mb-16 max-w-5xl w-full">
<motion.div
className="inline-flex items-center gap-3 bg-gradient-to-r from-warm-coral/10 to-orange-100/50 backdrop-blur-sm px-6 py-3 rounded-full border-2 border-warm-coral/30 shadow-xl mb-8"
className="inline-flex items-center gap-3 bg-gradient-to-r from-warm-coral/10 to-orange-100/50 backdrop-blur-sm pl-6 py-3 rounded-full border-2 border-warm-coral/30 shadow-xl mb-8"
initial={{ opacity: 0, scale: 0.8, y: 20 }}
whileInView={{ opacity: 1, scale: 1, y: 0 }}
transition={{ duration: 0.7, ease: [0.34, 1.56, 0.64, 1] }}
@@ -112,7 +115,7 @@ export function LandingMagicItinerary() {
>
<Wand2 className="w-6 h-6 text-warm-coral drop-shadow-lg" />
</motion.div>
<span className="font-semibold text-gray-800">AI-Powered Magic Itinerary</span>
<span className="font-semibold text-gray-800">Magic Itinerary</span>
<motion.div
className="w-2 h-2 bg-warm-coral rounded-full"
animate={{
@@ -131,7 +134,7 @@ export function LandingMagicItinerary() {
viewport={{ once: true }}
>
<span className="font-light">Plan Your</span>{' '}
<span className="font-bold italic bg-gradient-to-r from-warm-coral via-orange-500 to-rose-500 bg-clip-text pr-2 text-transparent drop-shadow-lg">
<span className="font-bold italic bg-gradient-to-r from-red-500 via-orange-500 to-rose-500 bg-clip-text pr-2 text-transparent drop-shadow-lg">
Dream Journey
</span>
<br />
@@ -250,7 +253,8 @@ export function LandingMagicItinerary() {
>
<Button
withShine={true}
className="py-6 px-14 rounded-full text-lg font-bold bg-gradient-to-r from-warm-coral via-orange-500 to-rose-500 hover:from-warm-coral/90 hover:via-orange-500/90 hover:to-rose-500/90 shadow-2xl hover:shadow-warm-coral/50 transition-all hover:scale-105 hover:-translate-y-1"
onClick={() => navigate('/landing-magic-itinerary')}
className="py-6 px-14 rounded-full text-lg font-bold bg-gradient-to-r via-orange-500 to-rose-500 hover:from-warm-coral/90 hover:via-orange-500/90 hover:to-rose-500/90 shadow-2xl hover:shadow-warm-coral/50 transition-all hover:scale-105 hover:-translate-y-1"
>
<span className="flex items-center gap-3">
<Wand2 className="w-5 h-5" />
@@ -258,11 +262,11 @@ export function LandingMagicItinerary() {
</span>
</Button>
<p className="text-gray-600 text-sm flex items-center gap-2">
{/* <p className="text-gray-600 text-sm flex items-center gap-2">
<Sparkles className="w-4 h-4 text-warm-coral" />
<span>Free to use • No credit card required</span>
<Sparkles className="w-4 h-4 text-warm-coral" />
</p>
</p> */}
</motion.div>
</div>
</div>

View File

@@ -204,7 +204,7 @@ export function LandingTrustSection() {
style={{
transform: `rotate(${cardRotation}deg) translateY(${cardOffset}px)`,
transformOrigin: 'center center',
minHeight: '480px',
minHeight: '360px',
background: `
radial-gradient(circle at 20% 80%, rgba(255, 248, 235, 0.8) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(250, 245, 230, 0.6) 0%, transparent 50%),

View File

@@ -150,10 +150,10 @@ export function LandingVarietyOfAdventures() {
const extendedCategories = [...melbourneCategories, ...melbourneCategories, ...melbourneCategories];
return (
<section className="py-20 lg:py-28 bg-white overflow-hidden">
<section className="lg: bg-white overflow-hidden">
<div className="container mx-auto px-4">
{/* Header */}
<div className="text-center mb-16 max-w-4xl mx-auto">
<div className="text-center mb-2 max-w-4xl mx-auto">
<motion.h2
className="font-poppins text-2xl md:text-3xl lg:text-4xl leading-tight text-foreground mb-6"
initial={{ opacity: 0, y: 30 }}

View File

@@ -1,3 +1,4 @@
// LoginModal.tsx
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { X } from 'lucide-react';
@@ -7,7 +8,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 +22,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 +148,22 @@ 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")
navigate("/passes")
}
onClose();
} catch (err: any) {
setError(err?.data?.message || 'Invalid OTP. Please try again.');
@@ -232,14 +240,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 +315,6 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
)
}
</AnimatePresence >
<RegisterModal
isOpen={showRegisterModal}
onClose={() => setShowRegisterModal(false)}
onLoginClick={() => {
setShowRegisterModal(false);
setStep('email');
setEmail('');
}}
/>
</>
);
}

View File

@@ -2,269 +2,168 @@ 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';
const melbourneAttractions = [
{
id: 1,
name: "Royal Botanic Gardens",
city: "Melbourne",
country: "Australia",
image: "https://images.unsplash.com/photo-1721272962395-a848331ce92d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByb3lhbCUyMGJvdGFuaWMlMjBnYXJkZW5zfGVufDF8fHx8MTc1NzMzNzc4OXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
rating: 4.8,
reviews: "15,600+",
category: "Gardens",
originalPrice: "Free",
includedValue: "$25",
perks: [
{ icon: Volume2, label: "Audio garden tour", color: "text-green-600" },
{ icon: MapPin, label: "Garden maps", color: "text-blue-600" },
{ icon: Camera, label: "Photo spots guide", color: "text-purple-600" }
]
},
{
id: 2,
name: "Federation Square",
city: "Melbourne",
country: "Australia",
image: "https://images.unsplash.com/photo-1639655001512-e4b58d4874b8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBmZWRlcmF0aW9uJTIwc3F1YXJlfGVufDF8fHx8MTc1NzMzNzc5Mnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
rating: 4.6,
reviews: "22,400+",
category: "Landmarks",
originalPrice: "Free",
includedValue: "$35",
perks: [
{ icon: Volume2, label: "Cultural tours", color: "text-orange-600" },
{ icon: Eye, label: "Gallery access", color: "text-blue-600" },
{ icon: Users, label: "Event access", color: "text-purple-600" }
]
},
{
id: 3,
name: "Queen Victoria Market",
city: "Melbourne",
country: "Australia",
image: "https://images.unsplash.com/photo-1676454953709-e0be46f62490?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBxdWVlbiUyMHZpY3RvcmlhJTIwbWFya2V0fGVufDF8fHx8MTc1NzMzNzc5Nnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
rating: 4.7,
reviews: "18,200+",
category: "Markets",
originalPrice: "$45",
includedValue: "$45",
perks: [
{ icon: Users, label: "Food tours", color: "text-orange-600" },
{ icon: Coffee, label: "Tastings", color: "text-brown-600" },
{ icon: Volume2, label: "History guide", color: "text-blue-600" }
]
},
{
id: 4,
name: "Eureka Skydeck",
city: "Melbourne",
country: "Australia",
image: "https://images.unsplash.com/photo-1629677713183-29248e1268d7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBldXJla2ElMjB0b3dlcnxlbnwxfHx8fDE3NTczMzc4MDB8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
rating: 4.9,
reviews: "11,800+",
category: "Views",
originalPrice: "$32",
includedValue: "$32",
perks: [
{ icon: Zap, label: "Skip-the-line", color: "text-green-600" },
{ icon: Eye, label: "360° views", color: "text-purple-600" },
{ icon: Camera, label: "Photo experiences", color: "text-blue-600" }
]
},
{
id: 5,
name: "St Kilda Beach & Pier",
city: "Melbourne",
country: "Australia",
image: "https://images.unsplash.com/photo-1674732954456-159835c0a46b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBzdCUyMGtpbGRhJTIwYmVhY2h8ZW58MXx8fHwxNzU3MzM3ODAzfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
rating: 4.5,
reviews: "14,300+",
category: "Beach",
originalPrice: "Free",
includedValue: "$20",
perks: [
{ icon: Users, label: "Penguin tours", color: "text-blue-600" },
{ icon: MapPin, label: "Beach activities", color: "text-green-600" },
{ icon: Camera, label: "Sunset spots", color: "text-purple-600" }
]
},
{
id: 6,
name: "Melbourne Laneways",
city: "Melbourne",
country: "Australia",
image: "https://images.unsplash.com/photo-1705120624704-0970afc29fea?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjBsYW5ld2F5cyUyMHN0cmVldCUyMGFydHxlbnwxfHx8fDE3NTczMzc4MDd8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
rating: 4.8,
reviews: "19,500+",
category: "Street Art",
originalPrice: "$55",
includedValue: "$55",
perks: [
{ icon: Palette, label: "Art tours", color: "text-pink-600" },
{ icon: Coffee, label: "Café stops", color: "text-brown-600" },
{ icon: Camera, label: "Photo walks", color: "text-purple-600" }
]
},
{
id: 7,
name: "Melbourne Zoo",
city: "Melbourne",
country: "Australia",
image: "https://images.unsplash.com/photo-1681429477985-30dc7b88dd5b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjB6b28lMjBhbmltYWxzfGVufDF8fHx8MTc1NzMzNzgxMHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
rating: 4.7,
reviews: "13,900+",
category: "Wildlife",
originalPrice: "$42",
includedValue: "$42",
perks: [
{ icon: Zap, label: "Skip-the-line", color: "text-green-600" },
{ icon: Users, label: "Animal encounters", color: "text-orange-600" },
{ icon: Volume2, label: "Keeper talks", color: "text-blue-600" }
]
},
{
id: 8,
name: "Royal Exhibition Building",
city: "Melbourne",
country: "Australia",
image: "https://images.unsplash.com/photo-1720523794299-c3b445d71a51?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtZWxib3VybmUlMjByb3lhbCUyMGV4aGliaXRpb24lMjBidWlsZGluZ3xlbnwxfHx8fDE3NTczMzc4MTR8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral",
rating: 4.6,
reviews: "8,700+",
category: "Heritage",
originalPrice: "$25",
includedValue: "$25",
perks: [
{ icon: Volume2, label: "Audio tours", color: "text-blue-600" },
{ icon: Eye, label: "Exhibitions", color: "text-purple-600" },
{ icon: MapPin, label: "Heritage walks", color: "text-green-600" }
]
}
];
const categories = ["All", "Landmarks", "Gardens", "Markets", "Views", "Beach", "Street Art", "Wildlife", "Heritage"];
import { useNavigate } from 'react-router-dom';
import { useGetAttractionsForHomePageQuery } from '../Redux/services/attractions.service';
export function MelbourneAttractions() {
const [activeCategory, setActiveCategory] = useState("All");
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null);
const navigate = useNavigate();
const cityName = localStorage.getItem("cityName");
const cityId = localStorage.getItem("cityId");
const filteredAttractions = activeCategory === "All"
? melbourneAttractions
: melbourneAttractions.filter(attraction => attraction.category === activeCategory);
const { data: homePageAttractionsData } = useGetAttractionsForHomePageQuery({ cityId });
const AttractionCard = ({ attraction, index }: { attraction: typeof melbourneAttractions[0], index: number }) => (
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
viewport={{ once: true }}
className="group cursor-pointer flex-shrink-0 w-[280px] md:w-auto md:flex-shrink h-96 flip-card-container"
>
{/* 3D Flip Container */}
<div className="flip-card-inner group-hover:[transform:rotateY(180deg)] relative w-full h-full">
{/* FRONT FACE */}
<div className="flip-card-face absolute inset-0 w-full h-full rounded-2xl overflow-hidden shadow-lg">
{/* Background Image */}
<ImageWithFallback
src={attraction.image}
alt={attraction.name}
className="w-full h-full object-cover"
/>
const apiAttractions = homePageAttractionsData?.attractions || [];
const apiCategories = homePageAttractionsData?.categories || [];
{/* Rating Badge */}
{/* <div className="absolute top-4 right-4 bg-white/95 backdrop-blur-sm rounded-full px-3 py-1.5 flex items-center gap-1 shadow-lg z-10">
<div className="w-4 h-4 bg-gradient-to-r from-yellow-400 to-yellow-500 rounded-full flex items-center justify-center">
<span className="text-white text-xs">★</span>
</div>
<span className="text-sm font-medium text-gray-900">{attraction.rating}</span>
</div> */}
// Filter attractions by selected category
const filteredAttractions = selectedCategoryId === null
? apiAttractions
: apiAttractions.filter((attraction: any) =>
attraction.categories?.some((cat: any) => cat.id === selectedCategoryId)
);
{/* Front Content - Clean Title & Location */}
<div className="absolute bottom-0 left-0 right-0">
<div className="bg-gradient-to-t from-black/80 via-black/50 to-transparent p-6">
<h3 className="font-bold text-xl text-white mb-1">{attraction.name}</h3>
<p className="text-white/90 text-sm">
{attraction.city}, {attraction.country}
</p>
const AttractionCard = ({ attraction, index }: { attraction: any; index: number }) => {
// Get cover image or first image from galleries
const coverImage = attraction.galleries?.find((g: any) => g.isCoverImage)?.filePathUrl
|| attraction.galleries?.[0]?.filePathUrl
|| '';
// Filter only inclusions (isInclusion: true)
const inclusions = attraction.inclusions?.filter((inc: any) => inc.isInclusion) || [];
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
viewport={{ once: true }}
className="group cursor-pointer flex-shrink-0 w-[280px] md:w-auto md:flex-shrink h-96 flip-card-container"
>
<div className="flip-card-inner group-hover:[transform:rotateY(180deg)] relative w-full h-full">
{/* FRONT FACE */}
<div className="flip-card-face absolute inset-0 w-full h-full rounded-2xl overflow-hidden shadow-lg">
<ImageWithFallback
src={coverImage}
alt={attraction.title}
className="w-full h-full object-cover"
/>
<div className="absolute bottom-0 left-0 right-0">
<div className="bg-gradient-to-t from-black/80 via-black/50 to-transparent p-6">
<h3 className="font-bold text-xl text-white mb-1">{attraction.title}</h3>
<p className="text-white/90 text-sm">{attraction.city?.cityName}, Australia</p>
</div>
</div>
</div>
</div>
{/* BACK FACE */}
<div className="flip-card-face flip-card-back absolute inset-0 w-full h-full rounded-2xl overflow-hidden shadow-lg bg-gradient-to-br from-gray-900 to-black">
{/* Back Content Container */}
<div className="relative w-full h-full p-6 flex flex-col justify-center text-white">
{/* Included Value Section */}
<div className="mb-4">
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-green-500 to-emerald-600 text-white px-3 py-1.5 rounded-full text-sm font-medium mb-3">
<CheckCircle className="w-4 h-4" />
<span>Included Value</span>
{/* BACK FACE */}
<div className="flip-card-face flip-card-back absolute inset-0 w-full h-full rounded-2xl overflow-hidden shadow-lg bg-gradient-to-br from-gray-900 to-black">
<div className="relative w-full h-full p-6 flex flex-col justify-center text-white">
{/* Pricing Section */}
<div className="mb-4">
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-green-500 to-emerald-600 text-white px-3 py-1.5 rounded-full text-sm font-medium mb-3">
<CheckCircle className="w-4 h-4" />
<span>Included Value</span>
</div>
<div className="text-2xl font-bold mb-1">
${attraction.ticketPriceAdult}
{attraction.ticketPriceChild && (
<span className="text-sm font-normal text-white/70 ml-2">
/ Child ${attraction.ticketPriceChild}
</span>
)}
</div>
<p className="text-white/80 text-sm">
{attraction.isBookingRequired ? 'Booking required' : 'No booking required'}
</p>
</div>
<div className="text-2xl font-bold mb-1">{attraction.includedValue}</div>
<p className="text-white/80 text-sm">
{attraction.originalPrice === "Free"
? "Premium access included"
: "Save money with CityCard"}
</p>
</div>
{/* What's Included List */}
<div className="mb-4">
<h4 className="font-semibold text-sm mb-3">What's Included:</h4>
<div className="space-y-2">
{attraction.perks.slice(0, 3).map((perk, perkIndex) => (
<div key={perkIndex} className="flex items-center gap-3 text-white/90">
<div className="w-6 h-6 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center">
<perk.icon className="w-3 h-3 text-white" />
</div>
<span className="text-sm">{perk.label}</span>
{/* Inclusions List */}
{inclusions.length > 0 && (
<div className="mb-4">
<h4 className="font-semibold text-sm mb-3">What's Included:</h4>
<div className="space-y-2">
{inclusions.slice(0, 3).map((inc: any) => (
<div key={inc.id} className="flex items-center gap-3 text-white/90">
<div className="w-6 h-6 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center flex-shrink-0">
<CheckCircle className="w-3 h-3 text-white" />
</div>
<span className="text-sm">{inc.title}</span>
</div>
))}
</div>
))}
</div>
</div>
{/* Duration & Meta Info */}
<div className="mb-4">
<div className="flex items-center gap-4 text-white/80 text-sm">
<div className="flex items-center gap-1">
<Clock className="w-4 h-4" />
<span>2-3 hours</span>
</div>
<div className="flex items-center gap-1">
<Users className="w-4 h-4" />
<span>All ages</span>
)}
{/* Duration & Group Info */}
<div className="mb-4">
<div className="flex items-center gap-4 text-white/80 text-sm">
{attraction.durations && (
<div className="flex items-center gap-1">
<Clock className="w-4 h-4" />
<span>{attraction.durations} mins</span>
</div>
)}
{attraction.groupSize && (
<div className="flex items-center gap-1">
<Users className="w-4 h-4" />
<span>Max {attraction.groupSize}</span>
</div>
)}
{attraction.ageRange && (
<div className="flex items-center gap-1">
<Star className="w-4 h-4" />
<span>{attraction.ageRange}</span>
</div>
)}
</div>
</div>
</div>
{/* Footer Features */}
<div className="border-t border-white/20 pt-4">
<div className="flex items-center justify-between text-white/80 text-xs">
<div className="flex items-center gap-2">
<MapPin className="w-3 h-3" />
<span>Mobile ticket</span>
{/* Categories */}
{attraction.categories?.length > 0 && (
<div className="flex flex-wrap gap-1 mb-4">
{attraction.categories.slice(0, 2).map((cat: any) => (
<span
key={cat.id}
className="text-xs bg-white/20 text-white/90 px-2 py-0.5 rounded-full"
>
{cat.categoryName}
</span>
))}
</div>
<div className="flex items-center gap-2">
<CheckCircle className="w-3 h-3" />
<span>Instant confirmation</span>
)}
{/* Footer */}
<div className="border-t border-white/20 pt-4">
<div className="flex items-center justify-between text-white/80 text-xs">
<div className="flex items-center gap-2">
<MapPin className="w-3 h-3" />
<span>Mobile ticket</span>
</div>
<div className="flex items-center gap-2">
<CheckCircle className="w-3 h-3" />
<span>Instant confirmation</span>
</div>
</div>
</div>
</div>
{/* Decorative Elements */}
<div className="absolute top-4 right-4 w-16 h-16 bg-gradient-to-br from-primary/20 to-secondary/20 rounded-full blur-xl"></div>
<div className="absolute bottom-4 left-4 w-12 h-12 bg-gradient-to-tr from-secondary/15 to-primary/15 rounded-full blur-lg"></div>
{/* Decorative Elements */}
<div className="absolute top-4 right-4 w-16 h-16 bg-gradient-to-br from-primary/20 to-secondary/20 rounded-full blur-xl"></div>
<div className="absolute bottom-4 left-4 w-12 h-12 bg-gradient-to-tr from-secondary/15 to-primary/15 rounded-full blur-lg"></div>
</div>
</div>
</div>
</div>
</motion.div>
);
</div>
</motion.div>
);
};
return (
<section className="py-20 bg-gradient-to-br from-gray-50 to-white relative overflow-hidden">
<div className="container mx-auto px-4">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 30 }}
@@ -276,19 +175,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>
@@ -300,23 +199,41 @@ export function MelbourneAttractions() {
viewport={{ once: true }}
className="flex flex-wrap justify-center gap-3 mb-12"
>
{categories.map((category, index) => (
{/* "All" button */}
<motion.button
initial={{ opacity: 0, scale: 0.8 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
onClick={() => setSelectedCategoryId(null)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={`px-6 py-4 h-14 rounded-2xl font-medium transition-all duration-300 ${
selectedCategoryId === null
? 'bg-gradient-to-r from-primary to-secondary text-white shadow-xl shadow-primary/25 ring-2 ring-primary/20'
: 'bg-white/80 backdrop-blur-sm text-gray-700 hover:text-gray-900 hover:shadow-lg border border-gray-200/50 hover:border-primary/20 hover:bg-white'
}`}
>
All
</motion.button>
{/* Dynamic category buttons from API */}
{apiCategories.map((category: any, index: number) => (
<motion.button
key={category}
key={category.id}
initial={{ opacity: 0, scale: 0.8 }}
whileInView={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.3, delay: index * 0.05 }}
viewport={{ once: true }}
onClick={() => setActiveCategory(category)}
onClick={() => setSelectedCategoryId(category.id)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={`px-6 py-4 h-14 rounded-2xl font-medium transition-all duration-300 ${
activeCategory === category
selectedCategoryId === category.id
? 'bg-gradient-to-r from-primary to-secondary text-white shadow-xl shadow-primary/25 ring-2 ring-primary/20'
: 'bg-white/80 backdrop-blur-sm text-gray-700 hover:text-gray-900 hover:shadow-lg border border-gray-200/50 hover:border-primary/20 hover:bg-white'
}`}
>
{category}
{category.categoryName}
</motion.button>
))}
</motion.div>
@@ -324,52 +241,44 @@ export function MelbourneAttractions() {
{/* Mobile Horizontal Carousel */}
<div className="block md:hidden mb-8">
<div className="relative">
{/* Scroll Container */}
<div className="flex gap-6 overflow-x-auto scrollbar-hide pb-4 px-4 -mx-4">
{filteredAttractions.map((attraction, index) => (
{filteredAttractions.map((attraction: any, index: number) => (
<AttractionCard key={attraction.id} attraction={attraction} index={index} />
))}
</div>
{/* Scroll Indicators */}
<div className="flex justify-center mt-6 gap-2">
{Array.from({ length: Math.ceil(filteredAttractions.length / 2) }).map((_, index) => (
<div
key={index}
className="w-2 h-2 rounded-full bg-gray-300"
/>
{Array.from({ length: Math.ceil(filteredAttractions.length / 2) }).map((_: any, index: number) => (
<div key={index} className="w-2 h-2 rounded-full bg-gray-300" />
))}
</div>
{/* Mobile Hint Text */}
<div className="text-center mt-4">
<p className="text-sm text-gray-500">
Swipe to explore more Melbourne attractions
</p>
<p className="text-sm text-gray-500">Swipe to explore more {cityName} attractions</p>
</div>
</div>
</div>
{/* Desktop Bento Grid */}
<div className="hidden md:block w-full">
{/* Top Row - 3 equal cards */}
<div className="grid grid-cols-3 gap-6">
{filteredAttractions.slice(0, 3).map((attraction, index) => (
{filteredAttractions.slice(0, 3).map((attraction: any, index: number) => (
<AttractionCard key={attraction.id} attraction={attraction} index={index} />
))}
</div>
{/* Consistent Vertical Spacing */}
<div className="h-6"></div>
{/* Bottom Row - 2 larger cards */}
<div className="grid grid-cols-2 gap-6">
{filteredAttractions.slice(3, 5).map((attraction, index) => (
{filteredAttractions.slice(3, 5).map((attraction: any, index: number) => (
<AttractionCard key={attraction.id} attraction={attraction} index={index + 3} />
))}
</div>
</div>
{/* Empty State */}
{filteredAttractions.length === 0 && (
<div className="text-center py-16 text-gray-500">
<p className="text-lg">No attractions found for this category.</p>
</div>
)}
{/* Call to Action */}
<motion.div
initial={{ opacity: 0, y: 30 }}
@@ -382,15 +291,15 @@ 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>
{/* Shine animation */}
<span className="relative z-10">Get Your {cityName} Card</span>
<div className="absolute inset-0 opacity-30">
<div className="h-full bg-gradient-to-r from-transparent via-white to-transparent animate-shine"></div>
</div>
</motion.button>
</motion.div>
</div>
</section>
);

View File

@@ -2,6 +2,9 @@ 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, useState } from "react";
import { useNavigate } from 'react-router-dom';
import { useGetBlogsForCityQuery } from '../Redux/services/blogs.service';
const blogPosts = [
{
@@ -46,7 +49,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 +91,31 @@ const categories = [
];
export function MelbourneBlogs() {
const sectionRef = useRef(null);
const navigate = useNavigate();
const cityId = localStorage.getItem('cityId');
const [categoryId, setCategoryId] = useState("");
const { data: blogsData, error, isLoading } = useGetBlogsForCityQuery({ cityId, categoryId });
const featuredPost = blogPosts.find(post => post.featured);
const regularPosts = blogPosts.filter(post => !post.featured);
const cityName = localStorage.getItem('cityName');
const baseUrl = import.meta.env.VITE_BASE_URL;
const blogss = blogsData?.blogs ?? [];
const categoriess = blogsData?.categories ?? []
const handleCategoryClick = (id: string) => {
// toggle logic: if already selected, reset to empty
setCategoryId(prev => (prev === id ? "" : id));
};
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 +132,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>
@@ -136,29 +158,42 @@ export function MelbourneBlogs() {
viewport={{ once: true }}
className="flex flex-wrap justify-center gap-3 mb-16"
>
{categories.map((category, index) => (
<motion.button
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setCategoryId("")}
className={`cursor-pointer px-6 py-3 rounded-full font-medium shadow-lg hover:shadow-xl transition-all duration-300 group
${categoryId === "" ? "bg-gradient-to-r from-primary to-secondary text-white" : "bg-white text-gray-700"}`}
>
<span className="flex items-center gap-2">All</span>
</motion.button>
{categoriess.map((category: any, index) => (
<motion.button
key={category.name}
key={category.id}
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.2 + index * 0.05 }}
viewport={{ once: true }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={`px-6 py-3 rounded-full bg-gradient-to-r ${category.color} text-white font-medium shadow-lg hover:shadow-xl transition-all duration-300 group`}
onClick={() => handleCategoryClick(category.id)}
className={`cursor-pointer px-6 py-3 rounded-full font-medium shadow-lg hover:shadow-xl transition-all duration-300 group
${categoryId === category.id ? "bg-gradient-to-r from-primary to-secondary text-white" : "bg-white text-gray-700"}`}
>
<span className="flex items-center gap-2">
{category.name}
<span className="text-xs bg-white/20 px-2 py-1 rounded-full group-hover:bg-white/30 transition-colors duration-200">
{category.count}
</span>
{category.categoryName}
</span>
</motion.button>
))}
</motion.div>
{/* Featured Post */}
{featuredPost && (
{/* {featuredPost && (
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
@@ -168,13 +203,13 @@ export function MelbourneBlogs() {
>
</motion.div>
)}
)} */}
{/* Regular Blog Posts Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{regularPosts.map((post, index) => (
{blogss && blogss?.map((blog: any, index) => (
<motion.article
key={post.id}
key={blog.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 + index * 0.1 }}
@@ -184,47 +219,52 @@ export function MelbourneBlogs() {
{/* Post Image */}
<div className="relative overflow-hidden h-48">
<ImageWithFallback
src={post.image}
alt={post.title}
src={`${baseUrl}${blog?.coverImage}`}
alt={blog?.blogTitle}
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}
{blog?.category?.categoryName}
</div>
</div>
{/* Post Content */}
<div className="p-6 flex-1 flex flex-col justify-between">
<div className="flex items-center gap-3 text-xs text-gray-500 mb-3">
<div className="flex items-center gap-1">
{/* <div className="flex items-center gap-1">
<User className="w-3 h-3" />
{post.author}
</div>
{blog?.author}
</div> */}
<div className="flex items-center gap-1">
<Calendar className="w-3 h-3" />
{post.date}
{blog?.createdAt && new Date(blog.createdAt).toLocaleDateString(
'en-US',
{ month: 'short', day: 'numeric', year: 'numeric' }
)}
</div>
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{post.readTime}
5 min read
</div>
</div>
<div className="flex-1 flex flex-col">
<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}
{blog?.blogTitle}
</h3>
<p className="text-gray-600 leading-relaxed mb-4 text-sm flex-1 line-clamp-3">
{post.excerpt}
</p>
<p
className="text-gray-600 leading-relaxed mb-4 text-sm flex-1 line-clamp-3"
dangerouslySetInnerHTML={{ __html: blog?.content }}
/>
{/* Tags */}
<div className="flex flex-wrap gap-1 mb-4">
{post.tags.slice(0, 2).map((tag, tagIndex) => (
{/* <div className="flex flex-wrap gap-1 mb-4">
{blog?.tags?.slice(0, 2).map((tag, tagIndex) => (
<span
key={tagIndex}
className="px-2 py-1 bg-gray-100 text-gray-600 rounded-full text-xs font-medium"
@@ -232,12 +272,12 @@ export function MelbourneBlogs() {
{tag}
</span>
))}
{post.tags.length > 2 && (
{blog?.tags?.length > 2 && (
<span className="px-2 py-1 bg-gray-100 text-gray-600 rounded-full text-xs">
+{post.tags.length - 2}
+{blog?.tags?.length - 2}
</span>
)}
</div>
</div> */}
</div>
<div className="flex items-center justify-between mt-auto">
@@ -261,23 +301,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

View File

@@ -2,8 +2,8 @@ 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 = [
// {
// id: 'selective',
// name: 'Flexi Card',
@@ -76,7 +76,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 +182,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 Sydneys worldfamous 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 +267,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>

View File

@@ -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>
);

View File

@@ -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 Sydneys iconic harbour lifestyle with Opera House and Harbour Bridge views",
color: "text-yellow-600"
},
{
icon: Coffee,
text: "Discover Sydneys 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 Sydneys 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 Australias most dynamic harbor cityall 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 }}

View File

@@ -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,14 +90,12 @@ 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;
const protectedPaths = ["/passes", "/whats-included", "/", "/melbourne"];
const handleOpenLoginModal = () => {
if (!user && protectedPaths.includes(location.pathname)) {
if (!user) {
setLoginOpen(true);
}
else if (!user) {
@@ -126,26 +118,17 @@ export default function Navbar({
melbourneLabel: 'How It Works'
},
// Position 2
{
label: 'Magic Itinerary',
path: '/landing-magic-itinerary',
isShared: false
},
// Position 3
{
label: 'Whats Included',
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: 'Magic Itinerary',
path: '/landing-magic-itinerary',
isShared: false
},
{
label: 'FAQ',
path: '/faq',
@@ -159,7 +142,7 @@ export default function Navbar({
melbourneLabel: 'Your Postcard'
}
],
melbourne: [
citySelected: [
// Position 1
{
label: 'Attractions',
@@ -188,11 +171,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 +223,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 +244,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 +353,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 +380,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]);
@@ -546,7 +501,7 @@ export default function Navbar({
>
<div className="">
<motion.div
className={`w-full transition-all duration-500 ease-out px-8 py-4 bg-white backdrop-blur-[20px] border border-white/20 ${isScrolled
className={`w-full transition-all duration-500 ease-out px-3 py-3 bg-white backdrop-blur-[20px] border border-white/20 ${isScrolled
? 'shadow-[0_10px_15px_-3px_rgba(0,0,0,0.08),0_4px_6px_-2px_rgba(0,0,0,0.05)]'
: 'shadow-lg shadow-black/5'
}`}
@@ -572,7 +527,7 @@ export default function Navbar({
? 'Melbourne CityCards Logo'
: 'CityCards Logo'
}
className="h-14 w-auto"
className="h-17 w-auto"
/>
</Link>
</motion.div>
@@ -656,27 +611,7 @@ export default function Navbar({
}
/>
{/* Shopping Cart */}
{/* <Dropdown
ref={cartRef}
isOpen={activeCartDropdown}
onToggle={() => setActiveCartDropdown(prev => !prev)}
items={cartDropdownItems}
title="Shopping Cart"
trigger={
<div className="relative text-gray-700 hover:text-gray-900 transition-colors duration-200 rounded-lg hover:bg-gray-50/50 cursor-pointer p-2">
<ShoppingBag className="w-6 h-6" />
<motion.div
className="absolute -top-1 -right-1 w-5 h-5 bg-primary rounded-full flex items-center justify-center"
animate={{ scale: [1, 1.1, 1] }}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
>
<span className="text-xs text-primary-foreground font-bold">{cartItems.length}</span>
</motion.div>
</div>
}
/> */}
<ShoppingBag className="w-6 h-6" onClick={() => navigate("/cart")} />
<ShoppingBag className="w-6 h-6 cursor-pointer" onClick={() => navigate("/cart")} />
{/* Enhanced City Card Button with Source Tracking */}
<div className="flex items-center gap-3 pl-2">
@@ -692,7 +627,8 @@ export default function Navbar({
label: 'My Profile',
icon: <User className="w-4 h-4" />,
action: () => {
navigate(citySelected ? `/${slugify(cityName)}/profile` : '/profile');
// navigate(citySelected ? `/${slugify(cityName)}/profile` : '/profile');
navigate(citySelected ? `/profile` : '/profile');
setActiveUserDropdown(false);
}
},

View File

@@ -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,
@@ -71,6 +73,7 @@ export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTou
const nextCard = attractionCards[(currentCardIndex + 1) % attractionCards.length];
const thirdCard = attractionCards[(currentCardIndex + 2) % attractionCards.length];
const cityName = localStorage.getItem("cityName")
return (
<div className="relative w-full min-h-[90vh] overflow-hidden flex items-center bg-gradient-to-br from-orange-50 via-white to-rose-50">
{/* Gradient Background Elements */}
@@ -107,7 +110,7 @@ export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTou
>
<Wand2 className="w-5 h-5 text-primary drop-shadow-lg" />
</motion.div>
<span className="font-poppins font-semibold text-gray-800">AI-Powered Planning</span>
<span className="font-poppins font-semibold text-gray-800">Smart Planning</span>
<motion.div
className="w-1.5 h-1.5 bg-primary rounded-full"
animate={{
@@ -128,14 +131,14 @@ export function PersonalizedTourHero({ onCreateItineraryClick }: PersonalizedTou
</h1>
<p className="font-poppins text-lg md:text-xl font-normal leading-relaxed text-gray-600 mb-8">
Let AI craft a personalized journey tailored to your interests, timeline, and travel style. Get the perfect itinerary in minutes.
Craft a personalized journey tailored to your interests, timeline, and travel style. Get the perfect itinerary in minutes.
</p>
{/* Quick Features */}
<div className="space-y-3 mb-8">
{[
{ icon: <Sparkles className="w-5 h-5" />, text: 'AI-powered smart suggestions' },
{ icon: <MapPin className="w-5 h-5" />, text: '40+ top Melbourne attractions' },
{ icon: <Sparkles className="w-5 h-5" />, text: 'Smart suggestions' },
{ icon: <MapPin className="w-5 h-5" />, text: `40+ top ${cityName} attractions` },
{ icon: <Calendar className="w-5 h-5" />, text: 'Flexible & customizable plans' }
].map((feature, index) => (
<motion.div
@@ -163,18 +166,18 @@ 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" />
<span className="relative z-10">Create My Itinerary</span>
</button>
<p className="font-poppins text-sm text-gray-600 font-normal flex items-center gap-2">
{/* <p className="font-poppins text-sm text-gray-600 font-normal flex items-center gap-2">
<Sparkles className="w-4 h-4 text-primary" />
<span>Free • Takes less than 2 minutes</span>
</p>
</p> */}
</motion.div>
</motion.div>

View File

@@ -0,0 +1,29 @@
// ProtectedRoute.tsx
import { useState } from 'react';
import { useAuth } from '../context/AuthContext';
import { LoginModal } from './LoginModal';
import { useNavigate } from 'react-router-dom';
interface ProtectedRouteProps {
children: React.ReactNode;
}
export function ProtectedRoute({ children }: ProtectedRouteProps) {
const { user } = useAuth();
const navigate = useNavigate();
const [isLoginOpen, setIsLoginOpen] = useState(!user);
if (!user) {
return (
<LoginModal
isOpen={isLoginOpen}
onClose={() => {
setIsLoginOpen(false);
navigate(-1);
}}
/>
);
}
return <>{children}</>;
}

View File

@@ -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>
);
}

View File

@@ -0,0 +1,403 @@
//RegisterPage.tsx
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';
import { useEffect } from 'react';
import { AlertCircle } from 'lucide-react';
export default function RegisterPage() {
const { login, user } = 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 [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
const navigate = useNavigate()
useEffect(() => {
const pendingEmail = localStorage.getItem("userEmail");
if (user || !pendingEmail) {
navigate("/");
}
}, [user]);
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 (/\s/.test(formData.firstName)) return toast.error('First name must not contain spaces'), false;
// if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only letters (AZ)'), 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 (/\s/.test(formData.lastName)) return toast.error('Last name must not contain spaces'), false;
// if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only letters (AZ)'), 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.trim()) return toast.error('Email address is required'), false;
// if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) return toast.error('Enter a valid email (e.g. name@example.com)'), false;
// // ISD
// if (!formData.isdCode.trim()) return toast.error('ISD code is required'), false;
// if (/\s/.test(formData.isdCode)) return toast.error('ISD code must not contain spaces'), false;
// if (!formData.isdCode.startsWith('+')) return toast.error("ISD code must start with '+' (e.g. +91)"), false;
// if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only digits after '+'"), false;
// // Phone
// if (!formData.mobileNumber.trim()) return toast.error('Mobile number is required'), false;
// if (/\s/.test(formData.mobileNumber)) return toast.error('Mobile number must not contain spaces'), false;
// if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Mobile number must contain only digits (09)'), false;
// if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false;
// // Address
// if (!formData.address1.trim()) return toast.error('Address is required'), false;
// if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address can only contain letters, numbers, spaces, commas, dots, and hyphens'), 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 is required'), false;
// if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City can only contain letters and spaces'), false;
// if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false;
// // State
// if (!formData.state.trim()) return toast.error('State is required'), false;
// if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) return toast.error('State can only contain letters and spaces'), false;
// if (/\s{2,}/.test(formData.state)) return toast.error('State must not contain multiple consecutive spaces'), false;
// // Country
// if (!formData.country.trim()) return toast.error('Country is required'), false;
// if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country can only contain letters and spaces'), false;
// // Postal Code
// if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false;
// if (/\s/.test(formData.postalCode)) return toast.error('Postal code must not contain spaces'), false;
// if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) return toast.error('Postal code must contain only letters and numbers'), false;
// if (formData.postalCode.length < 4 || formData.postalCode.length > 10) return toast.error('Postal code must be between 4 and 10 characters'), false;
// return true;
// };
const validateForm = () => {
const e: Record<string, string> = {};
if (!formData.firstName.trim()) e.firstName = 'First name is required';
else if (/\s/.test(formData.firstName)) e.firstName = 'First name must not contain spaces';
else if (!/^[A-Za-z]+$/.test(formData.firstName)) e.firstName = 'First name must contain only letters (AZ)';
else if (formData.firstName.length < 2 || formData.firstName.length > 50) e.firstName = 'First name must be between 2 and 50 characters';
if (!formData.lastName.trim()) e.lastName = 'Last name is required';
else if (/\s/.test(formData.lastName)) e.lastName = 'Last name must not contain spaces';
else if (!/^[A-Za-z]+$/.test(formData.lastName)) e.lastName = 'Last name must contain only letters (AZ)';
else if (formData.lastName.length < 2 || formData.lastName.length > 50) e.lastName = 'Last name must be between 2 and 50 characters';
if (!formData.emailAddress.trim()) e.emailAddress = 'Email address is required';
else if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) e.emailAddress = 'Enter a valid email (e.g. name@example.com)';
if (!formData.isdCode.trim()) e.isdCode = 'ISD code is required';
else if (/\s/.test(formData.isdCode)) e.isdCode = 'ISD code must not contain spaces';
else if (!formData.isdCode.startsWith('+')) e.isdCode = "ISD code must start with '+' (e.g. +91)";
else if (!/^\+\d+$/.test(formData.isdCode)) e.isdCode = "ISD code must contain only digits after '+'";
if (!formData.mobileNumber.trim()) e.mobileNumber = 'Mobile number is required';
else if (/\s/.test(formData.mobileNumber)) e.mobileNumber = 'Mobile number must not contain spaces';
else if (!/^\d+$/.test(formData.mobileNumber)) e.mobileNumber = 'Mobile number must contain only digits (09)';
else if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) e.mobileNumber = 'Mobile number must be between 7 and 15 digits';
if (!formData.address1.trim()) e.address1 = 'Address is required';
else if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) e.address1 = 'Address can only contain letters, numbers, spaces, commas, dots, and hyphens';
else if (formData.address1.length < 5 || formData.address1.length > 100) e.address1 = 'Address must be between 5 and 100 characters';
if (!formData.city.trim()) e.city = 'City is required';
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) e.city = 'City can only contain letters and spaces';
else if (/\s{2,}/.test(formData.city)) e.city = 'City must not contain multiple consecutive spaces';
if (!formData.state.trim()) e.state = 'State is required';
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) e.state = 'State can only contain letters and spaces';
else if (/\s{2,}/.test(formData.state)) e.state = 'State must not contain multiple consecutive spaces';
if (!formData.country.trim()) e.country = 'Country is required';
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) e.country = 'Country can only contain letters and spaces';
else if (formData.country.length < 2 || formData.country.length > 50) e.country = 'Country must be between 2 and 50 characters';
if (!formData.postalCode.trim()) e.postalCode = 'Postal code is required';
else if (/\s/.test(formData.postalCode)) e.postalCode = 'Postal code must not contain spaces';
else if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) e.postalCode = 'Postal code must contain only letters and numbers';
else if (formData.postalCode.length < 4 || formData.postalCode.length > 10) e.postalCode = 'Postal code must be between 4 and 10 characters';
setFieldErrors(e);
return Object.keys(e).length === 0;
};
// Helper to render inline error (add once near top of return or as a small component)
const FieldError = ({ name }: { name: string }) =>
fieldErrors[name] ? (
<p className="text-xs text-red-500 mt-1 flex items-center gap-1">
<AlertCircle className="w-3 h-3" />{fieldErrors[name]}
</p>
) : null;
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 <span className="text-red-500">*</span>
</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 ${fieldErrors.firstName ? 'border border-red-400' : ''}`}
/>
<FieldError name="firstName" />
</div>
<div>
<Label htmlFor="lastName" className="font-poppins font-light">
Last Name <span className="text-red-500">*</span>
</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 ${fieldErrors.lastName ? 'border border-red-400' : ''}`}
/>
<FieldError name="lastName" />
</div>
</div>
<div>
<Label htmlFor="emailAddress" className="font-poppins font-light">
Email Address <span className="text-red-500">*</span>
</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"
/>
<FieldError name="emailAddress" />
</div>
<div className="grid md:grid-cols-3 gap-6">
<div>
<Label htmlFor="isdCode" className="font-poppins font-light">
ISD Code <span className="text-red-500">*</span>
</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 ${fieldErrors.isdCode ? 'border border-red-400' : ''}`}
/>
<FieldError name="isdCode" />
</div>
<div className="md:col-span-2">
<Label htmlFor="mobileNumber" className="font-poppins font-light">
Mobile Number <span className="text-red-500">*</span>
</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 ${fieldErrors.mobileNumber ? 'border border-red-400' : ''}`}
/>
<FieldError name="mobileNumber" />
</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 <span className="text-red-500">*</span>
</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 ${fieldErrors.address1 ? 'border border-red-400' : ''}`}
/>
<FieldError name="address1" />
</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 <span className="text-red-500">*</span>
</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 ${fieldErrors.city ? 'border border-red-400' : ''}`}
/>
<FieldError name="city" />
</div>
<div>
<Label htmlFor="state" className="font-poppins font-light">
State <span className="text-red-500">*</span>
</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 ${fieldErrors.state ? 'border border-red-400' : ''}`}
/>
<FieldError name="state" />
</div>
</div>
<div className="grid md:grid-cols-2 gap-6">
<div>
<Label htmlFor="country" className="font-poppins font-light">
Country <span className="text-red-500">*</span>
</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 ${fieldErrors.country ? 'border border-red-400' : ''}`}
/>
<FieldError name="country" />
</div>
<div>
<Label htmlFor="postalCode" className="font-poppins font-light">
Postal Code <span className="text-red-500">*</span>
</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 ${fieldErrors.postalCode ? 'border border-red-400' : ''}`}
/>
<FieldError name="postalCode" />
</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>
);
}

View File

@@ -0,0 +1,12 @@
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}

View File

@@ -204,7 +204,7 @@ export function TrustSection() {
style={{
transform: `rotate(${cardRotation}deg) translateY(${cardOffset}px)`,
transformOrigin: 'center center',
minHeight: '480px',
minHeight: '360px',
background: `
radial-gradient(circle at 20% 80%, rgba(255, 248, 235, 0.8) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(250, 245, 230, 0.6) 0%, transparent 50%),

View File

@@ -100,7 +100,7 @@ export function WhatsIncludedHero({ onCreateItineraryClick }: WhatsIncludedHeroP
>
{/* Main Heading */}
<h1 className="font-poppins text-4xl sm:text-5xl md:text-6xl w-full leading-tight mb-6">
<span className="font-light">One pass.</span>{' '}
<span className="font-light">One CityCard</span>{' '}
<span className="font-bold italic pr-2 bg-gradient-to-r from-primary via-orange-500 to-rose-500 bg-clip-text text-transparent">
Everything you
</span>{' '}

View File

@@ -1,3 +1,4 @@
// AuthContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom';
@@ -40,6 +41,8 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
localStorage.removeItem("user")
localStorage.removeItem("accessToken")
localStorage.removeItem("userId")
localStorage.removeItem("userEmail")
sessionStorage.removeItem("citySelected")
navigate("/")
}

View 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>
);
}

View 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>
);
}

View 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 Melbournes 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
src/imports/image-10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
src/imports/image-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/imports/image-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/imports/image-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/imports/image-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
src/imports/image-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
src/imports/image-7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/imports/image-8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
src/imports/image-9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/imports/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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",
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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",
}

View 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",
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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",
}

File diff suppressed because one or more lines are too long

View File

@@ -5,11 +5,13 @@ import "./index.css";
import { Provider } from "react-redux";
import { store } from "./Redux/Store";
import { Toaster } from "sonner";
import { ScrollToTop } from "./components/ScrollToTop";
createRoot(document.getElementById("root")!).render(
<Provider store={store}>
<BrowserRouter>
<Toaster position="top-right" richColors duration={2000} closeButton />
<ScrollToTop />
<App />
</BrowserRouter>
</Provider>

View 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`}

View File

@@ -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>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -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>
// );
// }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,12 @@
import { ArrowRight, Check, CreditCard, DollarSign, MapPin, Palette, Sparkles, Ticket, Zap } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import { useEffect, useState } from 'react';
import { use, useEffect, useState } from 'react';
import { Layout } from '../Layout';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import { MobileAppSection } from '../components/MobileAppSection';
import { TrustSection } from '../components/TrustSection';
import { Button } from '../components/ui/button';
import { useNavigate } from 'react-router-dom';
interface User {
email: string;
@@ -68,6 +69,8 @@ export function DiscoverPage({
const [direction, setDirection] = useState(0);
const [activeStep, setActiveStep] = useState(0);
const navigate =useNavigate();
const handleStepInView = (index: number) => {
setActiveStep(index);
};
@@ -687,8 +690,8 @@ export function DiscoverPage({
</div>
<Button
onClick={onPassesClick}
className="w-full py-6 rounded-full font-poppins font-semibold text-lg bg-gray-900 hover:bg-black text-white transition-all duration-300"
onClick={()=>navigate('/passes')}
className="cursor-pointer w-full py-6 rounded-full font-poppins font-semibold text-lg bg-gray-900 hover:bg-black text-white transition-all duration-300"
>
VIEW FLEXI OPTIONS
</Button>
@@ -743,8 +746,8 @@ export function DiscoverPage({
</div>
<Button
onClick={onPassesClick}
className="w-full py-6 rounded-full font-poppins font-semibold text-lg bg-primary hover:bg-primary/90 text-white transition-all duration-300"
onClick={()=>navigate('/passes')}
className=" cursor-pointer w-full py-6 rounded-full font-poppins font-semibold text-lg bg-primary hover:bg-primary/90 text-white transition-all duration-300"
>
VIEW UNLIMITED OPTIONS
</Button>

View File

@@ -0,0 +1,379 @@
import React, { useCallback, useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import {
MapPin,
Calendar,
ChevronDown,
Share2,
Download,
ChevronRight,
Loader2,
} from 'lucide-react';
import { Button } from '../components/ui/button';
import { Card, CardContent } from '../components/ui/card';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import { useDownloadItineraryQuery, 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);
// 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 ?? [];
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
onClick={handleDownloadItinerary}
disabled={isDownloading}
variant="outline"
className="flex-1 border-2 border-primary/20 text-primary hover:bg-primary/5 font-poppins font-medium rounded-xl py-3"
>
{isDownloading ? (
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
) : (
<Download className="w-5 h-5 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

View File

@@ -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}

View File

@@ -75,6 +75,7 @@ export function MagicItineraryPage({
currentPage,
user
}: MagicItineraryPageProps) {
const cityName = localStorage.getItem("cityName") || "your city";
return (
<Layout activeCity="Landingpage" onSignInClick={onSignInClick} onSignOutClick={onSignOutClick} user={user}>
@@ -190,7 +191,7 @@ export function MagicItineraryPage({
</h3>
</div>
<p className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
A perfectly planned Melbourne adventure
A perfectly planned {cityName} adventure
</p>
</div>

View File

@@ -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}
@@ -219,7 +222,7 @@ export function MelbournePage({
</div> */}
<div className="container mx-auto px-4 py-12 space-y-24">
<div className="container mx-auto px-4 py-12">
{/* Features Grid */}
<motion.section
id="overview"
@@ -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"
@@ -389,7 +392,7 @@ export function MelbournePage({
>
<Wand2 className="w-6 h-6 text-primary drop-shadow-lg" />
</motion.div>
<span className="font-poppins font-semibold text-gray-800">AI-Powered Magic Itinerary</span>
<span className="font-poppins font-semibold text-gray-800">Magic Itinerary</span>
<motion.div
className="w-2 h-2 bg-primary rounded-full"
animate={{
@@ -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">
@@ -788,18 +791,18 @@ export function MelbournePage({
</span>
</Button>
<p className="font-poppins text-gray-600 text-sm flex items-center gap-2">
{/* <p className="font-poppins text-gray-600 text-sm flex items-center gap-2">
<Sparkles className="w-4 h-4 text-primary" />
<span>Free to use • No credit card required</span>
<Sparkles className="w-4 h-4 text-primary" />
</p>
</p> */}
</motion.div>
</div>
</div>
</section>
</div>
<div className="container mx-auto px-4 py-12 space-y-24">
<div className="container mx-auto px-4 py-12">
{/* Testimonials */}
<EnhancedTestimonials />

View File

@@ -156,20 +156,19 @@ export function PassesPage({
const navigate = useNavigate()
const cityId = localStorage.getItem("cityId")
const cityName = localStorage.getItem("cityName")
const { data: cityDetails, isLoading: loadingCityDetails } = useGetSelectedCityDetailsQuery(cityId)
const cards = cityDetails?.city?.cards ?? []
console.log(cards)
if (loadingCityDetails) {
return (<LoadingSpinner />)
}
const handleCheckoutClick = () => {
const handleCheckoutClick = (cardTypeName:string) => {
console.log('Proceeding to checkout for user:', user);
// Add your checkout logic here
navigate('/checkout');
navigate('/checkout', { state: { selectedCard: cardTypeName } });
};
const handleSignInClick = () => {
@@ -227,10 +226,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 +237,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>
@@ -282,7 +281,7 @@ export function PassesPage({
? "bg-primary hover:bg-primary/90 text-white hover:shadow-lg"
: "bg-gray-400 hover:bg-gray-400 text-white hover:shadow-md"
}`}
onClick={user ? handleCheckoutClick : handleSignInClick}
onClick={() => user ? handleCheckoutClick(cards[0]?.cardType?.cardTypeName) : handleSignInClick}
disabled={selectedPass !== passTypes[0].id}
>
@@ -299,14 +298,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 +319,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 +354,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>
@@ -372,7 +371,7 @@ export function PassesPage({
}`}
disabled={selectedPass !== passTypes[1].id}
onClick={user ? handleCheckoutClick : handleSignInClick}
onClick={() => user ? handleCheckoutClick(cards[1]?.cardType?.cardTypeName) : handleSignInClick}
>
{user ? 'PURCHASE NOW' : 'LOGIN TO BUY PASS'}
@@ -415,7 +414,7 @@ export function PassesPage({
<Clock className="w-7 h-7" strokeWidth={1.5} />
</div>
<div className="flex-1 w-full">
<h3 className="font-merchant text-2xl text-gray-900 mb-3">Calendar Days Policy</h3>
<h3 className="font-merchant text-xl text-gray-900 mb-3">Calendar Days Policy</h3>
<p className="font-poppins text-gray-600 leading-relaxed mb-6">
Unlimited passes work on a <span className="font-medium text-gray-900">consecutive calendar day basis</span>, not 24-hour periods. Your pass expires at 11:59 PM on your final day.
</p>
@@ -452,7 +451,7 @@ export function PassesPage({
<Shield className="w-7 h-7" strokeWidth={1.5} />
</div>
<div className="flex-1">
<h3 className="font-merchant text-2xl text-gray-900 mb-3">60-Minute Adventure Gap</h3>
<h3 className="font-merchant text-xl text-gray-900 mb-3">60-Minute Adventure Gap</h3>
<p className="font-poppins text-gray-600 leading-relaxed">
To keep the journey smooth for everyone, there's a simple <span className="font-medium text-gray-900">60-minute wait</span> between scanning your pass at attractions.
</p>
@@ -597,7 +596,7 @@ export function PassesPage({
</div>
<div>
<h3 className="text-white font-semibold text-lg">CityCards</h3>
<p className="text-white/70 text-sm">Melbourne Explorer</p>
<p className="text-white/70 text-sm">{cityName} Explorer</p>
</div>
</div>
</div>
@@ -770,7 +769,7 @@ export function PassesPage({
<h3 className="heading-dynamic text-4xl mb-4">
<span className="font-light">Ready to</span>{' '}
<span className="font-bold italic text-emphasis">explore</span>{' '}
<span className="font-semibold">Melbourne?</span>
<span className="font-semibold">{cityName}?</span>
</h3>
<p className="text-xl mb-8 max-w-2xl mx-auto opacity-90 font-light">
Choose your pass and start discovering amazing attractions with skip-the-line access.

Some files were not shown because too many files have changed in this diff Show More