@ticketboothapp/booking 1.2.24 → 1.2.25-rc.0

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.
Files changed (158) hide show
  1. package/package.json +29 -2
  2. package/src/assets/icons/minus.svg +7 -0
  3. package/src/assets/icons/partner-logos/getyourguide.svg +8 -0
  4. package/src/assets/icons/plus.svg +3 -0
  5. package/src/colours.css +23 -0
  6. package/src/components/BookingDetails.module.css +1591 -0
  7. package/src/components/BookingDetails.tsx +2264 -0
  8. package/src/components/BookingWidget.tsx +302 -0
  9. package/src/components/ManageBookingView.tsx +437 -0
  10. package/src/components/PhoneInputWithCountry.module.css +131 -0
  11. package/src/components/PhoneInputWithCountry.tsx +44 -0
  12. package/src/components/PickupLocationDialog.module.css +360 -0
  13. package/src/components/PickupLocationDialog.tsx +357 -0
  14. package/src/components/PostBookingDependentAddOnUpsell.module.css +174 -0
  15. package/src/components/PostBookingDependentAddOnUpsell.tsx +407 -0
  16. package/src/components/booking/AddOnsSection.module.css +10 -0
  17. package/src/components/booking/AddOnsSection.tsx +184 -0
  18. package/src/components/booking/AdminPaymentChoiceModal.tsx +98 -0
  19. package/src/components/booking/BookingDialog.module.css +643 -0
  20. package/src/components/booking/BookingDialog.tsx +356 -0
  21. package/src/components/booking/BookingFlow.tsx +4385 -0
  22. package/src/components/booking/BookingFlowCollage.module.css +148 -0
  23. package/src/components/booking/BookingFlowCollage.tsx +184 -0
  24. package/src/components/booking/BookingFlowPlaceholder.module.css +27 -0
  25. package/src/components/booking/BookingFlowPlaceholder.tsx +25 -0
  26. package/src/components/booking/BookingFlowPreview.tsx +51 -0
  27. package/src/components/booking/BookingProductGrid.module.css +359 -0
  28. package/src/components/booking/BookingProductGrid.tsx +497 -0
  29. package/src/components/booking/Calendar.module.css +616 -0
  30. package/src/components/booking/Calendar.tsx +1123 -0
  31. package/src/components/booking/CancellationPolicySelector.module.css +124 -0
  32. package/src/components/booking/CancellationPolicySelector.tsx +142 -0
  33. package/src/components/booking/ChangeBookingDialog.tsx +562 -0
  34. package/src/components/booking/CheckoutForm.module.css +244 -0
  35. package/src/components/booking/CheckoutForm.tsx +364 -0
  36. package/src/components/booking/CheckoutModal.tsx +451 -0
  37. package/src/components/booking/CurrencySwitcher.tsx +81 -0
  38. package/src/components/booking/DapFlowCollage.tsx +88 -0
  39. package/src/components/booking/DapTourDescription.tsx +35 -0
  40. package/src/components/booking/DependentAddOnBookingDialog.tsx +1350 -0
  41. package/src/components/booking/DependentAddOnPaymentForm.tsx +124 -0
  42. package/src/components/booking/ErrorBoundary.tsx +63 -0
  43. package/src/components/booking/InfoTooltip.tsx +108 -0
  44. package/src/components/booking/ItineraryBox.module.css +258 -0
  45. package/src/components/booking/ItineraryBox.tsx +550 -0
  46. package/src/components/booking/ItineraryBuilder.tsx +82 -0
  47. package/src/components/booking/ItineraryPlaceholder.module.css +45 -0
  48. package/src/components/booking/ItineraryPlaceholder.tsx +26 -0
  49. package/src/components/booking/MealDrinkAddOnSelector.tsx +338 -0
  50. package/src/components/booking/PickupLocationSelector.module.css +124 -0
  51. package/src/components/booking/PickupLocationSelector.tsx +1566 -0
  52. package/src/components/booking/PickupTimeSelector.module.css +134 -0
  53. package/src/components/booking/PickupTimeSelector.tsx +112 -0
  54. package/src/components/booking/PriceBreakdown.tsx +154 -0
  55. package/src/components/booking/PriceSummary.tsx +234 -0
  56. package/src/components/booking/PrivateShuttleBookingFlow.module.css +357 -0
  57. package/src/components/booking/PrivateShuttleBookingFlow.tsx +2662 -0
  58. package/src/components/booking/PromoCodeInput.module.css +166 -0
  59. package/src/components/booking/PromoCodeInput.tsx +99 -0
  60. package/src/components/booking/ReturnTimeSelector.module.css +173 -0
  61. package/src/components/booking/ReturnTimeSelector.tsx +145 -0
  62. package/src/components/booking/TermsAcceptance.tsx +111 -0
  63. package/src/components/booking/TicketSelector.module.css +164 -0
  64. package/src/components/booking/TicketSelector.tsx +199 -0
  65. package/src/components/booking/TourDescription.module.css +304 -0
  66. package/src/components/booking/TourDescription.tsx +273 -0
  67. package/src/components/booking/booking-flow-ui.ts +38 -0
  68. package/src/components/booking/booking-flow.css +944 -0
  69. package/src/components/button.css +245 -0
  70. package/src/components/button.tsx +152 -0
  71. package/src/components/colorable-svg.tsx +29 -0
  72. package/src/components/image.css +29 -0
  73. package/src/components/image.tsx +113 -0
  74. package/src/components/partner/PartnerBookingPage.module.css +130 -0
  75. package/src/components/partner/PartnerBookingPage.tsx +390 -0
  76. package/src/components/partner/PartnerBookingPageWithBrowserMetadata.tsx +45 -0
  77. package/src/components/product-tag.module.css +30 -0
  78. package/src/components/product-tag.tsx +34 -0
  79. package/src/components/product-theme-pages/image-modal.tsx +248 -0
  80. package/src/components/product-theme-pages/photo-gallery.module.css +200 -0
  81. package/src/components/terms/TermsContent.tsx +178 -0
  82. package/src/components/value-pill.module.css +59 -0
  83. package/src/components/value-pill.tsx +46 -0
  84. package/src/constants/images.ts +556 -0
  85. package/src/constants/pill-values.ts +210 -0
  86. package/src/constants/products.ts +155 -0
  87. package/src/contexts/AvailabilitiesCacheContext.tsx +125 -0
  88. package/src/contexts/BookingAppContext.tsx +134 -0
  89. package/src/contexts/CompanyContext.tsx +70 -0
  90. package/src/data/dap-descriptions/session-couples-families-friends.en.json +61 -0
  91. package/src/data/dap-descriptions/session-elopements.en.json +60 -0
  92. package/src/data/dap-descriptions/session-proposals.en.json +60 -0
  93. package/src/data/product-descriptions/afternoon-delight.en.json +35 -0
  94. package/src/data/product-descriptions/emerald-lake-escape.en.json +68 -0
  95. package/src/data/product-descriptions/lake-louise-adventure.en.json +74 -0
  96. package/src/data/product-descriptions/moraine-lake-adventure.en.json +78 -0
  97. package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +65 -0
  98. package/src/data/product-descriptions/moraine-lake-sunrise.en.json +64 -0
  99. package/src/data/product-descriptions/private-tour.en.json +80 -0
  100. package/src/data/product-descriptions/two-lakes-combo.en.json +65 -0
  101. package/src/data/products-config.json +101 -0
  102. package/src/hooks/useBookingSourceMetadataFromLocation.ts +21 -0
  103. package/src/hooks/useIsBookingLaunchLive.ts +49 -0
  104. package/src/index.ts +79 -0
  105. package/src/lib/analytics.ts +197 -0
  106. package/src/lib/booking/booking-source.ts +51 -0
  107. package/src/lib/booking/checkout-breakdown.ts +69 -0
  108. package/src/lib/booking/correlation-id.ts +46 -0
  109. package/src/lib/booking/i18n/config.ts +21 -0
  110. package/src/lib/booking/i18n/index.tsx +144 -0
  111. package/src/lib/booking/i18n/messages/en.json +236 -0
  112. package/src/lib/booking/i18n/messages/fr.json +236 -0
  113. package/src/lib/booking/itinerary-display.ts +36 -0
  114. package/src/lib/booking/itinerary-labels.ts +70 -0
  115. package/src/lib/booking/location-calculations.ts +43 -0
  116. package/src/lib/booking/location-utils.ts +165 -0
  117. package/src/lib/booking/map-utils.ts +153 -0
  118. package/src/lib/booking/marker-icons.ts +113 -0
  119. package/src/lib/booking/normalize-booking-product-id.ts +21 -0
  120. package/src/lib/booking/pickup-location-types.ts +25 -0
  121. package/src/lib/booking/places-api.ts +154 -0
  122. package/src/lib/booking/pricing.ts +466 -0
  123. package/src/lib/booking/product-option-id.ts +35 -0
  124. package/src/lib/booking/source-metadata.ts +226 -0
  125. package/src/lib/booking/sunday-week.ts +14 -0
  126. package/src/lib/booking/theme.ts +83 -0
  127. package/src/lib/booking/trace-context.ts +62 -0
  128. package/src/lib/booking/utils.ts +9 -0
  129. package/src/lib/booking-api.ts +1793 -0
  130. package/src/lib/booking-constants.ts +23 -0
  131. package/src/lib/booking-ref.ts +13 -0
  132. package/src/lib/booking-types.ts +36 -0
  133. package/src/lib/currency.ts +81 -0
  134. package/src/lib/dap-descriptions.ts +50 -0
  135. package/src/lib/dap-itinerary-preview.ts +315 -0
  136. package/src/lib/dependent-add-on-api.ts +434 -0
  137. package/src/lib/env.ts +96 -0
  138. package/src/lib/firebase.ts +20 -0
  139. package/src/lib/job-application-api.ts +83 -0
  140. package/src/lib/manage-booking-embed-print.ts +16 -0
  141. package/src/lib/manage-booking-post-checkout.ts +68 -0
  142. package/src/lib/photo-dap-config.ts +228 -0
  143. package/src/lib/photo-packages.ts +75 -0
  144. package/src/lib/pickup/map-utils.ts +56 -0
  145. package/src/lib/pickup/marker-icons.ts +19 -0
  146. package/src/lib/product-descriptions.ts +66 -0
  147. package/src/lib/products-config.ts +73 -0
  148. package/src/providers/booking-dialog-provider.tsx +282 -0
  149. package/src/providers/dependent-add-on-dialog-provider.tsx +105 -0
  150. package/src/radius.css +5 -0
  151. package/src/spacing.css +7 -0
  152. package/src/strings/en.json +1774 -0
  153. package/src/strings/es.json +1573 -0
  154. package/src/strings/fr.json +1573 -0
  155. package/src/strings/index.js +23 -0
  156. package/src/text-style.css +56 -0
  157. package/src/utils/currency-converter.ts +101 -0
  158. package/tsconfig.json +8 -2
@@ -0,0 +1,302 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { CompanyProvider } from '@/contexts/CompanyContext';
5
+ import {
6
+ BookingAppProvider,
7
+ type BookingAppMode,
8
+ type BookingAppPermissions,
9
+ type ManageParams,
10
+ } from '@/contexts/BookingAppContext';
11
+ import { BookingFlow } from '@/components/booking/BookingFlow';
12
+ import { PrivateShuttleBookingFlow } from '@/components/booking/PrivateShuttleBookingFlow';
13
+ import { CurrencySwitcher, useCurrency, type Currency } from '@/components/booking/CurrencySwitcher';
14
+ import { ErrorBoundary } from '@/components/booking/ErrorBoundary';
15
+ import { getProduct, setPartnerPortalBookingJwtGetter, type Product } from '@/lib/booking-api';
16
+ import { getProductByIdOrSlug } from '@/lib/products-config';
17
+ import { ENV } from '@/lib/env';
18
+ import BookingProductGrid from '@/components/booking/BookingProductGrid';
19
+ import { BookingDialogProvider } from '@/providers/booking-dialog-provider';
20
+
21
+ type Step = 'products' | 'booking';
22
+
23
+ interface NavState {
24
+ step: Step;
25
+ selectedProduct: Product | null;
26
+ }
27
+
28
+ export interface BookingWidgetProps {
29
+ initialProductId?: string;
30
+ initialCurrency?: Currency;
31
+ showHeader?: boolean;
32
+ authToken?: string | null;
33
+ onProductSelect?: (product: Product) => void;
34
+ onBookingSuccess?: (data: { reservationReference: string; sessionId?: string }) => void;
35
+ onBack?: () => void;
36
+ className?: string;
37
+ mode?: BookingAppMode;
38
+ permissions?: Partial<BookingAppPermissions>;
39
+ googleMapsApiKey?: string;
40
+ onShowManage?: (params: ManageParams) => void;
41
+ getSuccessUrl?: (params: { reservationRef: string; lastName: string; focusDate?: string }) => string;
42
+ showLanguageSelector?: boolean;
43
+ initialProduct?: Product;
44
+ products?: Product[];
45
+ companyId?: string;
46
+ initialBooking?: {
47
+ bookingReference: string;
48
+ productId: string;
49
+ availabilityId?: string;
50
+ dateTime: string;
51
+ originalTotalAmount?: number;
52
+ originalCurrency?: string;
53
+ bookingItems: Array<{ category: string; count: number }>;
54
+ returnAvailabilityId?: string | null;
55
+ pickupLocationId?: string | null;
56
+ travelerHotel?: string | null;
57
+ startTime?: string | null;
58
+ privateShuttleDetails?: { passengerCount?: number };
59
+ cancellationPolicyId?: string | null;
60
+ promoCode?: string | null;
61
+ additionalHoursCount?: number | null;
62
+ addOnSelections?: Array<{ addOnId: string; variantId?: string; quantity?: number }> | null;
63
+ };
64
+ /** Last name on the booking — required for public change-quote APIs inside the flow. */
65
+ changeFlowLastName?: string;
66
+ }
67
+
68
+ /**
69
+ * Embeddable booking UI (product grid + flow). Used by provider-dashboard change-booking in “change mode”.
70
+ */
71
+ export function BookingWidget({
72
+ initialProductId,
73
+ initialProduct,
74
+ initialBooking,
75
+ changeFlowLastName = '',
76
+ products: productsProp,
77
+ companyId: _companyId,
78
+ initialCurrency = 'CAD',
79
+ showHeader = true,
80
+ authToken,
81
+ onProductSelect,
82
+ onBookingSuccess,
83
+ onBack,
84
+ className = '',
85
+ mode = 'standalone',
86
+ permissions = {},
87
+ googleMapsApiKey,
88
+ onShowManage,
89
+ getSuccessUrl,
90
+ showLanguageSelector = true,
91
+ }: BookingWidgetProps) {
92
+ const { currency, setCurrency } = useCurrency();
93
+
94
+ useEffect(() => {
95
+ setPartnerPortalBookingJwtGetter(() => authToken ?? null);
96
+ return () => setPartnerPortalBookingJwtGetter(() => null);
97
+ }, [authToken]);
98
+
99
+ useEffect(() => {
100
+ if (initialCurrency) setCurrency(initialCurrency);
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, []);
103
+
104
+ const isChangeMode = !!(initialProduct && initialBooking);
105
+
106
+ const flowInitialValues = initialBooking
107
+ ? {
108
+ bookingReference: initialBooking.bookingReference,
109
+ dateTime: initialBooking.dateTime,
110
+ availabilityId: initialBooking.availabilityId ?? null,
111
+ productOptionId: initialBooking.productId,
112
+ pickupLocationId: initialBooking.pickupLocationId ?? null,
113
+ returnAvailabilityId: initialBooking.returnAvailabilityId ?? null,
114
+ bookingItems: initialBooking.bookingItems,
115
+ addOnSelections: initialBooking.addOnSelections ?? null,
116
+ promoCode: initialBooking.promoCode ?? null,
117
+ cancellationPolicyId: initialBooking.cancellationPolicyId ?? null,
118
+ customer: changeFlowLastName ? { lastName: changeFlowLastName } : null,
119
+ }
120
+ : undefined;
121
+
122
+ const privateShuttleInitialValues = initialBooking
123
+ ? {
124
+ bookingReference: initialBooking.bookingReference,
125
+ dateTime: initialBooking.dateTime,
126
+ pickupLocationId: initialBooking.pickupLocationId ?? null,
127
+ customPickupAddress: initialBooking.travelerHotel ?? null,
128
+ passengers: initialBooking.privateShuttleDetails?.passengerCount ?? null,
129
+ additionalHoursCount: initialBooking.additionalHoursCount ?? null,
130
+ notes: null,
131
+ specialRequest: null,
132
+ }
133
+ : undefined;
134
+
135
+ const bookingSourceAttribution: import('@/lib/booking/source-metadata').BookingSourceMetadata = {
136
+ pagePath: '/provider-dashboard',
137
+ };
138
+
139
+ const [navState, setNavState] = useState<NavState>(() => {
140
+ if (isChangeMode && initialProduct) {
141
+ return { step: 'booking', selectedProduct: initialProduct };
142
+ }
143
+ return { step: 'products', selectedProduct: null };
144
+ });
145
+
146
+ const [error, setError] = useState('');
147
+
148
+ useEffect(() => {
149
+ if (!isChangeMode || !productsProp?.length || !initialProductId) return;
150
+ const product = productsProp.find(
151
+ (p) => p.productId === initialProductId || p.options?.some((o) => o.optionId === initialProductId)
152
+ );
153
+ if (product) setNavState({ step: 'booking', selectedProduct: product });
154
+ }, [isChangeMode, initialProductId, productsProp]);
155
+
156
+ useEffect(() => {
157
+ if (isChangeMode || !initialProductId) return;
158
+ let cancelled = false;
159
+ (async () => {
160
+ const config = getProductByIdOrSlug(initialProductId);
161
+ const apiProductId = config?.productId ?? initialProductId;
162
+ try {
163
+ const p = await getProduct(apiProductId, ENV.COMPANY_ID);
164
+ if (!cancelled && p) setNavState({ step: 'booking', selectedProduct: p });
165
+ } catch (e) {
166
+ if (!cancelled) setError(e instanceof Error ? e.message : 'Failed to load product');
167
+ }
168
+ })();
169
+ return () => {
170
+ cancelled = true;
171
+ };
172
+ }, [isChangeMode, initialProductId]);
173
+
174
+ const handleSelectProduct = useCallback(
175
+ (product: Product) => {
176
+ setNavState({ step: 'booking', selectedProduct: product });
177
+ onProductSelect?.(product);
178
+ },
179
+ [onProductSelect]
180
+ );
181
+
182
+ const handleBookProductId = useCallback(
183
+ async (productSlugOrId: string) => {
184
+ const config = getProductByIdOrSlug(productSlugOrId);
185
+ const apiProductId = config?.productId ?? productSlugOrId;
186
+ try {
187
+ const p = await getProduct(apiProductId, ENV.COMPANY_ID);
188
+ if (p) handleSelectProduct(p);
189
+ } catch (e) {
190
+ setError(e instanceof Error ? e.message : 'Failed to load product');
191
+ }
192
+ },
193
+ [handleSelectProduct]
194
+ );
195
+
196
+ const handleBack = useCallback(() => {
197
+ setNavState({ step: 'products', selectedProduct: null });
198
+ onBack?.();
199
+ }, [onBack]);
200
+
201
+ const handleBookingSuccess = useCallback(
202
+ (data: { reservationReference: string; sessionId?: string }) => {
203
+ onBookingSuccess?.(data);
204
+ },
205
+ [onBookingSuccess]
206
+ );
207
+
208
+ return (
209
+ <CompanyProvider>
210
+ <BookingAppProvider
211
+ mode={mode}
212
+ permissions={permissions}
213
+ googleMapsApiKey={googleMapsApiKey}
214
+ onShowManage={onShowManage}
215
+ getSuccessUrl={getSuccessUrl}
216
+ showLanguageSelector={showLanguageSelector}
217
+ >
218
+ <BookingDialogProvider>
219
+ <div className={`min-h-0 overflow-x-hidden ${className}`}>
220
+ {showHeader && (
221
+ <header className="py-4 w-full overflow-hidden" style={{ backgroundColor: 'var(--booking-header-bg)', color: 'var(--booking-header-text)' }}>
222
+ <div className="max-w-4xl mx-auto px-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 min-w-0">
223
+ <h1 className="text-xl font-semibold shrink-0" style={{ fontFamily: 'var(--booking-font-sans)' }}>
224
+ Via Via Moraine Lake
225
+ </h1>
226
+ <div className="flex flex-wrap items-center gap-2 sm:gap-4 min-w-0">
227
+ <CurrencySwitcher currency={currency} onCurrencyChange={setCurrency} />
228
+ </div>
229
+ </div>
230
+ </header>
231
+ )}
232
+
233
+ {!showHeader && (
234
+ <div
235
+ className="border-b px-4 py-2 flex flex-wrap items-center justify-end gap-2 min-w-0"
236
+ style={{ borderColor: 'var(--booking-border)', backgroundColor: 'var(--booking-surface)' }}
237
+ >
238
+ <CurrencySwitcher currency={currency} onCurrencyChange={setCurrency} />
239
+ </div>
240
+ )}
241
+
242
+ <main className={`max-w-4xl mx-auto px-4 py-8 ${!showHeader ? 'pt-4' : ''}`} style={{ fontFamily: 'var(--booking-font-sans)' }}>
243
+ <div
244
+ className="overflow-visible p-8"
245
+ style={{
246
+ backgroundColor: 'var(--booking-surface)',
247
+ borderRadius: 'var(--booking-radius)',
248
+ boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)',
249
+ }}
250
+ >
251
+ {error ? (
252
+ <div
253
+ className="p-4 rounded-lg"
254
+ style={{
255
+ backgroundColor: 'var(--booking-error-bg)',
256
+ borderWidth: '1px',
257
+ borderStyle: 'solid',
258
+ borderColor: 'var(--booking-error-border)',
259
+ color: 'var(--booking-error-text)',
260
+ }}
261
+ >
262
+ {error}
263
+ </div>
264
+ ) : navState.step === 'products' ? (
265
+ <BookingProductGrid
266
+ initialFilterId="all"
267
+ bookOnTileClick
268
+ onBookProduct={handleBookProductId}
269
+ />
270
+ ) : navState.selectedProduct ? (
271
+ <ErrorBoundary>
272
+ {navState.selectedProduct.productType === 'PRIVATE_SHUTTLE' ? (
273
+ <PrivateShuttleBookingFlow
274
+ product={navState.selectedProduct}
275
+ onBack={handleBack}
276
+ currency={currency}
277
+ onSuccess={handleBookingSuccess}
278
+ mode={isChangeMode ? 'change' : 'standard'}
279
+ initialValues={isChangeMode ? privateShuttleInitialValues : undefined}
280
+ bookingSourceAttribution={bookingSourceAttribution}
281
+ />
282
+ ) : (
283
+ <BookingFlow
284
+ product={navState.selectedProduct}
285
+ onBack={handleBack}
286
+ currency={currency}
287
+ onSuccess={handleBookingSuccess}
288
+ mode={isChangeMode ? 'change' : 'standard'}
289
+ initialValues={isChangeMode ? flowInitialValues : undefined}
290
+ bookingSourceAttribution={bookingSourceAttribution}
291
+ />
292
+ )}
293
+ </ErrorBoundary>
294
+ ) : null}
295
+ </div>
296
+ </main>
297
+ </div>
298
+ </BookingDialogProvider>
299
+ </BookingAppProvider>
300
+ </CompanyProvider>
301
+ );
302
+ }