@ticketboothapp/booking 1.2.25-rc.0 → 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 -302
  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
package/package.json CHANGED
@@ -1,44 +1,26 @@
1
1
  {
2
2
  "name": "@ticketboothapp/booking",
3
- "version": "1.2.25-rc.0",
3
+ "version": "1.2.27",
4
4
  "private": false,
5
- "sideEffects": false,
5
+ "sideEffects": [
6
+ "**/*.css",
7
+ "**/*.module.css"
8
+ ],
6
9
  "publishConfig": {
7
10
  "access": "public"
8
11
  },
9
- "scripts": {
10
- "lint": "eslint src --ext .ts,.tsx",
11
- "typecheck": "tsc -p tsconfig.json --noEmit"
12
- },
13
12
  "exports": {
14
13
  ".": "./src/index.ts",
15
- "./styles/booking-flow.css": "./src/components/booking/booking-flow.css",
16
- "./styles/text-style.css": "./src/text-style.css",
17
- "./styles/colours.css": "./src/colours.css",
18
- "./styles/spacing.css": "./src/spacing.css",
19
- "./styles/radius.css": "./src/radius.css"
20
- },
21
- "dependencies": {
22
- "@radix-ui/react-select": "^2.2.6",
23
- "@radix-ui/react-slot": "^1.2.4",
24
- "@react-google-maps/api": "^2.20.7",
25
- "@stripe/react-stripe-js": "^3.9.0",
26
- "@stripe/stripe-js": "^7.9.0",
27
- "class-variance-authority": "^0.7.1",
28
- "clsx": "^2.1.1",
29
- "date-fns": "^4.1.0",
30
- "date-fns-tz": "^3.2.0",
31
- "framer-motion": "^12.0.0",
32
- "lucide-react": "^0.577.0",
33
- "react-international-phone": "^4.6.0",
34
- "tailwind-merge": "^3.5.0"
14
+ "./booking-flow.css": "./src/components/booking/booking-flow.css",
15
+ "./runtime": "./src/runtime/index.ts",
16
+ "./contexts/booking-app-context": "./src/contexts/BookingAppContext.tsx",
17
+ "./providers/booking-dialog-provider": "./src/providers/booking-dialog-provider.tsx",
18
+ "./hooks/useBookingSourceMetadataFromLocation": "./src/hooks/useBookingSourceMetadataFromLocation.ts",
19
+ "./hooks/useIsBookingLaunchLive": "./src/hooks/useIsBookingLaunchLive.ts"
35
20
  },
36
21
  "peerDependencies": {
37
22
  "next": "^15.0.0",
38
23
  "react": "^18.0.0 || ^19.0.0",
39
24
  "react-dom": "^18.0.0 || ^19.0.0"
40
- },
41
- "devDependencies": {
42
- "@types/google.maps": "^3.58.1"
43
25
  }
44
26
  }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { formatCurrencyAmount } from '@/lib/currency';
4
- import type { AddOn } from '@/lib/booking-api';
3
+ import { formatCurrencyAmount } from '../../../../../src/lib/currency';
4
+ import type { AddOn } from '../../../../../src/lib/booking-api';
5
5
  import { MealDrinkAddOnSelector, canUseMealDrinkSelector } from './MealDrinkAddOnSelector';
6
6
  import type { Currency } from './CurrencySwitcher';
7
7
  import styles from './AddOnsSection.module.css';
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { createPortal } from 'react-dom';
4
- import { formatCurrencyAmount } from '@/lib/currency';
4
+ import { formatCurrencyAmount } from '../../../../../src/lib/currency';
5
5
  import type { Currency } from './CurrencySwitcher';
6
6
 
7
7
  interface AdminPaymentChoiceModalProps {
@@ -1,20 +1,26 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useRef, useState } from 'react';
4
- import { useBookingSourceMetadataFromLocation } from '@/hooks/useBookingSourceMetadataFromLocation';
5
- import { useBookingDialog } from '@/providers/booking-dialog-provider';
6
- import { getProductByIdOrSlug, buildMinimalProductFromConfig } from '@/lib/products-config';
7
- import { getProduct, type Product } from '@/lib/booking-api';
8
- import { ENV } from '@/lib/env';
9
- import defaultStrings from '@/strings';
4
+ import { useBookingSourceMetadataFromLocation } from '../../hooks/useBookingSourceMetadataFromLocation';
5
+ import { useBookingDialog } from '../../providers/booking-dialog-provider';
6
+ import { useBookingHost } from '../../runtime';
7
+ import { getProduct, type Product } from '../../../../../src/lib/booking-api';
10
8
  import './booking-flow.css';
11
9
  import BookingProductGrid from './BookingProductGrid';
12
10
  import { BookingFlow } from './BookingFlow';
13
11
  import { PrivateShuttleBookingFlow } from './PrivateShuttleBookingFlow';
14
12
  import { BookingFlowPreview } from './BookingFlowPreview';
15
- import { useIsBookingLaunchLive } from '@/hooks/useIsBookingLaunchLive';
13
+ import { useIsBookingLaunchLive } from '../../hooks/useIsBookingLaunchLive';
16
14
  import styles from './BookingDialog.module.css';
17
15
 
16
+ /** Strings shape Via Via passes via `ViaViaBookingHost` (subset used by this dialog). */
17
+ type HostedBookingStrings = {
18
+ common: {
19
+ chooseYourExperience: string;
20
+ book: string;
21
+ };
22
+ };
23
+
18
24
  function BookFlowScreen({
19
25
  productId,
20
26
  onBack,
@@ -30,23 +36,29 @@ function BookFlowScreen({
30
36
  contentRef?: React.RefObject<HTMLDivElement | null>;
31
37
  isPartialLaunch: boolean;
32
38
  }) {
39
+ const { env, strings, catalog } = useBookingHost();
40
+ const defaultStrings = strings as HostedBookingStrings;
33
41
  const bookingSourceAttribution = useBookingSourceMetadataFromLocation();
34
42
  const [product, setProduct] = useState<Product | null>(null);
35
43
  const [error, setError] = useState<string | null>(null);
36
44
 
37
- const config = getProductByIdOrSlug(productId);
45
+ const config = catalog.getProductByIdOrSlug(productId) as {
46
+ productId?: string;
47
+ display?: { shortName?: string; slug?: string };
48
+ } | null;
38
49
  const apiProductId = config?.productId ?? productId;
39
50
 
40
51
  // Build minimal product from config for immediate availability fetch (no /products wait)
41
- const minimalProduct = config
42
- ? buildMinimalProductFromConfig(config, ENV.COMPANY_ID)
43
- : null;
52
+ const minimalProduct =
53
+ config && catalog.buildMinimalProductFromConfig
54
+ ? (catalog.buildMinimalProductFromConfig(config, env.COMPANY_ID) as Product)
55
+ : null;
44
56
 
45
57
  useEffect(() => {
46
58
  if (isPartialLaunch) return; // No API needed for partial launch
47
59
  let cancelled = false;
48
60
  setError(null);
49
- getProduct(apiProductId, ENV.COMPANY_ID)
61
+ getProduct(apiProductId, env.COMPANY_ID)
50
62
  .then((p) => {
51
63
  if (!cancelled && p) setProduct(p);
52
64
  else if (!cancelled && !p && !config) setError('Product not found');
@@ -155,6 +167,8 @@ function getFocusableElements(container: HTMLElement): HTMLElement[] {
155
167
  }
156
168
 
157
169
  export default function BookingDialog() {
170
+ const { catalog, strings } = useBookingHost();
171
+ const defaultStrings = strings as HostedBookingStrings;
158
172
  const {
159
173
  isOpen,
160
174
  close,
@@ -173,7 +187,11 @@ export default function BookingDialog() {
173
187
  // Use products-config for instant header title (no API wait)
174
188
  const displayNameFromConfig =
175
189
  currentScreen?.type === 'book-flow'
176
- ? getProductByIdOrSlug(currentScreen.productId)?.display?.shortName ?? null
190
+ ? (
191
+ catalog.getProductByIdOrSlug(currentScreen.productId) as {
192
+ display?: { shortName?: string };
193
+ } | null
194
+ )?.display?.shortName ?? null
177
195
  : null;
178
196
  const contentRef = useRef<HTMLDivElement>(null);
179
197
 
@@ -25,13 +25,13 @@ import {
25
25
  isInsufficientCapacityReserveError,
26
26
  describeStandardTourCapacityConflictMessage,
27
27
  reportReserveCapacityConflictClientContext,
28
- } from '@/lib/booking-api';
28
+ } from '../../../../../src/lib/booking-api';
29
29
  import {
30
30
  EARLIEST_AVAILABILITY_DATE,
31
31
  LATEST_AVAILABILITY_DATE,
32
32
  INITIAL_FETCH_WEEKS,
33
- } from '@/lib/booking-constants';
34
- import { getSundayOfWeek } from '@/lib/booking/sunday-week';
33
+ } from '../../../../../src/lib/booking-constants';
34
+ import { getSundayOfWeek } from '../../../../../src/lib/booking/sunday-week';
35
35
  import { Calendar } from './Calendar';
36
36
  import { AdminPaymentChoiceModal } from './AdminPaymentChoiceModal';
37
37
  import { ItineraryBox } from './ItineraryBox';
@@ -43,44 +43,40 @@ import { TicketSelector } from './TicketSelector';
43
43
  import { AddOnsSection } from './AddOnsSection';
44
44
  import { CheckoutForm } from './CheckoutForm';
45
45
  import { PromoCodeInput } from './PromoCodeInput';
46
- import { useTranslations, useLocale } from '@/lib/booking/i18n';
46
+ import { useTranslations, useLocale } from '../../../../../src/lib/booking/i18n';
47
47
  import { type Currency } from './CurrencySwitcher';
48
- import { formatBookingRefForDisplay } from '@/lib/booking-ref';
48
+ import { formatBookingRefForDisplay } from '../../../../../src/lib/booking-ref';
49
49
  import {
50
50
  formatCurrencyAmount,
51
51
  reconcileChangeBookingProposedTotal,
52
- } from '@/lib/currency';
53
- import { buildCheckoutBreakdown } from '@/lib/booking/checkout-breakdown';
54
- import type { PricingConfig, PrecomputedPricesByCategory, ItineraryDisplayStep } from '@/lib/booking-api';
55
- import { ItineraryStepType as StepType } from '@/lib/booking-api';
52
+ } from '../../../../../src/lib/currency';
53
+ import { buildCheckoutBreakdown } from '../../../../../src/lib/booking/checkout-breakdown';
54
+ import type { PricingConfig, PrecomputedPricesByCategory, ItineraryDisplayStep } from '../../../../../src/lib/booking-api';
55
+ import { ItineraryStepType as StepType } from '../../../../../src/lib/booking-api';
56
56
  import {
57
57
  getDisplayPriceFromBaseInDisplayCurrency,
58
58
  computePriceBreakdown,
59
59
  computeOrderSummary,
60
60
  type PriceBreakdown as PriceBreakdownData,
61
61
  type OrderSummary,
62
- } from '@/lib/booking/pricing';
63
- import { useCompanyTimezone } from '@/contexts/CompanyContext';
64
- import { useBookingApp } from '@/contexts/BookingAppContext';
65
- import { useAvailabilitiesCache, buildAvailabilitiesCacheKey } from '@/contexts/AvailabilitiesCacheContext';
62
+ } from '../../../../../src/lib/booking/pricing';
63
+ import { useCompanyTimezone } from '../../../../../src/contexts/CompanyContext';
64
+ import { useBookingApp } from '../../contexts/BookingAppContext';
65
+ import { useAvailabilitiesCache, buildAvailabilitiesCacheKey } from '../../../../../src/contexts/AvailabilitiesCacheContext';
66
66
  import { type PriceSummaryLine } from './PriceSummary';
67
67
  import { CheckoutModal, type CheckoutModalLineItem } from './CheckoutModal';
68
68
  import { BookingFlowCollage } from './BookingFlowCollage';
69
69
  import { TourDescription } from './TourDescription';
70
- import { getProductByIdOrSlug } from '@/lib/products-config';
71
- import { getProducts } from '@/constants/products';
72
- import defaultStrings from '@/strings';
73
- import { trackViewItem } from '@/lib/analytics';
74
70
  import {
75
71
  buildBookingSourceContext,
76
72
  inferClientBookingSourceFromProductIds,
77
73
  type BookingSourceMetadata,
78
- } from '@/lib/booking/source-metadata';
79
- import { getItineraryStepLabel } from '@/lib/booking/itinerary-display';
80
- import { MANAGE_BOOKING_FROM_CHANGE_PAYMENT, MANAGE_BOOKING_QUERY_FROM } from '@/lib/manage-booking-post-checkout';
81
- import { ENV } from '@/lib/env';
74
+ } from '../../../../../src/lib/booking/source-metadata';
75
+ import { getItineraryStepLabel } from '../../../../../src/lib/booking/itinerary-display';
76
+ import { MANAGE_BOOKING_FROM_CHANGE_PAYMENT, MANAGE_BOOKING_QUERY_FROM } from '../../../../../src/lib/manage-booking-post-checkout';
77
+ import { useBookingHost } from '../../runtime';
82
78
  import type { BookingFlowUiOptions } from './booking-flow-ui';
83
- import { BOOKING_FLOW_ABANDON_EVENT } from '@/providers/booking-dialog-provider';
79
+ import { BOOKING_FLOW_ABANDON_EVENT } from '../../providers/booking-dialog-provider';
84
80
 
85
81
  /** Live selection snapshot for change-booking compare UI (parent dialog). */
86
82
  export interface ChangeFlowSelectionPreview {
@@ -607,6 +603,7 @@ export function BookingFlow({
607
603
  availabilityPricingProfileId,
608
604
  availabilityCancellationPolicyProfileId,
609
605
  }: BookingFlowProps) {
606
+ const { env, strings: defaultStrings, analytics, catalog } = useBookingHost();
610
607
  const { t } = useTranslations();
611
608
  const { locale } = useLocale();
612
609
  const companyTimezone = useCompanyTimezone(); // Get timezone from context
@@ -836,7 +833,7 @@ export function BookingFlow({
836
833
  hasFiredViewItem.current = true;
837
834
  const id = productId || product.productId;
838
835
  const price = product.minPriceByCurrency?.[currency] ?? 0;
839
- trackViewItem(id, product.name, price, currency);
836
+ analytics.trackViewItem(id, product.name, price, currency);
840
837
  }
841
838
  }, [product, productId, currency]);
842
839
 
@@ -2383,7 +2380,7 @@ export function BookingFlow({
2383
2380
 
2384
2381
  const promoDiscountFetchKey = useMemo(() => {
2385
2382
  if (!appliedPromoCode || !selectedAvailability || totalQuantity === 0) return '';
2386
- const companyId = product.companyId ?? ENV.COMPANY_ID;
2383
+ const companyId = product.companyId ?? env.COMPANY_ID;
2387
2384
  if (!companyId) return '';
2388
2385
  const optionId = selectedAvailability.productOptionId;
2389
2386
  if (!optionId || !quantitiesSignature) return '';
@@ -2437,7 +2434,7 @@ export function BookingFlow({
2437
2434
  appliedPromoCode: code,
2438
2435
  } = promoDiscountParamsRef.current;
2439
2436
  if (!code || !sel) return;
2440
- const companyId = product.companyId ?? ENV.COMPANY_ID;
2437
+ const companyId = product.companyId ?? env.COMPANY_ID;
2441
2438
  const optionId = sel.productOptionId;
2442
2439
  if (!companyId || !optionId) return;
2443
2440
  const items = lines.map((l) => ({ category: l.category, qty: l.qty }));
@@ -4031,8 +4028,16 @@ export function BookingFlow({
4031
4028
  )}
4032
4029
  {/* Image/video collage - vertical video on left, image grid on right */}
4033
4030
  {productId && !isChangeFlow && flowUi?.showCollage !== false && (() => {
4034
- const config = getProductByIdOrSlug(productId);
4035
- const displayProducts = getProducts(defaultStrings);
4031
+ const config = catalog.getProductByIdOrSlug(productId) as {
4032
+ display?: {
4033
+ collageImageIds?: string[];
4034
+ imageIds?: string[];
4035
+ };
4036
+ } | null;
4037
+ const displayProducts = catalog.getProducts(defaultStrings) as Record<
4038
+ string,
4039
+ { id: string; videoUrl?: import('../../../../../src/constants/products').VideoSources }
4040
+ >;
4036
4041
  const displayProduct = Object.values(displayProducts).find((p) => p.id === productId);
4037
4042
  const collageImageIds = config?.display?.collageImageIds ?? config?.display?.imageIds ?? [];
4038
4043
  const hasVideo = !!displayProduct?.videoUrl;
@@ -1,12 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useRef, useEffect, useCallback } from 'react';
4
- import ViaViaImage from '@/components/image';
5
- import { getImageUrl } from '@/constants/images';
6
4
  import BackgroundPlayer from 'next-video/background-player';
7
- import type { VideoSources } from '@/constants/products';
8
- import ImageModal from '@/components/product-theme-pages/image-modal';
9
- import { useTranslations } from '@/lib/booking/i18n';
5
+ import type { VideoSources } from '../../../../../src/constants/products';
6
+ import { useBookingHost } from '../../runtime';
7
+ import { useTranslations } from '../../../../../src/lib/booking/i18n';
10
8
  import styles from './BookingFlowCollage.module.css';
11
9
 
12
10
  export interface BookingFlowCollageProps {
@@ -31,12 +29,18 @@ export function BookingFlowCollage({
31
29
  imageIds,
32
30
  altPrefix = 'Tour',
33
31
  }: BookingFlowCollageProps) {
32
+ const { catalog, slots } = useBookingHost();
33
+ const ViaViaImage = slots.Image;
34
+ const ImageModal = slots.ImageModal;
34
35
  const videoSrc = video ?? DEFAULT_VIDEO;
35
36
  // Use long version in BookingFlow when available; fall back to short
36
37
  const videoForCollage = (videoSrc.longSrc && videoSrc.longWebm)
37
38
  ? { src: videoSrc.longSrc, webm: videoSrc.longWebm }
38
39
  : { src: videoSrc.src, webm: videoSrc.webm };
39
- const posterUrl = videoPosterImageId ? getImageUrl(videoPosterImageId) : undefined;
40
+ const posterUrl =
41
+ videoPosterImageId && catalog.getImageUrl
42
+ ? String(catalog.getImageUrl(videoPosterImageId))
43
+ : undefined;
40
44
  const gridImages = imageIds.slice(0, 4);
41
45
  // Pad with first image if fewer than 4
42
46
  while (gridImages.length < 4 && gridImages.length > 0) {
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import type { ProductConfig } from '@/lib/booking-types';
3
+ import type { ProductConfig } from '../../../../../src/lib/booking-types';
4
4
  import styles from './BookingFlowPlaceholder.module.css';
5
5
 
6
6
  interface BookingFlowPlaceholderProps {
@@ -1,10 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useRef } from 'react';
4
- import { getProductByIdOrSlug } from '@/lib/products-config';
5
- import { getProducts } from '@/constants/products';
6
- import defaultStrings from '@/strings';
7
- import { trackViewItem } from '@/lib/analytics';
4
+ import { useBookingHost } from '../../runtime';
5
+ import type { VideoSources } from '../../../../../src/constants/products';
8
6
  import { BookingFlowCollage } from './BookingFlowCollage';
9
7
  import { TourDescription } from './TourDescription';
10
8
 
@@ -13,20 +11,31 @@ import { TourDescription } from './TourDescription';
13
11
  * No API call required - shows immediately.
14
12
  */
15
13
  export function BookingFlowPreview({ productId, defaultExpanded = true }: { productId: string; defaultExpanded?: boolean }) {
16
- const config = getProductByIdOrSlug(productId);
14
+ const { catalog, strings: defaultStrings, analytics } = useBookingHost();
15
+ const config = catalog.getProductByIdOrSlug(productId) as {
16
+ display?: { shortName?: string; collageImageIds?: string[]; imageIds?: string[] };
17
+ productId?: string;
18
+ } | null;
17
19
  const hasFiredViewItem = useRef(false);
18
20
 
19
21
  useEffect(() => {
20
22
  if (!hasFiredViewItem.current && productId && config) {
21
23
  hasFiredViewItem.current = true;
22
- const displayProducts = getProducts(defaultStrings);
24
+ const displayProducts = catalog.getProducts(defaultStrings) as Record<
25
+ string,
26
+ { id: string; name: string; avgPrice: number }
27
+ >;
23
28
  const displayProduct = Object.values(displayProducts).find((p) => p.id === productId);
24
29
  const productName = displayProduct?.name ?? config.display?.shortName ?? productId;
25
30
  const price = displayProduct?.avgPrice ?? 0;
26
- trackViewItem(productId, productName, price, 'CAD');
31
+ analytics.trackViewItem(productId, productName, price, 'CAD');
27
32
  }
28
- }, [productId, config]);
29
- const displayProducts = getProducts(defaultStrings);
33
+ }, [productId, config, catalog, defaultStrings, analytics]);
34
+
35
+ const displayProducts = catalog.getProducts(defaultStrings) as Record<
36
+ string,
37
+ { id: string; videoUrl?: VideoSources }
38
+ >;
30
39
  const displayProduct = Object.values(displayProducts).find((p) => p.id === productId);
31
40
  const collageImageIds = config?.display?.collageImageIds ?? config?.display?.imageIds ?? [];
32
41
  const hasVideo = !!displayProduct?.videoUrl;
@@ -5,21 +5,20 @@ import { motion, AnimatePresence } from 'framer-motion';
5
5
  import {
6
6
  useBookingDialog,
7
7
  type ProductGridRestoreState,
8
- } from '@/providers/booking-dialog-provider';
9
- import { useLocale, useTranslations } from '@/lib/booking/i18n';
10
- import { getProducts } from '@/constants/products';
11
- import { getProductByIdOrSlug } from '@/lib/products-config';
12
- import { getProductDescription } from '@/lib/product-descriptions';
13
- import ViaViaImage from '@/components/image';
14
- import ValuePill from '@/components/value-pill';
15
- import ProductTag from '@/components/product-tag';
16
- import { PillVariant } from '@/components/value-pill';
17
- import type { Product } from '@/constants/products';
18
- import defaultStrings from '@/strings';
19
- import { getImageUrl } from '@/constants/images';
8
+ } from '../../providers/booking-dialog-provider';
9
+ import { useBookingHost } from '../../runtime';
10
+ import { useLocale, useTranslations } from '../../../../../src/lib/booking/i18n';
11
+ import type { Product } from '../../../../../src/constants/products';
20
12
  import BackgroundPlayer from 'next-video/background-player';
21
13
  import styles from './BookingProductGrid.module.css';
22
- import Button, { ButtonHoverColor } from '@/components/button';
14
+
15
+ /** Locale strings needed by the product grid tiles (Via Via host). */
16
+ type HostedProductGridStrings = {
17
+ common: {
18
+ moreInfo: string;
19
+ bookNow: string;
20
+ };
21
+ };
23
22
 
24
23
  const DEFAULT_VIDEO = {
25
24
  src: '/videos/via-via-moraine-lake-tour-video.mp4',
@@ -63,6 +62,15 @@ function BookingProductTileCollapsed({
63
62
  useLayoutId: boolean;
64
63
  suppressLayoutAnimation?: boolean;
65
64
  }) {
65
+ const { slots } = useBookingHost();
66
+ const ViaViaImage = slots.Image;
67
+ const ProductTag = slots.ProductTag;
68
+ const ValuePill = slots.ValuePill;
69
+ const PillVariant = slots.PillVariant as {
70
+ overlay: string;
71
+ solid: string;
72
+ };
73
+
66
74
  return (
67
75
  <motion.div
68
76
  layout={!suppressLayoutAnimation}
@@ -127,7 +135,25 @@ function BookingProductTileExpanded({
127
135
  }) {
128
136
  const [isClosing, setIsClosing] = useState(false);
129
137
  const { locale } = useLocale();
130
- const productDesc = getProductDescription(product.id, locale);
138
+ const { slots, catalog, strings } = useBookingHost();
139
+ const defaultStrings = strings as HostedProductGridStrings;
140
+ const ViaViaImage = slots.Image;
141
+ const ProductTag = slots.ProductTag;
142
+ const ValuePill = slots.ValuePill;
143
+ const Button = slots.Button;
144
+ const ButtonHoverColor = slots.ButtonHoverColor as {
145
+ Turquoise: string;
146
+ White: string;
147
+ Orange: string;
148
+ };
149
+ const PillVariant = slots.PillVariant as {
150
+ overlay: string;
151
+ solid: string;
152
+ };
153
+
154
+ const productDesc = catalog.getProductDescription(product.id, locale) as {
155
+ shortDescription?: string;
156
+ } | null;
131
157
  const displayDescription = productDesc?.shortDescription ?? product.description;
132
158
 
133
159
  const handleCollapseClick = () => {
@@ -185,7 +211,11 @@ function BookingProductTileExpanded({
185
211
  muted
186
212
  loop
187
213
  playsInline
188
- poster={getImageUrl(product.images[0].id)}
214
+ poster={
215
+ catalog.getImageUrl
216
+ ? String(catalog.getImageUrl(product.images[0].id))
217
+ : ''
218
+ }
189
219
  className={styles.expandedVideo}
190
220
  />
191
221
  {product.tags && product.tags.length > 0 && (
@@ -258,6 +288,7 @@ export default function BookingProductGrid({
258
288
  onBookProduct,
259
289
  bookOnTileClick = false,
260
290
  }: BookingProductGridProps = {}) {
291
+ const { catalog, strings: defaultStrings } = useBookingHost();
261
292
  const { push } = useBookingDialog();
262
293
  const { t } = useTranslations();
263
294
  const hasAppliedRestore = useRef(false);
@@ -289,8 +320,11 @@ export default function BookingProductGrid({
289
320
  }, [isRestoring, restoreState, contentRef, onRestoreApplied]);
290
321
 
291
322
  const allProducts = useMemo(
292
- () => Object.values(getProducts(defaultStrings)),
293
- []
323
+ () =>
324
+ Object.values(
325
+ catalog.getProducts(defaultStrings) as Record<string, Product>
326
+ ),
327
+ [catalog, defaultStrings]
294
328
  );
295
329
 
296
330
  const products = useMemo(
@@ -397,8 +431,10 @@ export default function BookingProductGrid({
397
431
  );
398
432
 
399
433
  const handleBook = (product: Product) => {
400
- const config = getProductByIdOrSlug(product.id);
401
- const productSlugOrId = config?.display.slug ?? product.id;
434
+ const config = catalog.getProductByIdOrSlug(product.id) as {
435
+ display?: { slug?: string };
436
+ } | null;
437
+ const productSlugOrId = config?.display?.slug ?? product.id;
402
438
 
403
439
  if (onBookProduct) {
404
440
  onBookProduct(productSlugOrId);
@@ -361,13 +361,20 @@
361
361
  .calendarDaysGrid {
362
362
  display: grid;
363
363
  grid-template-columns: repeat(7, 1fr);
364
- align-items: stretch;
364
+ }
365
+
366
+ /* Desktop: don't stretch every cell to the tallest day in the row (was huge empty space). */
367
+ @media (min-width: 640px) {
368
+ .calendarDaysGrid {
369
+ align-items: start;
370
+ }
365
371
  }
366
372
 
367
373
  /* Mobile: rows size to content to eliminate bottom gap */
368
374
  @media (max-width: 639px) {
369
375
  .calendarDaysGrid {
370
376
  grid-auto-rows: minmax(min-content, max-content);
377
+ align-items: stretch;
371
378
  }
372
379
  }
373
380
 
@@ -381,17 +388,17 @@
381
388
  flex-direction: column;
382
389
  align-items: center;
383
390
  justify-content: center;
384
- min-height: 5.5rem; /* Desktop: enough room for time pills */
391
+ min-height: 4.75rem; /* Desktop: room for date row + time pills without oversized cells */
385
392
  cursor: pointer;
386
393
  }
387
394
 
388
395
  /* Admin (showCapacity): extra line under each time pill — taller cells on sm+ only */
389
396
  @media (min-width: 640px) {
390
397
  .calendar .calendarDayCell.calendarDayCellWithAdminCapacity {
391
- min-height: 7.5rem;
398
+ min-height: 6.5rem;
392
399
  }
393
400
  .calendar .calendarDayCell.calendarDayCellWithAdminCapacity.calendarDayCellWithAdminCapacityTall {
394
- min-height: 9rem;
401
+ min-height: 7.5rem;
395
402
  }
396
403
  }
397
404
 
@@ -404,6 +411,14 @@
404
411
  justify-content: center;
405
412
  width: 100%;
406
413
  }
414
+
415
+ /* Desktop: pin time pills to bottom of cell; centering left a tall empty band under the date. */
416
+ @media (min-width: 640px) {
417
+ .calendarDayCellInner:not(.calendarDayCellInnerMobile) {
418
+ justify-content: flex-end;
419
+ padding-bottom: 0.125rem;
420
+ }
421
+ }
407
422
  .calendar .calendarDayCell.calendarDayCellMobile .calendarDayCellInnerMobile,
408
423
  .calendar .calendarDayCell.calendarDayCellMobileTall .calendarDayCellInnerMobile {
409
424
  justify-content: flex-start !important;
@@ -5,15 +5,15 @@ import { createPortal } from 'react-dom';
5
5
  import { startOfWeek, addDays, addWeeks, subWeeks, isSameDay, parseISO, startOfMonth, endOfMonth, eachDayOfInterval, addMonths, subMonths } from 'date-fns';
6
6
  import { formatInTimeZone, fromZonedTime } from 'date-fns-tz';
7
7
  import { enUS, fr } from 'date-fns/locale';
8
- import type { Availability } from '@/lib/booking-api';
9
- import { useTranslations, useLocale } from '@/lib/booking/i18n';
8
+ import type { Availability } from '../../../../../src/lib/booking-api';
9
+ import { useTranslations, useLocale } from '../../../../../src/lib/booking/i18n';
10
10
  import {
11
11
  MINI_CALENDAR_START_MONTH,
12
12
  MINI_CALENDAR_END_MONTH,
13
13
  VISIBLE_RANGE_BUFFER_WEEKS,
14
- } from '@/lib/booking-constants';
15
- import { getSundayOfWeek } from '@/lib/booking/sunday-week';
16
- import { cn } from '@/lib/booking/utils';
14
+ } from '../../../../../src/lib/booking-constants';
15
+ import { getSundayOfWeek } from '../../../../../src/lib/booking/sunday-week';
16
+ import { cn } from '../../../../../src/lib/booking/utils';
17
17
  import styles from './Calendar.module.css';
18
18
 
19
19
  // ============ Types ============
@@ -207,8 +207,8 @@ const DateCell = memo(function DateCell({
207
207
  ? cn('min-w-0', styles.calendarDayCellWithAdminCapacity, styles.calendarDayCellWithAdminCapacityTall)
208
208
  : styles.calendarDayCellWithAdminCapacity
209
209
  : needsTallerCell
210
- ? 'min-w-0 min-h-[7rem]'
211
- : 'min-h-[6rem]'
210
+ ? 'min-w-0 min-h-[5.75rem]'
211
+ : 'min-h-[5rem]'
212
212
  ),
213
213
  isDisabled
214
214
  ? styles.calendarDayCellDisabled
@@ -250,7 +250,12 @@ const DateCell = memo(function DateCell({
250
250
 
251
251
  {/* Availability Indicators - Centered */}
252
252
  {availability && (
253
- <div className="flex flex-col items-center justify-center space-y-0 w-full px-0.5">
253
+ <div
254
+ className={cn(
255
+ 'flex flex-col items-center space-y-0 w-full px-0.5',
256
+ isMobile ? 'justify-start' : 'justify-end'
257
+ )}
258
+ >
254
259
  {/* Sold Out Badge - Centered */}
255
260
  {availability.isSoldOut ? (
256
261
  <div className={styles.calendarSoldOutBadge}>
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { Check } from 'lucide-react';
4
- import { formatCurrencyAmount } from '@/lib/currency';
5
- import type { CancellationPolicyOption, RefundTierOption } from '@/lib/booking-api';
4
+ import { formatCurrencyAmount } from '../../../../../src/lib/currency';
5
+ import type { CancellationPolicyOption, RefundTierOption } from '../../../../../src/lib/booking-api';
6
6
  import type { Currency } from './CurrencySwitcher';
7
7
  import styles from './CancellationPolicySelector.module.css';
8
8