@ticketboothapp/booking 1.2.25 → 1.2.27

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 (142) hide show
  1. package/package.json +11 -29
  2. package/src/components/booking/AddOnsSection.tsx +2 -2
  3. package/src/components/booking/AdminPaymentChoiceModal.tsx +1 -1
  4. package/src/components/booking/BookingDialog.tsx +31 -13
  5. package/src/components/booking/BookingFlow.tsx +32 -27
  6. package/src/components/booking/BookingFlowCollage.tsx +10 -6
  7. package/src/components/booking/BookingFlowPlaceholder.tsx +1 -1
  8. package/src/components/booking/BookingFlowPreview.tsx +18 -9
  9. package/src/components/booking/BookingProductGrid.tsx +55 -19
  10. package/src/components/booking/Calendar.module.css +19 -4
  11. package/src/components/booking/Calendar.tsx +13 -8
  12. package/src/components/booking/CancellationPolicySelector.tsx +2 -2
  13. package/src/components/booking/ChangeBookingDialog.tsx +22 -12
  14. package/src/components/booking/CheckoutForm.module.css +10 -0
  15. package/src/components/booking/CheckoutForm.tsx +10 -2
  16. package/src/components/booking/CheckoutModal.tsx +16 -14
  17. package/src/components/booking/DapFlowCollage.tsx +5 -2
  18. package/src/components/booking/DapTourDescription.tsx +4 -4
  19. package/src/components/booking/DependentAddOnBookingDialog.tsx +23 -16
  20. package/src/components/booking/DependentAddOnPaymentForm.tsx +10 -7
  21. package/src/components/booking/ItineraryBox.tsx +6 -6
  22. package/src/components/booking/ItineraryBuilder.tsx +1 -1
  23. package/src/components/booking/MealDrinkAddOnSelector.tsx +3 -3
  24. package/src/components/booking/PickupLocationSelector.tsx +20 -18
  25. package/src/components/booking/PickupTimeSelector.tsx +3 -3
  26. package/src/components/booking/PriceBreakdown.tsx +5 -5
  27. package/src/components/booking/PriceSummary.module.css +7 -0
  28. package/src/components/booking/PriceSummary.tsx +8 -7
  29. package/src/components/booking/PrivateShuttleBookingFlow.tsx +28 -19
  30. package/src/components/booking/PromoCodeInput.module.css +31 -25
  31. package/src/components/booking/PromoCodeInput.tsx +36 -24
  32. package/src/components/booking/ReturnTimeSelector.tsx +3 -3
  33. package/src/components/booking/TermsAcceptance.tsx +7 -2
  34. package/src/components/booking/TicketSelector.tsx +1 -1
  35. package/src/components/booking/TourDescription.tsx +11 -6
  36. package/src/components/booking/booking-flow.css +65 -4
  37. package/src/hooks/useBookingSourceMetadataFromLocation.ts +1 -1
  38. package/src/hooks/useIsBookingLaunchLive.ts +1 -1
  39. package/src/index.ts +26 -64
  40. package/src/providers/booking-dialog-provider.tsx +62 -53
  41. package/src/runtime/BookingHostContext.tsx +39 -0
  42. package/src/runtime/index.ts +13 -0
  43. package/src/runtime/types.ts +86 -0
  44. package/tsconfig.json +3 -5
  45. package/src/assets/icons/minus.svg +0 -7
  46. package/src/assets/icons/partner-logos/getyourguide.svg +0 -8
  47. package/src/assets/icons/plus.svg +0 -3
  48. package/src/colours.css +0 -23
  49. package/src/components/BookingDetails.module.css +0 -1591
  50. package/src/components/BookingDetails.tsx +0 -2264
  51. package/src/components/BookingWidget.tsx +0 -305
  52. package/src/components/ManageBookingView.tsx +0 -437
  53. package/src/components/PhoneInputWithCountry.module.css +0 -131
  54. package/src/components/PhoneInputWithCountry.tsx +0 -44
  55. package/src/components/PickupLocationDialog.module.css +0 -360
  56. package/src/components/PickupLocationDialog.tsx +0 -357
  57. package/src/components/PostBookingDependentAddOnUpsell.module.css +0 -174
  58. package/src/components/PostBookingDependentAddOnUpsell.tsx +0 -407
  59. package/src/components/button.css +0 -245
  60. package/src/components/button.tsx +0 -152
  61. package/src/components/colorable-svg.tsx +0 -29
  62. package/src/components/image.css +0 -29
  63. package/src/components/image.tsx +0 -113
  64. package/src/components/partner/PartnerBookingPage.module.css +0 -130
  65. package/src/components/partner/PartnerBookingPage.tsx +0 -390
  66. package/src/components/partner/PartnerBookingPageWithBrowserMetadata.tsx +0 -45
  67. package/src/components/product-tag.module.css +0 -30
  68. package/src/components/product-tag.tsx +0 -34
  69. package/src/components/product-theme-pages/image-modal.tsx +0 -248
  70. package/src/components/product-theme-pages/photo-gallery.module.css +0 -200
  71. package/src/components/terms/TermsContent.tsx +0 -178
  72. package/src/components/value-pill.module.css +0 -59
  73. package/src/components/value-pill.tsx +0 -46
  74. package/src/constants/images.ts +0 -556
  75. package/src/constants/pill-values.ts +0 -210
  76. package/src/constants/products.ts +0 -155
  77. package/src/contexts/AvailabilitiesCacheContext.tsx +0 -125
  78. package/src/contexts/CompanyContext.tsx +0 -70
  79. package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -61
  80. package/src/data/dap-descriptions/session-elopements.en.json +0 -60
  81. package/src/data/dap-descriptions/session-proposals.en.json +0 -60
  82. package/src/data/product-descriptions/afternoon-delight.en.json +0 -35
  83. package/src/data/product-descriptions/emerald-lake-escape.en.json +0 -68
  84. package/src/data/product-descriptions/lake-louise-adventure.en.json +0 -74
  85. package/src/data/product-descriptions/moraine-lake-adventure.en.json +0 -78
  86. package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +0 -65
  87. package/src/data/product-descriptions/moraine-lake-sunrise.en.json +0 -64
  88. package/src/data/product-descriptions/private-tour.en.json +0 -80
  89. package/src/data/product-descriptions/two-lakes-combo.en.json +0 -65
  90. package/src/data/products-config.json +0 -101
  91. package/src/lib/analytics.ts +0 -197
  92. package/src/lib/booking/booking-source.ts +0 -51
  93. package/src/lib/booking/checkout-breakdown.ts +0 -69
  94. package/src/lib/booking/correlation-id.ts +0 -46
  95. package/src/lib/booking/i18n/config.ts +0 -21
  96. package/src/lib/booking/i18n/index.tsx +0 -144
  97. package/src/lib/booking/i18n/messages/en.json +0 -236
  98. package/src/lib/booking/i18n/messages/fr.json +0 -236
  99. package/src/lib/booking/itinerary-display.ts +0 -36
  100. package/src/lib/booking/itinerary-labels.ts +0 -70
  101. package/src/lib/booking/location-calculations.ts +0 -43
  102. package/src/lib/booking/location-utils.ts +0 -165
  103. package/src/lib/booking/map-utils.ts +0 -153
  104. package/src/lib/booking/marker-icons.ts +0 -113
  105. package/src/lib/booking/normalize-booking-product-id.ts +0 -21
  106. package/src/lib/booking/pickup-location-types.ts +0 -25
  107. package/src/lib/booking/places-api.ts +0 -154
  108. package/src/lib/booking/pricing.ts +0 -466
  109. package/src/lib/booking/product-option-id.ts +0 -35
  110. package/src/lib/booking/source-metadata.ts +0 -226
  111. package/src/lib/booking/sunday-week.ts +0 -14
  112. package/src/lib/booking/theme.ts +0 -83
  113. package/src/lib/booking/trace-context.ts +0 -62
  114. package/src/lib/booking/utils.ts +0 -9
  115. package/src/lib/booking-api.ts +0 -1793
  116. package/src/lib/booking-constants.ts +0 -23
  117. package/src/lib/booking-ref.ts +0 -13
  118. package/src/lib/booking-types.ts +0 -36
  119. package/src/lib/currency.ts +0 -81
  120. package/src/lib/dap-descriptions.ts +0 -50
  121. package/src/lib/dap-itinerary-preview.ts +0 -315
  122. package/src/lib/dependent-add-on-api.ts +0 -434
  123. package/src/lib/env.ts +0 -96
  124. package/src/lib/firebase.ts +0 -20
  125. package/src/lib/job-application-api.ts +0 -83
  126. package/src/lib/manage-booking-embed-print.ts +0 -16
  127. package/src/lib/manage-booking-post-checkout.ts +0 -68
  128. package/src/lib/photo-dap-config.ts +0 -228
  129. package/src/lib/photo-packages.ts +0 -75
  130. package/src/lib/pickup/map-utils.ts +0 -56
  131. package/src/lib/pickup/marker-icons.ts +0 -19
  132. package/src/lib/product-descriptions.ts +0 -66
  133. package/src/lib/products-config.ts +0 -73
  134. package/src/providers/dependent-add-on-dialog-provider.tsx +0 -105
  135. package/src/radius.css +0 -5
  136. package/src/spacing.css +0 -7
  137. package/src/strings/en.json +0 -1774
  138. package/src/strings/es.json +0 -1573
  139. package/src/strings/fr.json +0 -1573
  140. package/src/strings/index.js +0 -23
  141. package/src/text-style.css +0 -56
  142. package/src/utils/currency-converter.ts +0 -101
@@ -2,17 +2,16 @@
2
2
 
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { AnimatePresence, motion } from 'framer-motion';
5
- import { getProductByIdOrSlug, buildMinimalProductFromConfig } from '@/lib/products-config';
6
- import { getProduct, type Product } from '@/lib/booking-api';
7
- import { ENV } from '@/lib/env';
8
- import { formatCurrencyAmount, type Currency } from '@/lib/currency';
5
+ import { getProduct, type Product } from '../../../../../src/lib/booking-api';
6
+ import { useBookingHost } from '../../runtime';
7
+ import { formatCurrencyAmount, type Currency } from '../../../../../src/lib/currency';
9
8
  import { BookingFlow, type ChangeFlowSelectionPreview } from './BookingFlow';
10
- import { useBookingSourceMetadataFromLocation } from '@/hooks/useBookingSourceMetadataFromLocation';
9
+ import { useBookingSourceMetadataFromLocation } from '../../hooks/useBookingSourceMetadataFromLocation';
11
10
  import styles from './BookingDialog.module.css';
12
11
  import './booking-flow.css';
13
- import type { BookingData } from '@/components/BookingDetails';
14
- import { getItineraryStepLabel } from '@/lib/booking/itinerary-display';
15
- import { effectiveProductOptionIdForChangeFlow } from '@/lib/booking/product-option-id';
12
+ import type { BookingData } from '../../../../../src/components/BookingDetails';
13
+ import { getItineraryStepLabel } from '../../../../../src/lib/booking/itinerary-display';
14
+ import { effectiveProductOptionIdForChangeFlow } from '../../../../../src/lib/booking/product-option-id';
16
15
 
17
16
  interface ChangeBookingDialogProps {
18
17
  isOpen: boolean;
@@ -240,6 +239,7 @@ export default function ChangeBookingDialog({
240
239
  onClose,
241
240
  onChangeCompleted,
242
241
  }: ChangeBookingDialogProps) {
242
+ const { env, catalog } = useBookingHost();
243
243
  const bookingSourceAttribution = useBookingSourceMetadataFromLocation();
244
244
  const [product, setProduct] = useState<Product | null>(null);
245
245
  const [error, setError] = useState<string | null>(null);
@@ -250,12 +250,22 @@ export default function ChangeBookingDialog({
250
250
 
251
251
  const bookingProductId = booking.productId;
252
252
  // Memoize so the load effect does not re-run every render (buildMinimalProductFromConfig returns a new object each call).
253
- const config = useMemo(() => getProductByIdOrSlug(bookingProductId), [bookingProductId]);
253
+ const config = useMemo(
254
+ () =>
255
+ catalog.getProductByIdOrSlug(bookingProductId) as {
256
+ display?: { slug?: string; shortName?: string };
257
+ productId?: string;
258
+ } | null,
259
+ [bookingProductId, catalog]
260
+ );
254
261
  const productSlug = config?.display?.slug ?? bookingProductId;
255
262
  const apiProductId = config?.productId ?? bookingProductId;
256
263
  const minimalProduct = useMemo(
257
- () => (config ? buildMinimalProductFromConfig(config, ENV.COMPANY_ID) : null),
258
- [config]
264
+ () =>
265
+ config && catalog.buildMinimalProductFromConfig
266
+ ? (catalog.buildMinimalProductFromConfig(config, env.COMPANY_ID) as Product)
267
+ : null,
268
+ [config, catalog, env.COMPANY_ID]
259
269
  );
260
270
 
261
271
  useEffect(() => {
@@ -263,7 +273,7 @@ export default function ChangeBookingDialog({
263
273
  let cancelled = false;
264
274
  setError(null);
265
275
  if (minimalProduct) setProduct(minimalProduct);
266
- getProduct(apiProductId, ENV.COMPANY_ID)
276
+ getProduct(apiProductId, env.COMPANY_ID)
267
277
  .then((p) => {
268
278
  if (!cancelled && p) setProduct(p);
269
279
  })
@@ -77,6 +77,16 @@
77
77
  margin-top: 1.5rem;
78
78
  }
79
79
 
80
+ /* Motion wrappers — no nested “card” chrome (flush with modal / form background) */
81
+ .pickupExpandedWrapper,
82
+ .pickupCollapsedWrapper {
83
+ margin: 0;
84
+ padding: 0;
85
+ background: transparent;
86
+ border: none;
87
+ box-shadow: none;
88
+ }
89
+
80
90
  .pickupHeader {
81
91
  display: flex;
82
92
  align-items: center;
@@ -4,9 +4,9 @@ import { PriceSummary, type PriceSummaryLine } from './PriceSummary';
4
4
  import { TermsAcceptance } from './TermsAcceptance';
5
5
  import { PickupLocationSelector } from './PickupLocationSelector';
6
6
  import type { Currency } from './CurrencySwitcher';
7
- import type { PickupLocation, Destination } from '@/lib/booking-api';
7
+ import type { PickupLocation, Destination } from '../../../../../src/lib/booking-api';
8
8
  import styles from './CheckoutForm.module.css';
9
- import Button, { ButtonHoverColor } from '@/components/button';
9
+ import { useBookingHost } from '../../runtime';
10
10
  import { AnimatePresence, motion } from 'framer-motion';
11
11
 
12
12
  type TranslationFn = (key: string, params?: Record<string, string>) => string;
@@ -113,6 +113,14 @@ export function CheckoutForm({
113
113
  attributionConfirmed = false,
114
114
  onAttributionConfirmedChange,
115
115
  }: CheckoutFormProps) {
116
+ const { slots } = useBookingHost();
117
+ const Button = slots.Button;
118
+ const ButtonHoverColor = slots.ButtonHoverColor as {
119
+ Turquoise: string;
120
+ White: string;
121
+ Orange: string;
122
+ };
123
+
116
124
  return (
117
125
  <div className={styles.section}>
118
126
  <div className={styles.summaryWrapper}>
@@ -1,16 +1,15 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, useRef, type ReactNode } from 'react';
3
+ import { useState, useEffect, useRef, useMemo, type ReactNode } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { loadStripe } from '@stripe/stripe-js';
6
6
  import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
7
- import { ENV } from '@/lib/env';
8
- import { storePendingPurchase, trackBeginCheckout } from '@/lib/analytics';
9
- import { formatCurrencyAmount } from '@/lib/currency';
7
+ import { useBookingHost } from '../../runtime';
8
+ import { formatCurrencyAmount } from '../../../../../src/lib/currency';
10
9
  import { PriceSummary, type PriceSummaryLine } from './PriceSummary';
11
10
  import type { Currency } from './CurrencySwitcher';
12
- import type { Locale } from '@/lib/booking/i18n/config';
13
- import type { OrderSummaryTicketLine, OrderSummaryFeeLine, PriceBreakdown as PriceBreakdownType } from '@/lib/booking/pricing';
11
+ import type { Locale } from '../../../../../src/lib/booking/i18n/config';
12
+ import type { OrderSummaryTicketLine, OrderSummaryFeeLine, PriceBreakdown as PriceBreakdownType } from '../../../../../src/lib/booking/pricing';
14
13
 
15
14
  export interface CheckoutModalLineItem {
16
15
  line: OrderSummaryTicketLine;
@@ -61,10 +60,6 @@ export interface CheckoutModalProps {
61
60
  };
62
61
  }
63
62
 
64
- const stripePromise = ENV.STRIPE_PUBLISHABLE_KEY
65
- ? loadStripe(ENV.STRIPE_PUBLISHABLE_KEY)
66
- : null;
67
-
68
63
  function CheckoutForm({
69
64
  successUrl,
70
65
  onClose,
@@ -84,6 +79,7 @@ function CheckoutForm({
84
79
  currency: Currency;
85
80
  locale: Locale;
86
81
  }) {
82
+ const { analytics } = useBookingHost();
87
83
  const stripe = useStripe();
88
84
  const elements = useElements();
89
85
  const [loading, setLoading] = useState(false);
@@ -103,7 +99,7 @@ function CheckoutForm({
103
99
  }
104
100
  onPaymentSubmitStart?.();
105
101
  // Store before redirect so success page can fire purchase event
106
- storePendingPurchase(total, currency);
102
+ analytics.storePendingPurchase(total, currency);
107
103
  const { error: confirmError } = await stripe.confirmPayment({
108
104
  elements,
109
105
  confirmParams: {
@@ -176,6 +172,12 @@ export function CheckoutModal({
176
172
  prePaymentPanel,
177
173
  changeTotals,
178
174
  }: CheckoutModalProps) {
175
+ const { env, analytics } = useBookingHost();
176
+ const stripePromise = useMemo(
177
+ () =>
178
+ env.STRIPE_PUBLISHABLE_KEY ? loadStripe(env.STRIPE_PUBLISHABLE_KEY) : null,
179
+ [env.STRIPE_PUBLISHABLE_KEY]
180
+ );
179
181
  const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
180
182
  const manageParams = new URLSearchParams({ reservationRef: reservationReference });
181
183
  if (customerLastName?.trim()) manageParams.set('lastName', customerLastName.trim());
@@ -195,10 +197,10 @@ export function CheckoutModal({
195
197
  qty: line.qty,
196
198
  price: line.itemTotal,
197
199
  }));
198
- trackBeginCheckout(total, currency, items);
200
+ analytics.trackBeginCheckout(total, currency, items);
199
201
  }
200
202
  if (!open) hasFiredBeginCheckout.current = false;
201
- }, [open, total, currency, ticketLines]);
203
+ }, [open, total, currency, ticketLines, analytics]);
202
204
 
203
205
  useEffect(() => {
204
206
  if (!open || !reservationExpiration) return;
@@ -233,7 +235,7 @@ export function CheckoutModal({
233
235
 
234
236
  if (!open) return null;
235
237
 
236
- if (!ENV.STRIPE_PUBLISHABLE_KEY) {
238
+ if (!env.STRIPE_PUBLISHABLE_KEY) {
237
239
  const noStripe = (
238
240
  <div
239
241
  className="booking-flow-root booking-flow-preflight fixed inset-0 z-[10050] flex items-center justify-center p-4 bg-black/50"
@@ -1,8 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useMemo, useState } from 'react';
4
- import ViaViaImage from '@/components/image';
5
- import ImageModal from '@/components/product-theme-pages/image-modal';
4
+ import { useBookingHost } from '../../runtime';
6
5
  import styles from './BookingFlowCollage.module.css';
7
6
 
8
7
  export interface DapFlowCollageProps {
@@ -13,6 +12,10 @@ export interface DapFlowCollageProps {
13
12
 
14
13
  /** Hero (left) + four-tile grid (right), same layout as BookingFlowCollage without video */
15
14
  export function DapFlowCollage({ imageIds, altPrefix = 'Experience' }: DapFlowCollageProps) {
15
+ const { slots: hostSlots } = useBookingHost();
16
+ const ViaViaImage = hostSlots.Image;
17
+ const ImageModal = hostSlots.ImageModal;
18
+
16
19
  const slots = useMemo(() => {
17
20
  const raw = imageIds.map((id) => id.trim()).filter(Boolean);
18
21
  if (raw.length === 0) return [];
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { useMemo } from 'react';
4
- import { TourDescription } from '@/components/booking/TourDescription';
5
- import { useLocale, useTranslations } from '@/lib/booking/i18n';
6
- import { getDapDescription } from '@/lib/dap-descriptions';
7
- import type { PhotoDapSlug } from '@/lib/photo-dap-config';
4
+ import { TourDescription } from './TourDescription';
5
+ import { useLocale, useTranslations } from '../../../../../src/lib/booking/i18n';
6
+ import { getDapDescription } from '../../../../../src/lib/dap-descriptions';
7
+ import type { PhotoDapSlug } from '../../../../../src/lib/photo-dap-config';
8
8
 
9
9
  export function DapTourDescription({
10
10
  slug,
@@ -11,8 +11,8 @@ import {
11
11
  import { format, parseISO } from 'date-fns';
12
12
  import {
13
13
  useDependentAddOnDialog,
14
- } from '@/providers/dependent-add-on-dialog-provider';
15
- import { ENV } from '@/lib/env';
14
+ } from '../../../../../src/providers/dependent-add-on-dialog-provider';
15
+ import { useBookingHost } from '../../runtime';
16
16
  import {
17
17
  getDependentAddOnAvailability,
18
18
  createDependentAddOnPaymentIntent,
@@ -20,20 +20,19 @@ import {
20
20
  type DependentAddOnCheckoutQuestion,
21
21
  type DependentAddOnSlot,
22
22
  type DapItineraryStep,
23
- } from '@/lib/dependent-add-on-api';
24
- import { dapItineraryStepsToDisplay } from '@/lib/dap-itinerary-preview';
25
- import { DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION } from '@/lib/photo-dap-config';
26
- import { CheckoutModal, type CheckoutModalLineItem } from '@/components/booking/CheckoutModal';
27
- import { DapFlowCollage } from '@/components/booking/DapFlowCollage';
28
- import { DapTourDescription } from '@/components/booking/DapTourDescription';
23
+ } from '../../../../../src/lib/dependent-add-on-api';
24
+ import { dapItineraryStepsToDisplay } from '../../../../../src/lib/dap-itinerary-preview';
25
+ import { DEFAULT_PHOTO_DAP_CANCELLATION_DAYS_BEFORE_SESSION } from '../../../../../src/lib/photo-dap-config';
26
+ import { CheckoutModal, type CheckoutModalLineItem } from './CheckoutModal';
27
+ import { DapFlowCollage } from './DapFlowCollage';
28
+ import { DapTourDescription } from './DapTourDescription';
29
29
  import {
30
30
  ItineraryReadOnlySummary,
31
31
  type ItineraryReadOnlyPhotoPreview,
32
- } from '@/components/booking/ItineraryBox';
33
- import { formatBookingRefForDisplay } from '@/lib/booking-ref';
34
- import type { Currency } from '@/lib/currency';
35
- import { useLocale, useTranslations } from '@/lib/booking/i18n';
36
- import Button, { ButtonHoverColor } from '@/components/button';
32
+ } from './ItineraryBox';
33
+ import { formatBookingRefForDisplay } from '../../../../../src/lib/booking-ref';
34
+ import type { Currency } from '../../../../../src/lib/currency';
35
+ import { useLocale, useTranslations } from '../../../../../src/lib/booking/i18n';
37
36
  import checkoutFormStyles from './CheckoutForm.module.css';
38
37
  import returnTimeStyles from './ReturnTimeSelector.module.css';
39
38
  import bookingStyles from './BookingDialog.module.css';
@@ -200,6 +199,14 @@ function dapSlotsPanelKey(ref: string, productOptionId: string): string {
200
199
  }
201
200
 
202
201
  export default function DependentAddOnBookingDialog() {
202
+ const { env, slots: hostSlots } = useBookingHost();
203
+ const Button = hostSlots.Button;
204
+ const ButtonHoverColor = hostSlots.ButtonHoverColor as {
205
+ Turquoise: string;
206
+ White: string;
207
+ Orange: string;
208
+ };
209
+
203
210
  const { t } = useTranslations();
204
211
  const { locale } = useLocale();
205
212
  const { isOpen, payload, close } = useDependentAddOnDialog();
@@ -391,7 +398,7 @@ export default function DependentAddOnBookingDialog() {
391
398
  void (async () => {
392
399
  try {
393
400
  const preview = await getDependentAddOnAvailability({
394
- companyId: ENV.COMPANY_ID,
401
+ companyId: env.COMPANY_ID,
395
402
  primaryBookingReference: refTrim,
396
403
  lastName: lastNameTrim,
397
404
  dependentAddOnProductId: payload.dependentAddOnProductId,
@@ -494,7 +501,7 @@ export default function DependentAddOnBookingDialog() {
494
501
  const optKey = (productOptionId ?? '').trim();
495
502
  try {
496
503
  const avail = await getDependentAddOnAvailability({
497
- companyId: ENV.COMPANY_ID,
504
+ companyId: env.COMPANY_ID,
498
505
  primaryBookingReference: ref,
499
506
  lastName,
500
507
  dependentAddOnProductId: payload.dependentAddOnProductId,
@@ -640,7 +647,7 @@ export default function DependentAddOnBookingDialog() {
640
647
  setPreparingPayment(true);
641
648
  try {
642
649
  const pi = await createDependentAddOnPaymentIntent({
643
- companyId: ENV.COMPANY_ID,
650
+ companyId: env.COMPANY_ID,
644
651
  primaryBookingReference: ref,
645
652
  lastName: primaryBookingLastName.trim(),
646
653
  dependentAddOnProductId: payload.dependentAddOnProductId,
@@ -1,16 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { useState } from 'react';
3
+ import { useState, useMemo } from 'react';
4
4
  import { loadStripe } from '@stripe/stripe-js';
5
5
  import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
6
- import { ENV } from '@/lib/env';
7
- import { formatCurrencyAmount, type Currency } from '@/lib/currency';
6
+ import { useBookingHost } from '../../runtime';
7
+ import { formatCurrencyAmount, type Currency } from '../../../../../src/lib/currency';
8
8
  import bookingStyles from './BookingDialog.module.css';
9
9
 
10
- const stripePromise = ENV.STRIPE_PUBLISHABLE_KEY
11
- ? loadStripe(ENV.STRIPE_PUBLISHABLE_KEY)
12
- : null;
13
-
14
10
  function toCurrency(code: string | undefined): Currency {
15
11
  const c = (code ?? 'CAD').toUpperCase();
16
12
  if (c === 'USD' || c === 'EUR' || c === 'GBP' || c === 'AUD' || c === 'CAD') {
@@ -95,6 +91,13 @@ export function DependentAddOnPaymentForm({
95
91
  onSuccess: () => void;
96
92
  onError: (message: string) => void;
97
93
  }) {
94
+ const { env } = useBookingHost();
95
+ const stripePromise = useMemo(
96
+ () =>
97
+ env.STRIPE_PUBLISHABLE_KEY ? loadStripe(env.STRIPE_PUBLISHABLE_KEY) : null,
98
+ [env.STRIPE_PUBLISHABLE_KEY]
99
+ );
100
+
98
101
  if (!stripePromise) {
99
102
  return (
100
103
  <p className={bookingStyles.dapStripeNotice}>
@@ -2,12 +2,12 @@
2
2
 
3
3
  import { Fragment, useRef, useEffect, useState } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
- import type { ItineraryDisplayStep } from '@/lib/booking-api';
6
- import { ItineraryStepType as StepType } from '@/lib/booking-api';
7
- import { getStepLabel } from '@/lib/booking/itinerary-labels';
8
- import type { PickupLocation } from '@/lib/booking-api';
9
- import type { DapItineraryStep } from '@/lib/dap-itinerary-preview';
10
- import { getPhotoSessionInsertPosition } from '@/lib/dap-itinerary-preview';
5
+ import type { ItineraryDisplayStep } from '../../../../../src/lib/booking-api';
6
+ import { ItineraryStepType as StepType } from '../../../../../src/lib/booking-api';
7
+ import { getStepLabel } from '../../../../../src/lib/booking/itinerary-labels';
8
+ import type { PickupLocation } from '../../../../../src/lib/booking-api';
9
+ import type { DapItineraryStep } from '../../../../../src/lib/dap-itinerary-preview';
10
+ import { getPhotoSessionInsertPosition } from '../../../../../src/lib/dap-itinerary-preview';
11
11
  import styles from './ItineraryBox.module.css';
12
12
 
13
13
  type TranslationFn = (key: string, params?: Record<string, string>) => string;
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import type { ItineraryBuilderDestination } from '@/lib/booking-api';
3
+ import type { ItineraryBuilderDestination } from '../../../../../src/lib/booking-api';
4
4
 
5
5
  interface ItineraryBuilderProps {
6
6
  /** Shared destinations from product.itineraryBuilder */
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { useMemo, useCallback } from 'react';
4
- import { formatCurrencyAmount } from '@/lib/currency';
5
- import type { AddOn, AddOnVariant } from '@/lib/booking-api';
4
+ import { formatCurrencyAmount } from '../../../../../src/lib/currency';
5
+ import type { AddOn, AddOnVariant } from '../../../../../src/lib/booking-api';
6
6
  import type { Currency } from './CurrencySwitcher';
7
- import type { Locale } from '@/lib/booking/i18n/config';
7
+ import type { Locale } from '../../../../../src/lib/booking/i18n/config';
8
8
 
9
9
  export interface AddOnSelection {
10
10
  addOnId: string;
@@ -2,38 +2,38 @@
2
2
 
3
3
  import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
4
4
  import { GoogleMap, Marker, InfoWindow, useJsApiLoader } from '@react-google-maps/api';
5
- import type { PickupLocation, Destination } from '@/lib/booking-api';
5
+ import type { PickupLocation, Destination } from '../../../../../src/lib/booking-api';
6
6
  import {
7
7
  formatDistance,
8
8
  formatTime,
9
9
  geocodeAddress,
10
10
  isWithinPrivateShuttleServiceArea,
11
- } from '@/lib/booking/location-utils';
11
+ } from '../../../../../src/lib/booking/location-utils';
12
12
  import {
13
13
  calculateNearbyLocations,
14
14
  isExactMatch,
15
- } from '@/lib/booking/location-calculations';
15
+ } from '../../../../../src/lib/booking/location-calculations';
16
16
  import {
17
17
  getAutocompleteSuggestions,
18
18
  getPlaceDetails,
19
19
  type AutocompleteSuggestion,
20
- } from '@/lib/booking/places-api';
20
+ } from '../../../../../src/lib/booking/places-api';
21
21
  import {
22
22
  createDistanceMarkerIcon,
23
23
  createPinMarkerIcon,
24
24
  createSearchedLocationPinIcon,
25
25
  createDestinationMarkerIcon,
26
- } from '@/lib/booking/marker-icons';
27
- import { ENV } from '@/lib/env';
26
+ } from '../../../../../src/lib/booking/marker-icons';
27
+ import { useBookingHost } from '../../runtime';
28
28
  import {
29
29
  calculateMapCenter,
30
30
  calculateMapBounds,
31
31
  getMapOptions,
32
32
  panToLocationIfNeeded,
33
- } from '@/lib/booking/map-utils';
34
- import type { SearchedLocation, NearbyLocation } from '@/lib/booking/pickup-location-types';
35
- import { useTranslations } from '@/lib/booking/i18n';
36
- import { useBookingApp } from '@/contexts/BookingAppContext';
33
+ } from '../../../../../src/lib/booking/map-utils';
34
+ import type { SearchedLocation, NearbyLocation } from '../../../../../src/lib/booking/pickup-location-types';
35
+ import { useTranslations } from '../../../../../src/lib/booking/i18n';
36
+ import { useBookingApp } from '../../contexts/BookingAppContext';
37
37
  import styles from './PickupLocationSelector.module.css';
38
38
 
39
39
  export interface CustomPickupLocation {
@@ -274,9 +274,10 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
274
274
  const searchInputRef = useRef<HTMLInputElement | null>(null);
275
275
  const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
276
276
 
277
+ const { env } = useBookingHost();
277
278
  const { googleMapsApiKey: keyFromContext } = useBookingApp();
278
279
  const keyFromWindow = typeof window !== 'undefined' ? (window as unknown as { __TICKETBOOTH_GOOGLE_MAPS_API_KEY__?: string }).__TICKETBOOTH_GOOGLE_MAPS_API_KEY__ : undefined;
279
- const googleMapsApiKey = keyFromContext ?? keyFromWindow ?? ENV.GOOGLE_MAPS_API_KEY;
280
+ const googleMapsApiKey = keyFromContext ?? keyFromWindow ?? env.GOOGLE_MAPS_API_KEY;
280
281
 
281
282
  // ============ Google Maps API Loading ============
282
283
  const { isLoaded, loadError } = useJsApiLoader({
@@ -1119,7 +1120,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1119
1120
  {/* When "I don't know" filter is active, show only that option (unless hidden) */}
1120
1121
  {!hideSkipOption && dontKnowFilterActive ? (
1121
1122
  <label
1122
- className="flex items-start gap-2 cursor-pointer p-2.5 rounded-lg hover:bg-stone-50 border border-stone-200"
1123
+ className="flex items-start gap-2 cursor-pointer p-2.5 rounded-lg hover:bg-stone-50"
1123
1124
  onClick={(e) => {
1124
1125
  e.preventDefault();
1125
1126
  handleSkip();
@@ -1146,7 +1147,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1146
1147
  <>
1147
1148
  {/* Custom address option - show at TOP when user searched (Private Shuttle allows custom), and within service area if restricted */}
1148
1149
  {allowCustomLocation && searchedLocation && !exactMatchLocationId && !cityFilter && isCustomLocationInServiceArea && (
1149
- <label className="flex items-start gap-2 cursor-pointer p-3 rounded-lg hover:bg-emerald-50/50 border-2 border-emerald-500 bg-emerald-50/50 shadow-sm" onMouseEnter={() => { setHoveredMarker('searched'); mapRef.current && searchedLocation.coordinates && panToLocationIfNeeded(mapRef.current, searchedLocation.coordinates); }} onMouseLeave={() => setHoveredMarker(null)}>
1150
+ <label className="flex items-start gap-2 cursor-pointer p-3 rounded-lg hover:bg-emerald-50/60 bg-emerald-50/50 shadow-sm" onMouseEnter={() => { setHoveredMarker('searched'); mapRef.current && searchedLocation.coordinates && panToLocationIfNeeded(mapRef.current, searchedLocation.coordinates); }} onMouseLeave={() => setHoveredMarker(null)}>
1150
1151
  <input type="radio" name="pickup-location" checked={selectedCustomAddress === searchedLocation.address} onChange={() => handleCustomLocationSelect(searchedLocation.address, searchedLocation.coordinates ?? undefined)} className="mt-1 w-4 h-4 text-emerald-600 focus:ring-emerald-500 shrink-0" />
1151
1152
  <div className="flex-1 min-w-0">
1152
1153
  <p className="font-semibold text-emerald-800 text-sm">{t('pickup.useThisAddress')}</p>
@@ -1159,10 +1160,10 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1159
1160
  const isNearby = nearbyLocations.length > 0 && nearbyLocations.some(n => n.id === location.id);
1160
1161
  const isPartnerHighlighted = highlightedPickupLocationIdSet.has(location.id);
1161
1162
  return (
1162
- <label key={location.id} className={`flex items-start gap-2 cursor-pointer p-2.5 rounded-lg border ${
1163
+ <label key={location.id} className={`flex items-start gap-2 cursor-pointer p-2.5 rounded-lg ${
1163
1164
  isPartnerHighlighted
1164
- ? 'border-emerald-300 bg-emerald-50/40 hover:bg-emerald-50'
1165
- : 'border-stone-200 hover:bg-stone-50'
1165
+ ? 'bg-emerald-50/40 hover:bg-emerald-50'
1166
+ : 'hover:bg-stone-50'
1166
1167
  }`} onMouseEnter={() => { setHoveredMarker(location.id); mapRef.current && location.coordinates && panToLocationIfNeeded(mapRef.current, location.coordinates); }} onMouseLeave={() => setHoveredMarker(null)}>
1167
1168
  <input type="radio" name="pickup-location" checked={selectedLocationId === location.id} onChange={() => handleLocationSelect(location.id, location.name)} className="mt-1 w-4 h-4 text-emerald-600 focus:ring-emerald-500 shrink-0" />
1168
1169
  <div className="flex-1 min-w-0">
@@ -1202,7 +1203,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1202
1203
  {/* I don't know - only at end of list when no filters are selected (unless hidden) */}
1203
1204
  {!hideSkipOption && selectedFilters.size === 0 && (
1204
1205
  <label
1205
- className="flex items-start gap-2 cursor-pointer p-2.5 rounded-lg hover:bg-stone-50 border border-stone-200"
1206
+ className="flex items-start gap-2 cursor-pointer p-2.5 rounded-lg hover:bg-stone-50"
1206
1207
  onClick={(e) => {
1207
1208
  e.preventDefault();
1208
1209
  handleSkip();
@@ -1527,10 +1528,11 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
1527
1528
  * don't hit ApiProjectMapError.
1528
1529
  */
1529
1530
  export function PickupLocationSelector(props: PickupLocationSelectorProps) {
1531
+ const { env } = useBookingHost();
1530
1532
  const { t } = useTranslations();
1531
1533
  const { googleMapsApiKey: keyFromContext } = useBookingApp();
1532
1534
  const keyFromWindow = typeof window !== 'undefined' ? (window as unknown as { __TICKETBOOTH_GOOGLE_MAPS_API_KEY__?: string }).__TICKETBOOTH_GOOGLE_MAPS_API_KEY__ : undefined;
1533
- const mapsKey = keyFromContext ?? keyFromWindow ?? ENV.GOOGLE_MAPS_API_KEY;
1535
+ const mapsKey = keyFromContext ?? keyFromWindow ?? env.GOOGLE_MAPS_API_KEY;
1534
1536
  const hasMapsKey = Boolean(mapsKey?.trim());
1535
1537
  const showMapsDebug =
1536
1538
  typeof window !== 'undefined' && new URLSearchParams(window.location.search).get('maps_debug') === '1';
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import type { Availability } from '@/lib/booking-api';
4
- import type { ProductOption } from '@/lib/booking-api';
3
+ import type { Availability } from '../../../../../src/lib/booking-api';
4
+ import type { ProductOption } from '../../../../../src/lib/booking-api';
5
5
  import styles from './PickupTimeSelector.module.css';
6
6
 
7
7
  type TranslationFn = (key: string, params?: Record<string, string | number>) => string;
@@ -98,7 +98,7 @@ export function PickupTimeSelector({
98
98
  {t('booking.soldOut')}
99
99
  </div>
100
100
  )}
101
- {isInsufficientForParty && !isAdmin && (
101
+ {isInsufficientForParty && !isSoldOut && !isAdmin && (
102
102
  <div className={styles.soldOut}>
103
103
  {`Only ${timeInfo.vacancies} spot${timeInfo.vacancies === 1 ? '' : 's'} left, decrease your ticket count below to select this time`}
104
104
  </div>
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
- import { formatCurrencyAmount } from '@/lib/currency';
5
- import type { PriceBreakdown as PriceBreakdownType } from '@/lib/booking/pricing';
6
- import { useTranslations } from '@/lib/booking/i18n';
7
- import { useBookingApp } from '@/contexts/BookingAppContext';
4
+ import { formatCurrencyAmount } from '../../../../../src/lib/currency';
5
+ import type { PriceBreakdown as PriceBreakdownType } from '../../../../../src/lib/booking/pricing';
6
+ import { useTranslations } from '../../../../../src/lib/booking/i18n';
7
+ import { useBookingApp } from '../../contexts/BookingAppContext';
8
8
  import type { Currency } from './CurrencySwitcher';
9
- import type { Locale } from '@/lib/booking/i18n/config';
9
+ import type { Locale } from '../../../../../src/lib/booking/i18n/config';
10
10
 
11
11
  export interface PriceBreakdownProps {
12
12
  category: string;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Receipt rules only where the design always had a line (subtotal row, total row).
3
+ * Tax/discount stay plain rows + spacing — lines above promo come from PromoCodeInput.promoRow.
4
+ */
5
+ .ruleAbove {
6
+ border-top: 1px solid var(--booking-stone-200, #e7e5e4);
7
+ }
@@ -1,11 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { formatCurrencyAmount } from '@/lib/currency';
4
- import type { PriceBreakdown as PriceBreakdownType } from '@/lib/booking/pricing';
3
+ import { formatCurrencyAmount } from '../../../../../src/lib/currency';
4
+ import type { PriceBreakdown as PriceBreakdownType } from '../../../../../src/lib/booking/pricing';
5
5
  import { PriceBreakdown } from './PriceBreakdown';
6
6
  import { InfoTooltip } from './InfoTooltip';
7
7
  import type { Currency } from './CurrencySwitcher';
8
- import type { Locale } from '@/lib/booking/i18n/config';
8
+ import type { Locale } from '../../../../../src/lib/booking/i18n/config';
9
+ import styles from './PriceSummary.module.css';
9
10
 
10
11
  /** One row in the price summary: either a ticket line (with optional breakdown tooltip) or a simple line. */
11
12
  export type PriceSummaryLine =
@@ -112,8 +113,8 @@ export function PriceSummary({
112
113
  const textSize = size === 'sm' ? 'text-sm' : 'text-base';
113
114
  const totalSize = size === 'sm' ? 'text-xl' : 'text-2xl';
114
115
  const subtotalRowClass = subtotalSpacing === 'relaxed'
115
- ? 'pt-3 pb-3 mt-2 border-t border-stone-200'
116
- : 'mt-2 pt-1.5 border-t border-stone-200';
116
+ ? `pt-3 pb-3 mt-2 ${styles.ruleAbove}`
117
+ : `mt-2 pt-1.5 ${styles.ruleAbove}`;
117
118
 
118
119
  let subtotalShown = false;
119
120
 
@@ -194,7 +195,7 @@ export function PriceSummary({
194
195
  <div className="space-y-0">
195
196
  {depositMode ? (
196
197
  <>
197
- <div className={`flex justify-between gap-3 pt-2 border-t border-stone-200 min-w-0 ${totalSize}`}>
198
+ <div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove}`}>
198
199
  <span className="font-semibold text-stone-900 min-w-0 truncate">
199
200
  {t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total'}
200
201
  </span>
@@ -218,7 +219,7 @@ export function PriceSummary({
218
219
  )}
219
220
  </>
220
221
  ) : (
221
- <div className={`flex justify-between gap-3 pt-2 border-t border-stone-200 min-w-0 ${totalSize}`}>
222
+ <div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove}`}>
222
223
  <span className="font-semibold text-stone-900 min-w-0 truncate">
223
224
  {totalLabel ??
224
225
  (t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total')}