@ticketboothapp/booking 0.1.22 → 1.2.24

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 +2 -29
  2. package/src/index.ts +0 -79
  3. package/tsconfig.json +2 -8
  4. package/src/assets/icons/minus.svg +0 -7
  5. package/src/assets/icons/partner-logos/getyourguide.svg +0 -8
  6. package/src/assets/icons/plus.svg +0 -3
  7. package/src/colours.css +0 -23
  8. package/src/components/BookingDetails.module.css +0 -1591
  9. package/src/components/BookingDetails.tsx +0 -2264
  10. package/src/components/BookingWidget.tsx +0 -302
  11. package/src/components/ManageBookingView.tsx +0 -437
  12. package/src/components/PhoneInputWithCountry.module.css +0 -131
  13. package/src/components/PhoneInputWithCountry.tsx +0 -44
  14. package/src/components/PickupLocationDialog.module.css +0 -360
  15. package/src/components/PickupLocationDialog.tsx +0 -357
  16. package/src/components/PostBookingDependentAddOnUpsell.module.css +0 -174
  17. package/src/components/PostBookingDependentAddOnUpsell.tsx +0 -407
  18. package/src/components/booking/AddOnsSection.module.css +0 -10
  19. package/src/components/booking/AddOnsSection.tsx +0 -184
  20. package/src/components/booking/AdminPaymentChoiceModal.tsx +0 -98
  21. package/src/components/booking/BookingDialog.module.css +0 -643
  22. package/src/components/booking/BookingDialog.tsx +0 -356
  23. package/src/components/booking/BookingFlow.tsx +0 -4385
  24. package/src/components/booking/BookingFlowCollage.module.css +0 -148
  25. package/src/components/booking/BookingFlowCollage.tsx +0 -184
  26. package/src/components/booking/BookingFlowPlaceholder.module.css +0 -27
  27. package/src/components/booking/BookingFlowPlaceholder.tsx +0 -25
  28. package/src/components/booking/BookingFlowPreview.tsx +0 -51
  29. package/src/components/booking/BookingProductGrid.module.css +0 -359
  30. package/src/components/booking/BookingProductGrid.tsx +0 -497
  31. package/src/components/booking/Calendar.module.css +0 -616
  32. package/src/components/booking/Calendar.tsx +0 -1123
  33. package/src/components/booking/CancellationPolicySelector.module.css +0 -124
  34. package/src/components/booking/CancellationPolicySelector.tsx +0 -142
  35. package/src/components/booking/ChangeBookingDialog.tsx +0 -562
  36. package/src/components/booking/CheckoutForm.module.css +0 -244
  37. package/src/components/booking/CheckoutForm.tsx +0 -364
  38. package/src/components/booking/CheckoutModal.tsx +0 -451
  39. package/src/components/booking/CurrencySwitcher.tsx +0 -81
  40. package/src/components/booking/DapFlowCollage.tsx +0 -88
  41. package/src/components/booking/DapTourDescription.tsx +0 -35
  42. package/src/components/booking/DependentAddOnBookingDialog.tsx +0 -1350
  43. package/src/components/booking/DependentAddOnPaymentForm.tsx +0 -124
  44. package/src/components/booking/ErrorBoundary.tsx +0 -63
  45. package/src/components/booking/InfoTooltip.tsx +0 -108
  46. package/src/components/booking/ItineraryBox.module.css +0 -258
  47. package/src/components/booking/ItineraryBox.tsx +0 -550
  48. package/src/components/booking/ItineraryBuilder.tsx +0 -82
  49. package/src/components/booking/ItineraryPlaceholder.module.css +0 -45
  50. package/src/components/booking/ItineraryPlaceholder.tsx +0 -26
  51. package/src/components/booking/MealDrinkAddOnSelector.tsx +0 -338
  52. package/src/components/booking/PickupLocationSelector.module.css +0 -124
  53. package/src/components/booking/PickupLocationSelector.tsx +0 -1566
  54. package/src/components/booking/PickupTimeSelector.module.css +0 -134
  55. package/src/components/booking/PickupTimeSelector.tsx +0 -112
  56. package/src/components/booking/PriceBreakdown.tsx +0 -154
  57. package/src/components/booking/PriceSummary.tsx +0 -234
  58. package/src/components/booking/PrivateShuttleBookingFlow.module.css +0 -357
  59. package/src/components/booking/PrivateShuttleBookingFlow.tsx +0 -2662
  60. package/src/components/booking/PromoCodeInput.module.css +0 -166
  61. package/src/components/booking/PromoCodeInput.tsx +0 -99
  62. package/src/components/booking/ReturnTimeSelector.module.css +0 -173
  63. package/src/components/booking/ReturnTimeSelector.tsx +0 -145
  64. package/src/components/booking/TermsAcceptance.tsx +0 -111
  65. package/src/components/booking/TicketSelector.module.css +0 -164
  66. package/src/components/booking/TicketSelector.tsx +0 -199
  67. package/src/components/booking/TourDescription.module.css +0 -304
  68. package/src/components/booking/TourDescription.tsx +0 -273
  69. package/src/components/booking/booking-flow-ui.ts +0 -38
  70. package/src/components/booking/booking-flow.css +0 -944
  71. package/src/components/button.css +0 -245
  72. package/src/components/button.tsx +0 -152
  73. package/src/components/colorable-svg.tsx +0 -29
  74. package/src/components/image.css +0 -29
  75. package/src/components/image.tsx +0 -113
  76. package/src/components/partner/PartnerBookingPage.module.css +0 -130
  77. package/src/components/partner/PartnerBookingPage.tsx +0 -390
  78. package/src/components/partner/PartnerBookingPageWithBrowserMetadata.tsx +0 -45
  79. package/src/components/product-tag.module.css +0 -30
  80. package/src/components/product-tag.tsx +0 -34
  81. package/src/components/product-theme-pages/image-modal.tsx +0 -248
  82. package/src/components/product-theme-pages/photo-gallery.module.css +0 -200
  83. package/src/components/terms/TermsContent.tsx +0 -178
  84. package/src/components/value-pill.module.css +0 -59
  85. package/src/components/value-pill.tsx +0 -46
  86. package/src/constants/images.ts +0 -556
  87. package/src/constants/pill-values.ts +0 -210
  88. package/src/constants/products.ts +0 -155
  89. package/src/contexts/AvailabilitiesCacheContext.tsx +0 -125
  90. package/src/contexts/BookingAppContext.tsx +0 -134
  91. package/src/contexts/CompanyContext.tsx +0 -70
  92. package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -61
  93. package/src/data/dap-descriptions/session-elopements.en.json +0 -60
  94. package/src/data/dap-descriptions/session-proposals.en.json +0 -60
  95. package/src/data/product-descriptions/afternoon-delight.en.json +0 -35
  96. package/src/data/product-descriptions/emerald-lake-escape.en.json +0 -68
  97. package/src/data/product-descriptions/lake-louise-adventure.en.json +0 -74
  98. package/src/data/product-descriptions/moraine-lake-adventure.en.json +0 -78
  99. package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +0 -65
  100. package/src/data/product-descriptions/moraine-lake-sunrise.en.json +0 -64
  101. package/src/data/product-descriptions/private-tour.en.json +0 -80
  102. package/src/data/product-descriptions/two-lakes-combo.en.json +0 -65
  103. package/src/data/products-config.json +0 -101
  104. package/src/hooks/useBookingSourceMetadataFromLocation.ts +0 -21
  105. package/src/hooks/useIsBookingLaunchLive.ts +0 -49
  106. package/src/lib/analytics.ts +0 -197
  107. package/src/lib/booking/booking-source.ts +0 -51
  108. package/src/lib/booking/checkout-breakdown.ts +0 -69
  109. package/src/lib/booking/correlation-id.ts +0 -46
  110. package/src/lib/booking/i18n/config.ts +0 -21
  111. package/src/lib/booking/i18n/index.tsx +0 -144
  112. package/src/lib/booking/i18n/messages/en.json +0 -236
  113. package/src/lib/booking/i18n/messages/fr.json +0 -236
  114. package/src/lib/booking/itinerary-display.ts +0 -36
  115. package/src/lib/booking/itinerary-labels.ts +0 -70
  116. package/src/lib/booking/location-calculations.ts +0 -43
  117. package/src/lib/booking/location-utils.ts +0 -165
  118. package/src/lib/booking/map-utils.ts +0 -153
  119. package/src/lib/booking/marker-icons.ts +0 -113
  120. package/src/lib/booking/normalize-booking-product-id.ts +0 -21
  121. package/src/lib/booking/pickup-location-types.ts +0 -25
  122. package/src/lib/booking/places-api.ts +0 -154
  123. package/src/lib/booking/pricing.ts +0 -466
  124. package/src/lib/booking/product-option-id.ts +0 -35
  125. package/src/lib/booking/source-metadata.ts +0 -226
  126. package/src/lib/booking/sunday-week.ts +0 -14
  127. package/src/lib/booking/theme.ts +0 -83
  128. package/src/lib/booking/trace-context.ts +0 -62
  129. package/src/lib/booking/utils.ts +0 -9
  130. package/src/lib/booking-api.ts +0 -1793
  131. package/src/lib/booking-constants.ts +0 -23
  132. package/src/lib/booking-ref.ts +0 -13
  133. package/src/lib/booking-types.ts +0 -36
  134. package/src/lib/currency.ts +0 -81
  135. package/src/lib/dap-descriptions.ts +0 -50
  136. package/src/lib/dap-itinerary-preview.ts +0 -315
  137. package/src/lib/dependent-add-on-api.ts +0 -434
  138. package/src/lib/env.ts +0 -96
  139. package/src/lib/firebase.ts +0 -20
  140. package/src/lib/job-application-api.ts +0 -83
  141. package/src/lib/manage-booking-embed-print.ts +0 -16
  142. package/src/lib/manage-booking-post-checkout.ts +0 -68
  143. package/src/lib/photo-dap-config.ts +0 -228
  144. package/src/lib/photo-packages.ts +0 -75
  145. package/src/lib/pickup/map-utils.ts +0 -56
  146. package/src/lib/pickup/marker-icons.ts +0 -19
  147. package/src/lib/product-descriptions.ts +0 -66
  148. package/src/lib/products-config.ts +0 -73
  149. package/src/providers/booking-dialog-provider.tsx +0 -282
  150. package/src/providers/dependent-add-on-dialog-provider.tsx +0 -105
  151. package/src/radius.css +0 -5
  152. package/src/spacing.css +0 -7
  153. package/src/strings/en.json +0 -1774
  154. package/src/strings/es.json +0 -1573
  155. package/src/strings/fr.json +0 -1573
  156. package/src/strings/index.js +0 -23
  157. package/src/text-style.css +0 -97
  158. package/src/utils/currency-converter.ts +0 -101
@@ -1,148 +0,0 @@
1
- .collage {
2
- display: flex;
3
- flex-direction: row;
4
- gap: 0.5rem;
5
- width: 100%;
6
- border-radius: 0.75rem;
7
- overflow: hidden;
8
- align-items: stretch;
9
- }
10
-
11
- .videoSlot {
12
- flex: 0 0 auto;
13
- width: min(48%, 340px);
14
- min-width: 160px;
15
- /* Slightly taller than square, but not strict 9:16 */
16
- aspect-ratio: 3 / 4;
17
- position: relative;
18
- overflow: hidden;
19
- border-radius: 0.5rem;
20
- }
21
-
22
- .videoWrapper {
23
- position: absolute;
24
- inset: 0;
25
- width: 100%;
26
- height: 100%;
27
- }
28
-
29
- .videoControls {
30
- position: absolute;
31
- bottom: 0.5rem;
32
- right: 0.5rem;
33
- display: flex;
34
- gap: 0.25rem;
35
- z-index: 2;
36
- pointer-events: none;
37
- }
38
-
39
- .videoControls .videoControlBtn {
40
- pointer-events: auto;
41
- width: 2rem;
42
- height: 2rem;
43
- border-radius: 0.375rem;
44
- border: none;
45
- background: rgba(0, 0, 0, 0.5);
46
- color: white;
47
- cursor: pointer;
48
- display: flex;
49
- align-items: center;
50
- justify-content: center;
51
- transition: background 0.15s ease;
52
- }
53
-
54
- .videoControls .videoControlBtn:hover {
55
- background: rgba(0, 0, 0, 0.7);
56
- }
57
-
58
- .videoControls .videoControlBtn:focus-visible {
59
- outline: 2px solid white;
60
- outline-offset: 2px;
61
- }
62
-
63
- .video {
64
- position: absolute;
65
- inset: 0;
66
- width: 100% !important;
67
- height: 100% !important;
68
- min-height: 100% !important;
69
- }
70
-
71
- /* Constrain next-video BackgroundPlayer output */
72
- .videoSlot :global(video),
73
- .videoSlot :global(.next-video-bg-video),
74
- .videoSlot :global(.next-video-bg-poster),
75
- .videoWrapper :global(video),
76
- .videoWrapper :global(.next-video-bg-video),
77
- .videoWrapper :global(.next-video-bg-poster) {
78
- width: 100% !important;
79
- height: 100% !important;
80
- object-fit: cover !important;
81
- object-position: center !important;
82
- }
83
-
84
- .imageGrid {
85
- flex: 1;
86
- display: grid;
87
- grid-template-columns: 1fr 1fr 1fr;
88
- grid-template-rows: 1fr 1fr;
89
- gap: 0.5rem;
90
- min-height: 200px;
91
- }
92
-
93
- /* Top-left: narrow | Top-right: wide */
94
- .imageGrid .gridCell:nth-child(1) { grid-column: 1 / span 1; }
95
- .imageGrid .gridCell:nth-child(2) { grid-column: 2 / span 2; }
96
- /* Bottom-left: wide | Bottom-right: narrow */
97
- .imageGrid .gridCell:nth-child(3) { grid-column: 1 / span 2; }
98
- .imageGrid .gridCell:nth-child(4) { grid-column: 3 / span 1; }
99
-
100
- .gridCell {
101
- position: relative;
102
- width: 100%;
103
- height: 100%;
104
- min-height: 80px;
105
- overflow: hidden;
106
- border-radius: 0.5rem;
107
- padding: 0;
108
- border: none;
109
- background: none;
110
- cursor: pointer;
111
- display: block;
112
- text-align: left;
113
- }
114
-
115
- .gridCell > div {
116
- width: 100%;
117
- height: 100%;
118
- min-height: 100%;
119
- }
120
-
121
- .gridCell img {
122
- width: 100%;
123
- height: 100%;
124
- object-fit: cover;
125
- }
126
-
127
- .videoOnly .videoSlot {
128
- width: 100%;
129
- max-width: 380px;
130
- margin: 0 auto;
131
- }
132
-
133
- @media (max-width: 640px) {
134
- .collage {
135
- flex-direction: column;
136
- gap: 0.5rem;
137
- }
138
-
139
- .videoSlot {
140
- width: 100%;
141
- max-width: none;
142
- margin: 0;
143
- }
144
-
145
- .imageGrid {
146
- min-height: 200px;
147
- }
148
- }
@@ -1,184 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useRef, useEffect, useCallback } from 'react';
4
- import ViaViaImage from '@/components/image';
5
- import { getImageUrl } from '@/constants/images';
6
- 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';
10
- import styles from './BookingFlowCollage.module.css';
11
-
12
- export interface BookingFlowCollageProps {
13
- /** Video for the left slot - vertical aspect ratio */
14
- video?: VideoSources;
15
- /** Poster image for video (fallback before play) */
16
- videoPosterImageId?: string;
17
- /** 4 image IDs for the right-side grid */
18
- imageIds: string[];
19
- /** Alt text prefix for images (e.g. product name) */
20
- altPrefix?: string;
21
- }
22
-
23
- const DEFAULT_VIDEO: VideoSources = {
24
- src: '/videos/via-via-moraine-lake-tour-video.mp4',
25
- webm: '/videos/via-via-moraine-lake-tour-video.webm',
26
- };
27
-
28
- export function BookingFlowCollage({
29
- video,
30
- videoPosterImageId,
31
- imageIds,
32
- altPrefix = 'Tour',
33
- }: BookingFlowCollageProps) {
34
- const videoSrc = video ?? DEFAULT_VIDEO;
35
- // Use long version in BookingFlow when available; fall back to short
36
- const videoForCollage = (videoSrc.longSrc && videoSrc.longWebm)
37
- ? { src: videoSrc.longSrc, webm: videoSrc.longWebm }
38
- : { src: videoSrc.src, webm: videoSrc.webm };
39
- const posterUrl = videoPosterImageId ? getImageUrl(videoPosterImageId) : undefined;
40
- const gridImages = imageIds.slice(0, 4);
41
- // Pad with first image if fewer than 4
42
- while (gridImages.length < 4 && gridImages.length > 0) {
43
- gridImages.push(gridImages[0]!);
44
- }
45
- const hasGridImages = gridImages.length > 0;
46
-
47
- const [selectedImageIndex, setSelectedImageIndex] = useState<number | null>(null);
48
- const videoRef = useRef<HTMLVideoElement | null>(null);
49
- const [isPaused, setIsPaused] = useState(false);
50
- const [isMuted, setIsMuted] = useState(true);
51
- const { t } = useTranslations();
52
-
53
- const syncStateFromVideo = useCallback(() => {
54
- const el = videoRef.current;
55
- if (!el) return;
56
- setIsPaused(el.paused);
57
- setIsMuted(el.muted);
58
- }, []);
59
-
60
- useEffect(() => {
61
- const el = videoRef.current;
62
- if (!el) return;
63
- syncStateFromVideo();
64
- el.addEventListener('play', syncStateFromVideo);
65
- el.addEventListener('pause', syncStateFromVideo);
66
- el.addEventListener('volumechange', syncStateFromVideo);
67
- return () => {
68
- el.removeEventListener('play', syncStateFromVideo);
69
- el.removeEventListener('pause', syncStateFromVideo);
70
- el.removeEventListener('volumechange', syncStateFromVideo);
71
- };
72
- }, [syncStateFromVideo, videoSrc]);
73
-
74
- const handleTogglePlay = useCallback(() => {
75
- const el = videoRef.current;
76
- if (!el) return;
77
- if (el.paused) el.play();
78
- else el.pause();
79
- }, []);
80
-
81
- const handleToggleMute = useCallback(() => {
82
- const el = videoRef.current;
83
- if (!el) return;
84
- el.muted = !el.muted;
85
- }, []);
86
-
87
- const imageItems = gridImages.map((id, i) => ({ id, alt: `${altPrefix} - ${i + 1}` }));
88
-
89
- return (
90
- <div className={`${styles.collage} ${!hasGridImages ? styles.videoOnly : ''}`}>
91
- <div className={styles.videoSlot}>
92
- <div className={styles.videoWrapper}>
93
- <BackgroundPlayer
94
- ref={videoRef}
95
- {...videoForCollage}
96
- autoPlay
97
- muted
98
- loop
99
- playsInline
100
- poster={posterUrl}
101
- className={styles.video}
102
- />
103
- <div className={styles.videoControls}>
104
- <button
105
- type="button"
106
- className={styles.videoControlBtn}
107
- onClick={handleTogglePlay}
108
- aria-label={isPaused ? t('common.videoPlay') : t('common.videoPause')}
109
- >
110
- {isPaused ? (
111
- <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
112
- <path d="M8 5v14l11-7z" />
113
- </svg>
114
- ) : (
115
- <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
116
- <path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
117
- </svg>
118
- )}
119
- </button>
120
- <button
121
- type="button"
122
- className={styles.videoControlBtn}
123
- onClick={handleToggleMute}
124
- aria-label={isMuted ? t('common.videoUnmute') : t('common.videoMute')}
125
- >
126
- {isMuted ? (
127
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
128
- <path d="M11 5L6 9H2v6h4l5 4V5z" />
129
- <line x1="23" y1="9" x2="17" y2="15" />
130
- <line x1="17" y1="9" x2="23" y2="15" />
131
- </svg>
132
- ) : (
133
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
134
- <path d="M11 5L6 9H2v6h4l5 4V5z" />
135
- <path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07" />
136
- </svg>
137
- )}
138
- </button>
139
- </div>
140
- </div>
141
- </div>
142
- {hasGridImages && (
143
- <div className={styles.imageGrid}>
144
- {gridImages.map((id, i) => (
145
- <button
146
- key={`${id}-${i}`}
147
- type="button"
148
- className={styles.gridCell}
149
- onClick={() => setSelectedImageIndex(i)}
150
- aria-label={`View image ${i + 1}`}
151
- >
152
- <ViaViaImage
153
- imageId={id}
154
- alt={`${altPrefix} - ${i + 1}`}
155
- context="GALLERY"
156
- />
157
- </button>
158
- ))}
159
- </div>
160
- )}
161
-
162
- {selectedImageIndex !== null && (
163
- <ImageModal
164
- selectedImage={imageItems[selectedImageIndex]!}
165
- currentIndex={selectedImageIndex}
166
- totalImages={imageItems.length}
167
- images={imageItems}
168
- onClose={() => setSelectedImageIndex(null)}
169
- onNext={() => {
170
- if (selectedImageIndex < imageItems.length - 1) {
171
- setSelectedImageIndex(selectedImageIndex + 1);
172
- }
173
- }}
174
- onPrevious={() => {
175
- if (selectedImageIndex > 0) {
176
- setSelectedImageIndex(selectedImageIndex - 1);
177
- }
178
- }}
179
- overlayZIndex={10000}
180
- />
181
- )}
182
- </div>
183
- );
184
- }
@@ -1,27 +0,0 @@
1
- .placeholder {
2
- padding: 1rem 0;
3
- text-align: center;
4
- }
5
-
6
- .message {
7
- font-size: 0.9375rem;
8
- line-height: 1.6;
9
- color: #4b5563;
10
- margin: 0 0 1rem 0;
11
- }
12
-
13
- .productName {
14
- font-size: 0.875rem;
15
- color: #6b7280;
16
- margin: 0 0 0.5rem 0;
17
- }
18
-
19
- .productName strong {
20
- color: #111;
21
- }
22
-
23
- .hint {
24
- font-size: 0.8125rem;
25
- color: #9ca3af;
26
- margin: 0;
27
- }
@@ -1,25 +0,0 @@
1
- 'use client';
2
-
3
- import type { ProductConfig } from '@/lib/booking-types';
4
- import styles from './BookingFlowPlaceholder.module.css';
5
-
6
- interface BookingFlowPlaceholderProps {
7
- product: ProductConfig;
8
- }
9
-
10
- export default function BookingFlowPlaceholder({ product }: BookingFlowPlaceholderProps) {
11
- return (
12
- <div className={styles.placeholder}>
13
- <p className={styles.message}>
14
- The booking flow will be integrated here using the logic from{' '}
15
- <code>~/ticketbooth/ticketbooth-fe/booking</code>.
16
- </p>
17
- <p className={styles.productName}>
18
- Selected: <strong>{product.display.shortName}</strong>
19
- </p>
20
- <p className={styles.hint}>
21
- Use the Back button to return to the product.
22
- </p>
23
- </div>
24
- );
25
- }
@@ -1,51 +0,0 @@
1
- 'use client';
2
-
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';
8
- import { BookingFlowCollage } from './BookingFlowCollage';
9
- import { TourDescription } from './TourDescription';
10
-
11
- /**
12
- * Renders collage + TourDescription using only products-config and getProducts().
13
- * No API call required - shows immediately.
14
- */
15
- export function BookingFlowPreview({ productId, defaultExpanded = true }: { productId: string; defaultExpanded?: boolean }) {
16
- const config = getProductByIdOrSlug(productId);
17
- const hasFiredViewItem = useRef(false);
18
-
19
- useEffect(() => {
20
- if (!hasFiredViewItem.current && productId && config) {
21
- hasFiredViewItem.current = true;
22
- const displayProducts = getProducts(defaultStrings);
23
- const displayProduct = Object.values(displayProducts).find((p) => p.id === productId);
24
- const productName = displayProduct?.name ?? config.display?.shortName ?? productId;
25
- const price = displayProduct?.avgPrice ?? 0;
26
- trackViewItem(productId, productName, price, 'CAD');
27
- }
28
- }, [productId, config]);
29
- const displayProducts = getProducts(defaultStrings);
30
- const displayProduct = Object.values(displayProducts).find((p) => p.id === productId);
31
- const collageImageIds = config?.display?.collageImageIds ?? config?.display?.imageIds ?? [];
32
- const hasVideo = !!displayProduct?.videoUrl;
33
- const hasImages = collageImageIds.length > 0;
34
- const altPrefix = config?.display?.shortName ?? productId;
35
-
36
- return (
37
- <div className="booking-flow-root space-y-8">
38
- {productId && (hasVideo || hasImages) && (
39
- <div className="booking-collage-wrapper">
40
- <BookingFlowCollage
41
- video={displayProduct?.videoUrl}
42
- videoPosterImageId={config?.display?.imageIds?.[0]}
43
- imageIds={hasImages ? collageImageIds : [config?.display?.imageIds?.[0]].filter(Boolean) as string[]}
44
- altPrefix={altPrefix}
45
- />
46
- </div>
47
- )}
48
- <TourDescription productSlug={productId} defaultExpanded={defaultExpanded} />
49
- </div>
50
- );
51
- }