@ticketboothapp/booking 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/app/photo-sessions/photo-packages.ts +75 -0
- package/src/assets/icons/partner-logos/getyourguide.svg +8 -0
- package/src/assets/icons/plus.svg +3 -0
- package/src/colours.css +23 -0
- package/src/components/BookingDetails.module.css +1591 -0
- package/src/components/BookingDetails.tsx +2072 -354
- package/src/components/BookingWidget.tsx +28 -248
- package/src/components/JobApplicationDialog.module.css +440 -0
- package/src/components/JobApplicationDialog.tsx +620 -0
- package/src/components/ManageBookingView.tsx +28 -36
- package/src/components/PhoneInputWithCountry.module.css +131 -0
- package/src/components/PhoneInputWithCountry.tsx +44 -0
- package/src/components/PickupLocationDialog.module.css +360 -0
- package/src/components/PickupLocationDialog.tsx +357 -0
- package/src/components/PickupLocationMap.tsx +110 -0
- package/src/components/PostBookingDependentAddOnUpsell.module.css +174 -0
- package/src/components/PostBookingDependentAddOnUpsell.tsx +407 -0
- package/src/components/accordion.css +27 -0
- package/src/components/accordion.tsx +29 -0
- package/src/components/analytics/AnalyticsConsentRestore.tsx +19 -0
- package/src/components/analytics/AnalyticsScripts.tsx +106 -0
- package/src/components/analytics/CookieConsentBanner.css +86 -0
- package/src/components/analytics/CookieConsentBanner.tsx +102 -0
- package/src/components/booking/AddOnsSection.module.css +10 -0
- package/src/components/booking/AddOnsSection.tsx +184 -0
- package/src/components/booking/AdminPaymentChoiceModal.tsx +98 -0
- package/src/components/booking/BookingDialog.module.css +643 -0
- package/src/components/booking/BookingDialog.tsx +356 -0
- package/src/components/booking/BookingFlow.tsx +4385 -0
- package/src/components/booking/BookingFlowCollage.module.css +148 -0
- package/src/components/booking/BookingFlowCollage.tsx +184 -0
- package/src/components/booking/BookingFlowPlaceholder.module.css +27 -0
- package/src/components/booking/BookingFlowPlaceholder.tsx +25 -0
- package/src/components/booking/BookingFlowPreview.tsx +51 -0
- package/src/components/booking/BookingProductGrid.module.css +359 -0
- package/src/components/booking/BookingProductGrid.tsx +497 -0
- package/src/components/booking/Calendar.module.css +616 -0
- package/src/components/{Calendar.tsx → booking/Calendar.tsx} +464 -247
- package/src/components/booking/CancellationPolicySelector.module.css +124 -0
- package/src/components/booking/CancellationPolicySelector.tsx +142 -0
- package/src/components/booking/ChangeBookingDialog.tsx +562 -0
- package/src/components/booking/CheckoutForm.module.css +244 -0
- package/src/components/booking/CheckoutForm.tsx +364 -0
- package/src/components/{CheckoutModal.tsx → booking/CheckoutModal.tsx} +176 -19
- package/src/components/booking/DapFlowCollage.tsx +88 -0
- package/src/components/booking/DapTourDescription.tsx +35 -0
- package/src/components/booking/DependentAddOnBookingDialog.tsx +1350 -0
- package/src/components/booking/DependentAddOnPaymentForm.tsx +124 -0
- package/src/components/booking/InfoTooltip.tsx +108 -0
- package/src/components/booking/ItineraryBox.module.css +258 -0
- package/src/components/booking/ItineraryBox.tsx +550 -0
- package/src/components/{ItineraryBuilder.tsx → booking/ItineraryBuilder.tsx} +1 -2
- package/src/components/booking/ItineraryPlaceholder.module.css +45 -0
- package/src/components/booking/ItineraryPlaceholder.tsx +26 -0
- package/src/components/{MealDrinkAddOnSelector.tsx → booking/MealDrinkAddOnSelector.tsx} +21 -13
- package/src/components/booking/PickupLocationSelector.module.css +124 -0
- package/src/components/{PickupLocationSelector.tsx → booking/PickupLocationSelector.tsx} +315 -290
- package/src/components/booking/PickupTimeSelector.module.css +134 -0
- package/src/components/booking/PickupTimeSelector.tsx +112 -0
- package/src/components/{PriceBreakdown.tsx → booking/PriceBreakdown.tsx} +3 -3
- package/src/components/{PriceSummary.tsx → booking/PriceSummary.tsx} +51 -28
- package/src/components/booking/PrivateShuttleBookingFlow.module.css +357 -0
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +2662 -0
- package/src/components/booking/PromoCodeInput.module.css +166 -0
- package/src/components/booking/PromoCodeInput.tsx +99 -0
- package/src/components/booking/ReturnTimeSelector.module.css +173 -0
- package/src/components/booking/ReturnTimeSelector.tsx +145 -0
- package/src/components/{TermsAcceptance.tsx → booking/TermsAcceptance.tsx} +9 -8
- package/src/components/booking/TicketSelector.module.css +164 -0
- package/src/components/booking/TicketSelector.tsx +199 -0
- package/src/components/booking/TourDescription.module.css +304 -0
- package/src/components/booking/TourDescription.tsx +273 -0
- package/src/components/booking/booking-flow-ui.ts +15 -1
- package/src/components/booking/booking-flow.css +944 -0
- package/src/components/bottom-sheet.module.css +78 -0
- package/src/components/bottom-sheet.tsx +60 -0
- package/src/components/breadcrumb.module.css +40 -0
- package/src/components/breadcrumb.tsx +36 -0
- package/src/components/button.css +245 -0
- package/src/components/button.tsx +152 -0
- package/src/components/client-bottom-sheet.tsx +14 -0
- package/src/components/colorable-svg.tsx +29 -0
- package/src/components/conditional-footer.tsx +27 -0
- package/src/components/contact-us.module.css +147 -0
- package/src/components/contact-us.tsx +49 -0
- package/src/components/email-signup.css +151 -0
- package/src/components/email-signup.tsx +63 -0
- package/src/components/faq-wrapper.module.css +47 -0
- package/src/components/faq-wrapper.tsx +15 -0
- package/src/components/footer.css +187 -0
- package/src/components/footer.tsx +143 -0
- package/src/components/global-simple-modal.tsx +33 -0
- package/src/components/google-review-summary.module.css +77 -0
- package/src/components/google-review-summary.tsx +50 -0
- package/src/components/hero-image.css +13 -0
- package/src/components/hero-image.tsx +44 -0
- package/src/components/image.css +29 -0
- package/src/components/image.tsx +113 -0
- package/src/components/language-aware-link.tsx +72 -0
- package/src/components/language-switcher.module.css +124 -0
- package/src/components/language-switcher.tsx +75 -0
- package/src/components/map-section.css +59 -0
- package/src/components/map-section.tsx +63 -0
- package/src/components/navbar.module.css +152 -0
- package/src/components/navbar.tsx +125 -0
- package/src/components/parallax-provider.tsx +11 -0
- package/src/components/partner/PartnerBookingPage.module.css +130 -0
- package/src/components/partner/PartnerBookingPage.tsx +390 -0
- package/src/components/partner/PartnerBookingPageWithBrowserMetadata.tsx +19 -35
- package/src/components/product-tag.module.css +30 -0
- package/src/components/product-tag.tsx +34 -0
- package/src/components/product-theme-pages/best-option.module.css +70 -0
- package/src/components/product-theme-pages/best-option.tsx +35 -0
- package/src/components/product-theme-pages/extended-tour-options.module.css +22 -0
- package/src/components/product-theme-pages/extended-tour-options.tsx +11 -0
- package/src/components/product-theme-pages/image-modal.tsx +248 -0
- package/src/components/product-theme-pages/photo-gallery.module.css +200 -0
- package/src/components/product-theme-pages/photo-gallery.tsx +90 -0
- package/src/components/product-theme-pages/product-theme-page-layout.module.css +13 -0
- package/src/components/product-theme-pages/product-theme-page-layout.tsx +67 -0
- package/src/components/product-theme-pages/top-of-fold.module.css +179 -0
- package/src/components/product-theme-pages/top-of-fold.tsx +80 -0
- package/src/components/product-tile/image-only-product-tile-desktop.module.css +106 -0
- package/src/components/product-tile/image-only-product-tile-desktop.tsx +56 -0
- package/src/components/product-tile/image-only-product-tile-mobile.module.css +122 -0
- package/src/components/product-tile/image-only-product-tile-mobile.tsx +89 -0
- package/src/components/product-tile/image-only-product-tile.tsx +44 -0
- package/src/components/product-tile/product-tile-card.module.css +84 -0
- package/src/components/product-tile/product-tile-card.tsx +61 -0
- package/src/components/review-highlights-section.css +85 -0
- package/src/components/review-highlights-section.tsx +127 -0
- package/src/components/season-closure-overlay.module.css +99 -0
- package/src/components/season-closure-overlay.tsx +98 -0
- package/src/components/simple-modal.tsx +69 -0
- package/src/components/simple-top-of-fold.module.css +76 -0
- package/src/components/simple-top-of-fold.tsx +34 -0
- package/src/components/spacer.css +41 -0
- package/src/components/spacer.tsx +23 -0
- package/src/components/star-rating.module.css +74 -0
- package/src/components/star-rating.tsx +48 -0
- package/src/components/terms/TermsContent.tsx +178 -0
- package/src/components/title-subtitle.module.css +10 -0
- package/src/components/title-subtitle.tsx +30 -0
- package/src/components/translatable-reviews.tsx +75 -0
- package/src/components/value-pill.module.css +59 -0
- package/src/components/value-pill.tsx +46 -0
- package/src/components/value-props.css +185 -0
- package/src/components/value-props.tsx +88 -0
- package/src/constants/booking-guide-quiz.ts +64 -0
- package/src/constants/contact-info.ts +2 -0
- package/src/constants/faq.ts +44 -0
- package/src/constants/images.ts +556 -0
- package/src/constants/json-ld/faq-json-ld.tsx +170 -0
- package/src/constants/json-ld/homepage-json-ld.tsx +138 -0
- package/src/constants/json-ld/job-posting-json-ld.tsx +92 -0
- package/src/constants/json-ld/organization-json-ld.tsx +62 -0
- package/src/constants/json-ld/page-json-ld.tsx +6 -0
- package/src/constants/json-ld/product-json-ld.tsx +154 -0
- package/src/constants/json-ld/review-json-ld.tsx +377 -0
- package/src/constants/navigation-links/footer-links.ts +48 -0
- package/src/constants/navigation-links/nav-bar-links.ts +41 -0
- package/src/constants/navigation-links/navigation-link.ts +6 -0
- package/src/constants/pill-values.ts +210 -0
- package/src/constants/products.ts +155 -0
- package/src/constants/quiz-recommendations.ts +506 -0
- package/src/constants/reviews.ts +75 -0
- package/src/constants/staff.ts +197 -0
- package/src/constants/value-props.ts +58 -0
- package/src/data/dap-descriptions/session-couples-families-friends.en.json +61 -0
- package/src/data/dap-descriptions/session-elopements.en.json +60 -0
- package/src/data/dap-descriptions/session-proposals.en.json +60 -0
- package/src/data/product-descriptions/afternoon-delight.en.json +35 -0
- package/src/data/product-descriptions/emerald-lake-escape.en.json +68 -0
- package/src/data/product-descriptions/lake-louise-adventure.en.json +74 -0
- package/src/data/product-descriptions/moraine-lake-adventure.en.json +78 -0
- package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +65 -0
- package/src/data/product-descriptions/moraine-lake-sunrise.en.json +64 -0
- package/src/data/product-descriptions/private-tour.en.json +80 -0
- package/src/data/product-descriptions/two-lakes-combo.en.json +65 -0
- package/src/data/products-config.json +101 -0
- package/src/hooks/use-bottom-sheet.tsx +15 -0
- package/src/hooks/use-simple-modal.tsx +27 -0
- package/src/hooks/useBookingSourceMetadataFromLocation.ts +21 -0
- package/src/hooks/useEmailSubscription.tsx +103 -0
- package/src/hooks/useEmbeddedInIframe.ts +16 -0
- package/src/hooks/useIsBookingLaunchLive.ts +49 -0
- package/src/hooks/useQuiz.tsx +210 -0
- package/src/index.ts +27 -2
- package/src/lib/analytics.ts +197 -0
- package/src/lib/booking/booking-source.ts +20 -2
- package/src/lib/{checkout-breakdown.ts → booking/checkout-breakdown.ts} +1 -1
- package/src/lib/booking/correlation-id.ts +46 -0
- package/src/lib/{i18n → booking/i18n}/messages/en.json +48 -4
- package/src/lib/{i18n → booking/i18n}/messages/fr.json +48 -4
- package/src/lib/booking/itinerary-display.ts +36 -0
- package/src/lib/{itinerary-labels.ts → booking/itinerary-labels.ts} +1 -1
- package/src/lib/{location-calculations.ts → booking/location-calculations.ts} +4 -4
- package/src/lib/{location-utils.ts → booking/location-utils.ts} +26 -0
- package/src/lib/{map-utils.ts → booking/map-utils.ts} +3 -3
- package/src/lib/booking/normalize-booking-product-id.ts +7 -0
- package/src/lib/{pickup-location-types.ts → booking/pickup-location-types.ts} +2 -2
- package/src/lib/{pricing.ts → booking/pricing.ts} +2 -2
- package/src/lib/booking/product-option-id.ts +35 -0
- package/src/lib/booking/source-metadata.ts +72 -7
- package/src/lib/booking/sunday-week.ts +14 -0
- package/src/lib/booking/trace-context.ts +62 -0
- package/src/lib/booking-api.ts +1793 -0
- package/src/lib/{constants.ts → booking-constants.ts} +11 -5
- package/src/lib/booking-types.ts +36 -0
- package/src/lib/currency.ts +38 -45
- package/src/lib/dap-descriptions.ts +50 -0
- package/src/lib/dap-itinerary-preview.ts +315 -0
- package/src/lib/dependent-add-on-api.ts +434 -0
- package/src/lib/env.ts +89 -5
- package/src/lib/firebase.ts +20 -0
- package/src/lib/job-application-api.ts +83 -0
- package/src/lib/manage-booking-embed-print.ts +16 -0
- package/src/lib/manage-booking-post-checkout.ts +68 -0
- package/src/lib/photo-dap-config.ts +228 -0
- package/src/lib/pickup/map-utils.ts +56 -0
- package/src/lib/pickup/marker-icons.ts +19 -0
- package/src/lib/product-descriptions.ts +66 -0
- package/src/lib/products-config.ts +73 -0
- package/src/providers/booking-dialog-provider.tsx +107 -38
- package/src/providers/bottom-sheet-provider.tsx +40 -0
- package/src/providers/dependent-add-on-dialog-provider.tsx +105 -0
- package/src/radius.css +5 -0
- package/src/spacing.css +7 -0
- package/src/strings/en.json +1774 -0
- package/src/strings/es.json +1573 -0
- package/src/strings/fr.json +1573 -0
- package/src/strings/index.js +23 -0
- package/src/text-style.css +97 -0
- package/src/types/fareharbor.d.ts +12 -0
- package/src/types/quiz.ts +59 -0
- package/src/utils/currency-converter.ts +101 -0
- package/src/components/BookingFlow.tsx +0 -2952
- package/src/components/LanguageSwitcher.tsx +0 -30
- package/src/components/PrivateShuttleBookingFlow.tsx +0 -2290
- package/src/components/ProductList.tsx +0 -78
- package/src/components/WhatsAppPhoneInput.tsx +0 -224
- package/src/components/index.ts +0 -31
- package/src/lib/api.ts +0 -801
- package/src/lib/booking-api-auth.ts +0 -9
- package/src/lib/checkout-breakdown.test.ts +0 -70
- package/src/types/google-maps.d.ts +0 -2
- /package/src/components/{CurrencySwitcher.tsx → booking/CurrencySwitcher.tsx} +0 -0
- /package/src/components/{ErrorBoundary.tsx → booking/ErrorBoundary.tsx} +0 -0
- /package/src/lib/{i18n → booking/i18n}/config.ts +0 -0
- /package/src/lib/{i18n → booking/i18n}/index.tsx +0 -0
- /package/src/lib/{marker-icons.ts → booking/marker-icons.ts} +0 -0
- /package/src/lib/{places-api.ts → booking/places-api.ts} +0 -0
- /package/src/lib/{theme.ts → booking/theme.ts} +0 -0
- /package/src/lib/{utils.ts → booking/utils.ts} +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pickup time selector - grid of time slot buttons
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
.label {
|
|
6
|
+
display: block;
|
|
7
|
+
font-size: 0.875rem;
|
|
8
|
+
font-weight: 500;
|
|
9
|
+
color: var(--booking-stone-700, #44403c);
|
|
10
|
+
margin-bottom: 0.5rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.grid {
|
|
14
|
+
display: grid;
|
|
15
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
16
|
+
gap: 0.5rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@media (min-width: 640px) {
|
|
20
|
+
.grid {
|
|
21
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.btn {
|
|
26
|
+
padding: 0.875rem 1.25rem;
|
|
27
|
+
border-radius: 0.5rem;
|
|
28
|
+
font-size: 0.875rem;
|
|
29
|
+
font-weight: 500;
|
|
30
|
+
transition: all 0.2s;
|
|
31
|
+
position: relative;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.btnDefault {
|
|
35
|
+
padding-top: 0.75rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.btnWithBadge {
|
|
39
|
+
padding-top: 1.25rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@media (min-width: 640px) {
|
|
43
|
+
.btnWithBadge {
|
|
44
|
+
padding-top: 1rem;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.btnSelected {
|
|
49
|
+
background: var(--booking-emerald-600, #059669);
|
|
50
|
+
color: #fff;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.btnAvailable {
|
|
54
|
+
background: var(--light-orange-background-dark, #f7e4dc);
|
|
55
|
+
color: var(--booking-stone-700, #44403c);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.btnAvailable:hover {
|
|
59
|
+
background: var(--light-orange-background, #fff1eb);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.btnDisabled {
|
|
63
|
+
background: var(--booking-stone-100, #f5f5f4);
|
|
64
|
+
color: var(--booking-stone-400, #a8a29e);
|
|
65
|
+
cursor: not-allowed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.btnSoldOutAdmin {
|
|
69
|
+
background: #fee2e2;
|
|
70
|
+
color: #b91c1c;
|
|
71
|
+
border: 1px solid #fca5a5;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.btnSoldOutAdmin:hover {
|
|
75
|
+
background: #fee2e2;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.btnSoldOutLocked {
|
|
79
|
+
background: #fee2e2;
|
|
80
|
+
color: #b91c1c;
|
|
81
|
+
border: 1px solid #fca5a5;
|
|
82
|
+
cursor: not-allowed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.btnSoldOutLocked:hover {
|
|
86
|
+
background: #fee2e2;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.capacity {
|
|
90
|
+
font-size: 0.75rem;
|
|
91
|
+
margin-top: 0.125rem;
|
|
92
|
+
font-variant-numeric: tabular-nums;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Fitted to green selected tile — not pure white (readability) */
|
|
96
|
+
.capacityOnSelected {
|
|
97
|
+
color: rgba(255, 255, 255, 0.9);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.capacityDefault {
|
|
101
|
+
color: var(--booking-stone-500, #78716c);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.capacityProjected {
|
|
105
|
+
font-size: 0.7rem;
|
|
106
|
+
line-height: 1.2;
|
|
107
|
+
margin-top: 0.25rem;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
text-align: center;
|
|
110
|
+
color: #b91c1c;
|
|
111
|
+
font-variant-numeric: tabular-nums;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.badge {
|
|
115
|
+
position: absolute;
|
|
116
|
+
top: -0.75rem;
|
|
117
|
+
left: 50%;
|
|
118
|
+
transform: translateX(-50%);
|
|
119
|
+
font-family: 'Poppins', sans-serif;
|
|
120
|
+
font-size: 0.75rem;
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
text-transform: lowercase;
|
|
123
|
+
color: #fff;
|
|
124
|
+
padding: 0.25rem 0.625rem;
|
|
125
|
+
border-radius: 9999px;
|
|
126
|
+
white-space: nowrap;
|
|
127
|
+
background-color: #ff4d00;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.soldOut {
|
|
131
|
+
font-size: 0.75rem;
|
|
132
|
+
font-weight: 500;
|
|
133
|
+
color: #b91c1c;
|
|
134
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { Availability } from '@/lib/booking-api';
|
|
4
|
+
import type { ProductOption } from '@/lib/booking-api';
|
|
5
|
+
import styles from './PickupTimeSelector.module.css';
|
|
6
|
+
|
|
7
|
+
type TranslationFn = (key: string, params?: Record<string, string | number>) => string;
|
|
8
|
+
|
|
9
|
+
export interface PickupTimeInfo extends Availability {
|
|
10
|
+
pickupTime: string;
|
|
11
|
+
displayTime: string;
|
|
12
|
+
originalTime: string;
|
|
13
|
+
displayTimeRange?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PickupTimeSelectorProps {
|
|
17
|
+
pickupTimes: PickupTimeInfo[];
|
|
18
|
+
selectedDateTime: string | null;
|
|
19
|
+
selectedTicketCount: number;
|
|
20
|
+
optionsMap: Map<string, ProductOption>;
|
|
21
|
+
hasAnyMostPopular: boolean;
|
|
22
|
+
isAdmin: boolean;
|
|
23
|
+
pickupLocationSkipped: boolean;
|
|
24
|
+
t: TranslationFn;
|
|
25
|
+
onTimeSelect: (availability: Availability) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function PickupTimeSelector({
|
|
29
|
+
pickupTimes,
|
|
30
|
+
selectedDateTime,
|
|
31
|
+
selectedTicketCount,
|
|
32
|
+
optionsMap,
|
|
33
|
+
hasAnyMostPopular,
|
|
34
|
+
isAdmin,
|
|
35
|
+
pickupLocationSkipped,
|
|
36
|
+
t,
|
|
37
|
+
onTimeSelect,
|
|
38
|
+
}: PickupTimeSelectorProps) {
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
<label className={styles.label}>
|
|
42
|
+
{t('booking.selectPickupTime')}
|
|
43
|
+
</label>
|
|
44
|
+
<div className={styles.grid}>
|
|
45
|
+
{pickupTimes.map((timeInfo) => {
|
|
46
|
+
const isSelected = selectedDateTime === timeInfo.dateTime;
|
|
47
|
+
const isSoldOut = timeInfo.vacancies === 0;
|
|
48
|
+
const isInsufficientForParty =
|
|
49
|
+
selectedTicketCount > 0 && (timeInfo.vacancies ?? 0) < selectedTicketCount;
|
|
50
|
+
const canSelect = isAdmin || (!isSoldOut && !isInsufficientForParty);
|
|
51
|
+
const option = timeInfo.productOptionId
|
|
52
|
+
? optionsMap.get(timeInfo.productOptionId)
|
|
53
|
+
: undefined;
|
|
54
|
+
const isMostPopular = pickupTimes.length > 1 && option?.mostPopular;
|
|
55
|
+
const totalCap = timeInfo.totalCapacity ?? 0;
|
|
56
|
+
const booked = timeInfo.bookedCapacity ?? (totalCap - (timeInfo.vacancies ?? 0));
|
|
57
|
+
const showCapacity = isAdmin && totalCap > 0;
|
|
58
|
+
const slotVacancies = timeInfo.vacancies;
|
|
59
|
+
const projectedSeats = booked + selectedTicketCount;
|
|
60
|
+
const showAdminProjectedLoad =
|
|
61
|
+
isSelected && showCapacity && selectedTicketCount > slotVacancies;
|
|
62
|
+
// Selected tile stays green; unselected + admin + tight party uses public-style tile, not "sold out admin" pink
|
|
63
|
+
const btnClass = (() => {
|
|
64
|
+
if (isSelected) return styles.btnSelected;
|
|
65
|
+
if (!isAdmin) {
|
|
66
|
+
return (isSoldOut || isInsufficientForParty) ? styles.btnSoldOutLocked : styles.btnAvailable;
|
|
67
|
+
}
|
|
68
|
+
if (isSoldOut && slotVacancies === 0) return styles.btnSoldOutAdmin;
|
|
69
|
+
return styles.btnAvailable;
|
|
70
|
+
})();
|
|
71
|
+
return (
|
|
72
|
+
<button
|
|
73
|
+
key={timeInfo.dateTime}
|
|
74
|
+
onClick={() => canSelect && onTimeSelect(timeInfo)}
|
|
75
|
+
disabled={!canSelect}
|
|
76
|
+
className={`${styles.btn} ${hasAnyMostPopular ? styles.btnWithBadge : styles.btnDefault} ${btnClass}`}
|
|
77
|
+
>
|
|
78
|
+
<div>{pickupLocationSkipped && timeInfo.displayTimeRange ? timeInfo.displayTimeRange : timeInfo.displayTime}</div>
|
|
79
|
+
{showCapacity && (showAdminProjectedLoad ? (
|
|
80
|
+
<div className={styles.capacityProjected} aria-live="polite">
|
|
81
|
+
<div>{t('booking.adminBookingLoadLine1', { projected: projectedSeats })}</div>
|
|
82
|
+
<div>{t('booking.adminBookingLoadLine2', { total: totalCap })}</div>
|
|
83
|
+
</div>
|
|
84
|
+
) : (
|
|
85
|
+
<div
|
|
86
|
+
className={`${styles.capacity} ${isSelected ? styles.capacityOnSelected : styles.capacityDefault}`}
|
|
87
|
+
>
|
|
88
|
+
{t('calendar.spotsAvailable', { count: slotVacancies })}
|
|
89
|
+
</div>
|
|
90
|
+
))}
|
|
91
|
+
{isMostPopular && (
|
|
92
|
+
<div className={styles.badge}>
|
|
93
|
+
{t('booking.mostPopular')}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
{isSoldOut && (
|
|
97
|
+
<div className={styles.soldOut}>
|
|
98
|
+
{t('booking.soldOut')}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
{isInsufficientForParty && !isAdmin && (
|
|
102
|
+
<div className={styles.soldOut}>
|
|
103
|
+
{`Only ${timeInfo.vacancies} spot${timeInfo.vacancies === 1 ? '' : 's'} left, decrease your ticket count below to select this time`}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
</button>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { formatCurrencyAmount } from '@/lib/currency';
|
|
5
|
-
import type { PriceBreakdown as PriceBreakdownType } from '@/lib/pricing';
|
|
6
|
-
import { useTranslations } from '@/lib/i18n';
|
|
5
|
+
import type { PriceBreakdown as PriceBreakdownType } from '@/lib/booking/pricing';
|
|
6
|
+
import { useTranslations } from '@/lib/booking/i18n';
|
|
7
7
|
import { useBookingApp } from '@/contexts/BookingAppContext';
|
|
8
8
|
import type { Currency } from './CurrencySwitcher';
|
|
9
|
-
import type { Locale } from '@/lib/i18n/config';
|
|
9
|
+
import type { Locale } from '@/lib/booking/i18n/config';
|
|
10
10
|
|
|
11
11
|
export interface PriceBreakdownProps {
|
|
12
12
|
category: string;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { formatCurrencyAmount } from '@/lib/currency';
|
|
4
|
-
import type { PriceBreakdown as PriceBreakdownType } from '@/lib/pricing';
|
|
4
|
+
import type { PriceBreakdown as PriceBreakdownType } from '@/lib/booking/pricing';
|
|
5
5
|
import { PriceBreakdown } from './PriceBreakdown';
|
|
6
|
+
import { InfoTooltip } from './InfoTooltip';
|
|
6
7
|
import type { Currency } from './CurrencySwitcher';
|
|
7
|
-
import type { Locale } from '@/lib/i18n/config';
|
|
8
|
+
import type { Locale } from '@/lib/booking/i18n/config';
|
|
8
9
|
|
|
9
10
|
/** One row in the price summary: either a ticket line (with optional breakdown tooltip) or a simple line. */
|
|
10
11
|
export type PriceSummaryLine =
|
|
@@ -22,6 +23,8 @@ export type PriceSummaryLine =
|
|
|
22
23
|
/** Drives styling: discount (red, -), return add-on (green, +), default (stone). Receipt types: TICKET, FEE, RETURN_OPTION, PROMO_CODE, CANCELLATION_UPGRADE, TAX, etc. */
|
|
23
24
|
type?: string;
|
|
24
25
|
quantity?: number | null;
|
|
26
|
+
/** Optional tooltip text - when set, shows info icon next to label (e.g. for Moraine Lake Road Access Fee) */
|
|
27
|
+
tooltip?: string;
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
export interface PriceSummaryProps {
|
|
@@ -49,14 +52,14 @@ export interface PriceSummaryProps {
|
|
|
49
52
|
className?: string;
|
|
50
53
|
/** Optional: render between tax row and total row (e.g. promo code input in BookingFlow) */
|
|
51
54
|
extraBetweenTaxAndTotal?: React.ReactNode;
|
|
52
|
-
/** Optional: render after total row (e.g. price delta in change booking flow) */
|
|
53
|
-
extraAfterTotal?: React.ReactNode;
|
|
54
55
|
/** Subtotal row spacing: 'compact' for booking flow/Stripe modal, 'relaxed' for /manage (equal top/bottom) */
|
|
55
56
|
subtotalSpacing?: 'compact' | 'relaxed';
|
|
56
|
-
/** Deposit mode:
|
|
57
|
-
depositMode?: { totalLabel: string; balanceAmount: number };
|
|
57
|
+
/** Deposit mode: show Total (full), Deposit (amount due today), Remaining Balance */
|
|
58
|
+
depositMode?: { totalLabel: string; balanceAmount: number; fullTotalAmount: number };
|
|
58
59
|
/** When true (e.g. deposit flow with single line), hide redundant Subtotal row */
|
|
59
60
|
hideSubtotal?: boolean;
|
|
61
|
+
/** Overrides the final "Total" row label (e.g. change booking: amount due for the change) */
|
|
62
|
+
totalLabel?: string;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
function getLineAmountClass(type: string | undefined, amount: number): string {
|
|
@@ -101,13 +104,13 @@ export function PriceSummary({
|
|
|
101
104
|
t = (k) => k,
|
|
102
105
|
className = '',
|
|
103
106
|
extraBetweenTaxAndTotal,
|
|
104
|
-
extraAfterTotal,
|
|
105
107
|
subtotalSpacing = 'compact',
|
|
106
108
|
depositMode,
|
|
107
109
|
hideSubtotal = false,
|
|
110
|
+
totalLabel,
|
|
108
111
|
}: PriceSummaryProps) {
|
|
109
112
|
const textSize = size === 'sm' ? 'text-sm' : 'text-base';
|
|
110
|
-
const totalSize = size === 'sm' ? 'text-
|
|
113
|
+
const totalSize = size === 'sm' ? 'text-xl' : 'text-2xl';
|
|
111
114
|
const subtotalRowClass = subtotalSpacing === 'relaxed'
|
|
112
115
|
? 'pt-3 pb-3 mt-2 border-t border-stone-200'
|
|
113
116
|
: 'mt-2 pt-1.5 border-t border-stone-200';
|
|
@@ -130,7 +133,7 @@ export function PriceSummary({
|
|
|
130
133
|
/>
|
|
131
134
|
);
|
|
132
135
|
}
|
|
133
|
-
const { label, amount, type, quantity } = row;
|
|
136
|
+
const { label, amount, type, quantity, tooltip } = row;
|
|
134
137
|
// Receipt mode: insert Subtotal row before first TAX line
|
|
135
138
|
const isTaxLine = type === 'TAX';
|
|
136
139
|
const showSubtotalBeforeTax = isTaxLine && subtotal != null && subtotal > 0 && !subtotalShown;
|
|
@@ -144,9 +147,12 @@ export function PriceSummary({
|
|
|
144
147
|
</div>
|
|
145
148
|
)}
|
|
146
149
|
<div className={`flex justify-between gap-3 min-w-0 ${textSize}`}>
|
|
147
|
-
<span className="text-stone-600 min-w-0
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
<span className="text-stone-600 min-w-0 flex items-center gap-1">
|
|
151
|
+
<span className="min-w-0 truncate">
|
|
152
|
+
{label}
|
|
153
|
+
{type === 'TICKET' && quantity != null && quantity > 1 ? ` (x${quantity})` : ''}
|
|
154
|
+
</span>
|
|
155
|
+
{tooltip && <InfoTooltip text={tooltip} />}
|
|
150
156
|
</span>
|
|
151
157
|
<span className={`flex-shrink-0 whitespace-nowrap font-medium ${getLineAmountClass(type, amount)}`}>
|
|
152
158
|
{formatLineAmount('line', amount, type, currency, locale)}
|
|
@@ -186,26 +192,43 @@ export function PriceSummary({
|
|
|
186
192
|
{extraBetweenTaxAndTotal}
|
|
187
193
|
|
|
188
194
|
<div className="space-y-0">
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
195
|
+
{depositMode ? (
|
|
196
|
+
<>
|
|
197
|
+
<div className={`flex justify-between gap-3 pt-2 border-t border-stone-200 min-w-0 ${totalSize}`}>
|
|
198
|
+
<span className="font-semibold text-stone-900 min-w-0 truncate">
|
|
199
|
+
{t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total'}
|
|
200
|
+
</span>
|
|
201
|
+
<span className="flex-shrink-0 whitespace-nowrap font-semibold text-stone-900">
|
|
202
|
+
{formatCurrencyAmount(depositMode.fullTotalAmount, currency, locale)}
|
|
203
|
+
</span>
|
|
204
|
+
</div>
|
|
205
|
+
<div className={`flex justify-between gap-3 min-w-0 pt-1 ${textSize}`}>
|
|
206
|
+
<span className="text-stone-600 min-w-0 truncate">{depositMode.totalLabel}</span>
|
|
207
|
+
<span className="flex-shrink-0 whitespace-nowrap font-medium text-stone-700">
|
|
208
|
+
{formatCurrencyAmount(total, currency, locale)}
|
|
209
|
+
</span>
|
|
210
|
+
</div>
|
|
211
|
+
{depositMode.balanceAmount > 0 && (
|
|
212
|
+
<div className={`flex justify-between gap-3 min-w-0 pt-1 ${textSize}`}>
|
|
213
|
+
<span className="text-stone-600 min-w-0 truncate">{t('booking.remainingBalance') || 'Remaining Balance'}</span>
|
|
214
|
+
<span className="flex-shrink-0 whitespace-nowrap font-medium text-stone-700">
|
|
215
|
+
{formatCurrencyAmount(depositMode.balanceAmount, currency, locale)}
|
|
216
|
+
</span>
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
</>
|
|
220
|
+
) : (
|
|
221
|
+
<div className={`flex justify-between gap-3 pt-2 border-t border-stone-200 min-w-0 ${totalSize}`}>
|
|
222
|
+
<span className="font-semibold text-stone-900 min-w-0 truncate">
|
|
223
|
+
{totalLabel ??
|
|
224
|
+
(t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total')}
|
|
225
|
+
</span>
|
|
226
|
+
<span className="flex-shrink-0 whitespace-nowrap font-semibold text-stone-900">
|
|
227
|
+
{formatCurrencyAmount(total, currency, locale)}
|
|
204
228
|
</span>
|
|
205
229
|
</div>
|
|
206
230
|
)}
|
|
207
231
|
</div>
|
|
208
|
-
{extraAfterTotal}
|
|
209
232
|
</div>
|
|
210
233
|
);
|
|
211
234
|
}
|