@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
@@ -1,210 +0,0 @@
1
- const busIconPath = '/pill-value-icons/bus-icon.svg';
2
- const clockIconPath = '/pill-value-icons/clock-icon.svg';
3
- const sunriseIconPath = '/pill-value-icons/sun-icon.svg';
4
- const cameraIconPath = '/pill-value-icons/camera-icon.svg';
5
- const moneyIconPath = '/pill-value-icons/money-icon.svg';
6
- const doubleCheckIconPath = '/pill-value-icons/double-check-icon.svg';
7
- const hikerIconPath = '/pill-value-icons/hiker-icon.svg';
8
- const waterIconPath = '/pill-value-icons/water-icon.svg';
9
- const lunchIconPath = '/pill-value-icons/lunch-icon.svg';
10
- const croissantIconPath = '/pill-value-icons/croissant-icon.svg';
11
- const locationPinIconPath = '/pill-value-icons/location-pin-icon.svg';
12
- const addTimeIconPath = '/pill-value-icons/add-time-icon.svg';
13
- const coffeeIconPath = '/pill-value-icons/coffee-icon.svg';
14
- const blanketIconPath = '/pill-value-icons/blanket-icon.svg';
15
- import defaultStrings from '@/strings';
16
- import { ProductKey } from './products';
17
-
18
- export type PillValue = {
19
- icon: string;
20
- label: string;
21
- }
22
-
23
- export function createDeparturePillValue(productKey: ProductKey, strings = defaultStrings): PillValue {
24
- return {
25
- icon: busIconPath,
26
- label: getDepartureLabel(productKey, strings)
27
- };
28
- }
29
-
30
- function getDepartureLabel(productKey: ProductKey, strings: any): string {
31
- switch (productKey) {
32
- case 'two-lakes-combo':
33
- return strings.pillValues.departurePillValues.twoLakesCombo;
34
- case 'moraine-lake-adventure':
35
- return strings.pillValues.departurePillValues.moraineLakeAdventure;
36
- case 'lake-louise-adventure':
37
- return strings.pillValues.departurePillValues.lakeLouiseAdventure;
38
- case 'emerald-lake-escape':
39
- return strings.pillValues.departurePillValues.emeraldLakeEscapeTour;
40
- case 'private-tour':
41
- return strings.pillValues.departurePillValues.privateTour;
42
- case 'afternoon-delight':
43
- return strings.pillValues.departurePillValues.afternoonDelight;
44
- default:
45
- return '';
46
- }
47
- }
48
-
49
- export function createDurationPillValue(productKey: ProductKey, strings = defaultStrings): PillValue {
50
- return {
51
- icon: clockIconPath,
52
- label: getDurationLabel(productKey, strings)
53
- };
54
- }
55
-
56
- function getDurationLabel(productKey: ProductKey, strings: any): string {
57
- switch (productKey) {
58
- case 'moraine-lake-sunrise':
59
- return strings.pillValues.durationPillValues.moraineLakeSunrise;
60
- case 'moraine-lake-sunrise-lake-louise-golden-hour-1':
61
- return strings.pillValues.durationPillValues.moraineLakeSunriseLakeLouiseGoldenHour1;
62
- case 'moraine-lake-sunrise-lake-louise-golden-hour-2':
63
- return strings.pillValues.durationPillValues.moraineLakeSunriseLakeLouiseGoldenHour2;
64
- case 'two-lakes-combo-1':
65
- return strings.pillValues.durationPillValues.twoLakesCombo1;
66
- case 'two-lakes-combo-2':
67
- return strings.pillValues.durationPillValues.twoLakesCombo2;
68
- case 'moraine-lake-adventure':
69
- return strings.pillValues.durationPillValues.moraineLakeAdventure;
70
- case 'lake-louise-adventure':
71
- return strings.pillValues.durationPillValues.lakeLouiseAdventure;
72
- case 'emerald-lake-escape':
73
- return strings.pillValues.durationPillValues.emeraldLakeEscapeTour;
74
- case 'private-tour':
75
- return strings.pillValues.durationPillValues.privateTour;
76
- case 'afternoon-delight1':
77
- return strings.pillValues.durationPillValues.afternoonDelight1;
78
- case 'afternoon-delight2':
79
- return strings.pillValues.durationPillValues.afternoonDelight2;
80
- default:
81
- return '';
82
- }
83
- }
84
-
85
- export function createAddTimePillValue(productKey: ProductKey, strings = defaultStrings): PillValue {
86
- return {
87
- icon: addTimeIconPath,
88
- label: getAddTimeLabel(productKey, strings)
89
- };
90
- }
91
-
92
- function getAddTimeLabel(productKey: ProductKey, strings: any): string {
93
- switch (productKey) {
94
- case 'moraine-lake-sunrise':
95
- return strings.pillValues.addTimePillValues.moraineLakeSunrise;
96
- case 'moraine-lake-sunrise-lake-louise-golden-hour':
97
- return strings.pillValues.addTimePillValues.moraineLakeSunriseLakeLouiseGoldenHour;
98
- case 'two-lakes-combo':
99
- return strings.pillValues.addTimePillValues.twoLakesCombo;
100
- case 'moraine-lake-adventure':
101
- return strings.pillValues.addTimePillValues.moraineLakeAdventure;
102
- case 'lake-louise-adventure':
103
- return strings.pillValues.addTimePillValues.lakeLouiseAdventure;
104
- case 'emerald-lake-escape':
105
- return strings.pillValues.addTimePillValues.emeraldLakeEscapeTour;
106
- case 'private-tour':
107
- return strings.pillValues.addTimePillValues.privateTour;
108
- default:
109
- return '';
110
- }
111
- }
112
-
113
- export function createMoneyPillValue(productKey: ProductKey, strings = defaultStrings): PillValue {
114
- return {
115
- icon: moneyIconPath,
116
- label: getMoneyLabel(productKey, strings)
117
- };
118
- }
119
-
120
- function getMoneyLabel(productKey: ProductKey, strings: any): string {
121
- switch (productKey) {
122
- case 'moraine-lake-sunrise':
123
- return strings.pillValues.moneyPillValues.moraineLakeSunrise;
124
- case 'moraine-lake-sunrise-lake-louise-golden-hour':
125
- return strings.pillValues.moneyPillValues.moraineLakeSunriseLakeLouiseGoldenHour;
126
- case 'two-lakes-combo':
127
- return strings.pillValues.moneyPillValues.twoLakesCombo;
128
- case 'moraine-lake-adventure':
129
- return strings.pillValues.moneyPillValues.moraineLakeAdventure;
130
- case 'lake-louise-adventure':
131
- return strings.pillValues.moneyPillValues.lakeLouiseAdventure;
132
- case 'emerald-lake-escape':
133
- return strings.pillValues.moneyPillValues.emeraldLakeEscapeTour;
134
- case 'private-tour':
135
- return strings.pillValues.moneyPillValues.privateTour;
136
- case 'afternoon-delight':
137
- return strings.pillValues.moneyPillValues.afternoonDelight;
138
- default:
139
- return '';
140
- }
141
- }
142
-
143
- export function createemeraldLakeEscapeTourLocationsPillValues(strings = defaultStrings): PillValue[] {
144
- return Object.values(strings.pillValues.emeraldLakeEscapeTourLocations).map(location => ({
145
- icon: locationPinIconPath,
146
- label: location as string
147
- }));
148
- }
149
- export const createSunrisePillValue = (strings = defaultStrings): PillValue => {
150
- return {
151
- icon: sunriseIconPath,
152
- label: strings.pillValues.sunrise
153
- };
154
- }
155
-
156
- export const createTwoLakesInOnePillValue = (strings = defaultStrings): PillValue => {
157
- return {
158
- icon: doubleCheckIconPath,
159
- label: strings.pillValues.twoLakesInOne
160
- };
161
- }
162
-
163
- export const createPhotographyPillValue = (strings = defaultStrings): PillValue => {
164
- return {
165
- icon: cameraIconPath,
166
- label: strings.pillValues.photography
167
- };
168
- }
169
-
170
- export const createHikePillValue = (strings = defaultStrings): PillValue => {
171
- return {
172
- icon: hikerIconPath,
173
- label: strings.pillValues.hike
174
- };
175
- }
176
-
177
- export const createCanoePillValue = (strings = defaultStrings): PillValue => {
178
- return {
179
- icon: waterIconPath,
180
- label: strings.pillValues.canoe
181
- };
182
- }
183
-
184
- export const createLunchPillValue = (strings = defaultStrings): PillValue => {
185
- return {
186
- icon: lunchIconPath,
187
- label: strings.pillValues.lunch
188
- };
189
- }
190
-
191
- export const createCroissantPillValue = (strings = defaultStrings): PillValue => {
192
- return {
193
- icon: croissantIconPath,
194
- label: strings.pillValues.croissant
195
- };
196
- }
197
-
198
- export const createHotDrinksPillValue = (strings = defaultStrings): PillValue => {
199
- return {
200
- icon: coffeeIconPath,
201
- label: strings.pillValues.hotDrinks
202
- };
203
- }
204
-
205
- export const createCozyBlanketsPillValue = (strings = defaultStrings): PillValue => {
206
- return {
207
- icon: blanketIconPath,
208
- label: strings.pillValues.blankets
209
- };
210
- }
@@ -1,155 +0,0 @@
1
- import { ImageData, IMAGES } from './images';
2
- import { PillValue, createDeparturePillValue, createDurationPillValue, createSunrisePillValue, createTwoLakesInOnePillValue, createHikePillValue, createCanoePillValue, createMoneyPillValue, createAddTimePillValue, createHotDrinksPillValue, createLunchPillValue, createCroissantPillValue, createemeraldLakeEscapeTourLocationsPillValues, createCozyBlanketsPillValue } from './pill-values';
3
- import { ProductTagStyle } from '@/components/product-tag';
4
-
5
- export interface ProductTag {
6
- text: string;
7
- style: ProductTagStyle;
8
- }
9
-
10
- export interface VideoSources {
11
- src: string;
12
- webm: string;
13
- /** Long version for BookingFlow collage. Falls back to short (src/webm) if omitted. */
14
- longSrc?: string;
15
- longWebm?: string;
16
- }
17
-
18
- export interface Product {
19
- id: string;
20
- name: string;
21
- shortName: string;
22
- images: readonly ImageData[];
23
- bookingLink: string;
24
- currentStartTime: string;
25
- description: string;
26
- path: string;
27
- avgPrice: number;
28
- pillValues: PillValue[];
29
- tags?: ProductTag[];
30
- /** Video for expanded view in booking dialog. Place mp4 and webm in public/videos/ named {productId}.mp4 and {productId}.webm */
31
- videoUrl?: VideoSources;
32
- }
33
-
34
- // Type for accessing product keys
35
- export type ProductKey = keyof ReturnType<typeof getProducts>;
36
-
37
- export const getProducts = (strings: any): Record<string, Product> => ({
38
- MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR: {
39
- id: 'moraine-lake-sunrise-lake-louise-golden-hour',
40
- videoUrl: { src: '/videos/moraine-lake-sunrise-lake-louise-golden-hour.mp4', webm: '/videos/moraine-lake-sunrise-lake-louise-golden-hour.webm', longSrc: '/videos/moraine-lake-sunrise-lake-louise-golden-hour-long.mp4', longWebm: '/videos/moraine-lake-sunrise-lake-louise-golden-hour-long.webm' },
41
- name: strings.products.productNames.moraineLakeSunriseLakeLouiseGoldenHour,
42
- shortName: strings.products.productShortNames.moraineLakeSunriseLakeLouiseGoldenHourShort,
43
- images: [
44
- IMAGES.MORAINE_LAKE_CANOES_SUNRISE,
45
- ],
46
- bookingLink: '#book-now/moraine-lake-sunrise-lake-louise-golden-hour',
47
- currentStartTime: strings.products.startTimes.moraineLakeSunriseLakeLouiseGoldenHour,
48
- description: strings.productThemePages.sunriseTours.description,
49
- path: '/moraine-lake-sunrise-shuttle',
50
- avgPrice: 159,
51
- pillValues: [createSunrisePillValue(strings), createDurationPillValue('moraine-lake-sunrise-lake-louise-golden-hour-1', strings), createDurationPillValue('moraine-lake-sunrise-lake-louise-golden-hour-2', strings), createAddTimePillValue('moraine-lake-sunrise-lake-louise-golden-hour', strings), createHotDrinksPillValue(strings), createCozyBlanketsPillValue(strings), createTwoLakesInOnePillValue(strings), createMoneyPillValue('moraine-lake-sunrise-lake-louise-golden-hour', strings)],
52
- tags: [{
53
- text: strings.products.productTags.mostPopular,
54
- style: ProductTagStyle.MOST_POPULAR
55
- }]
56
- },
57
- MORAINE_LAKE_SUNRISE: {
58
- id: 'moraine-lake-sunrise',
59
- videoUrl: { src: '/videos/moraine-lake-sunrise.mp4', webm: '/videos/moraine-lake-sunrise.webm', longSrc: '/videos/moraine-lake-sunrise-long.mp4', longWebm: '/videos/moraine-lake-sunrise-long.webm' },
60
- name: strings.products.productNames.moraineLakeSunrise,
61
- shortName: strings.products.productShortNames.moraineLakeSunriseShort,
62
- images: [
63
- IMAGES.MORAINE_LAKE_SUNRISE_CHEERS,
64
- ],
65
- bookingLink: '#book-now/moraine-lake-sunrise',
66
- currentStartTime: strings.products.startTimes.moraineLakeSunrise,
67
- description: strings.productThemePages.sunriseTours.description,
68
- path: '/moraine-lake-sunrise-shuttle',
69
- avgPrice: 129,
70
- pillValues: [createSunrisePillValue(strings), createDurationPillValue('moraine-lake-sunrise', strings), createAddTimePillValue('moraine-lake-sunrise', strings), createHotDrinksPillValue(strings), createCozyBlanketsPillValue(strings), createMoneyPillValue('moraine-lake-sunrise', strings)]
71
- },
72
- TWO_LAKES_COMBO: {
73
- id: 'two-lakes-combo',
74
- videoUrl: { src: '/videos/two-lakes-combo.mp4', webm: '/videos/two-lakes-combo.webm', longSrc: '/videos/two-lakes-combo-long.mp4', longWebm: '/videos/two-lakes-combo-long.webm' },
75
- name: strings.products.productNames.twoLakesCombo,
76
- shortName: strings.products.productShortNames.twoLakesComboShort,
77
- images: [
78
- IMAGES.MORAINE_LAKE_YELLOW_BIKINI,
79
- ],
80
- bookingLink: '#book-now/two-lakes-combo',
81
- currentStartTime: strings.products.startTimes.moraineLakeAdventure,
82
- description: strings.products.productDescriptions.twoLakesCombo,
83
- path: '/moraine-lake-shuttle',
84
- avgPrice: 149,
85
- pillValues: [createDeparturePillValue('two-lakes-combo', strings), createDurationPillValue('two-lakes-combo-1', strings), createDurationPillValue('two-lakes-combo-2', strings), createAddTimePillValue('two-lakes-combo', strings), createTwoLakesInOnePillValue(strings), createMoneyPillValue('two-lakes-combo', strings)],
86
- tags: [{
87
- text: strings.products.productTags.mostPopular,
88
- style: ProductTagStyle.MOST_POPULAR
89
- }]
90
- },
91
- MORAINE_LAKE_ADVENTURE: {
92
- id: 'moraine-lake-adventure',
93
- videoUrl: { src: '/videos/moraine-lake-adventure.mp4', webm: '/videos/moraine-lake-adventure.webm', longSrc: '/videos/moraine-lake-adventure-long.mp4', longWebm: '/videos/moraine-lake-adventure-long.webm' },
94
- name: strings.products.productNames.moraineLakeAdventure,
95
- shortName: strings.products.productShortNames.moraineLakeAdventureShort,
96
- images: [
97
- IMAGES.MORAINE_LAKE_CANOE,
98
- ],
99
- bookingLink: '#book-now/moraine-lake-adventure',
100
- currentStartTime: strings.products.startTimes.moraineLakeAdventure,
101
- description: strings.productThemePages.moraineLakeShuttle.description,
102
- path: '/moraine-lake-shuttle',
103
- avgPrice: 95,
104
- pillValues: [createDeparturePillValue('moraine-lake-adventure', strings), createDurationPillValue('moraine-lake-adventure', strings), createAddTimePillValue('moraine-lake-adventure', strings), createHikePillValue(strings), createCanoePillValue(strings), createMoneyPillValue('moraine-lake-adventure', strings)]
105
- },
106
- LAKE_LOUISE_ADVENTURE: {
107
- id: 'lake-louise-adventure',
108
- videoUrl: { src: '/videos/lake-louise-adventure.mp4', webm: '/videos/lake-louise-adventure.webm', longSrc: '/videos/lake-louise-adventure-long.mp4', longWebm: '/videos/lake-louise-adventure-long.webm' },
109
- name: strings.products.productNames.lakeLouiseAdventure,
110
- shortName: strings.products.productShortNames.lakeLouiseAdventureShort,
111
- images: [
112
- IMAGES.LAKE_LOUISE_FLORA,
113
- ],
114
- bookingLink: '#book-now/lake-louise-adventure',
115
- currentStartTime: strings.products.startTimes.lakeLouiseAdventure,
116
- description: strings.productThemePages.lakeLouiseShuttle.description,
117
- path: '/lake-louise-shuttle',
118
- avgPrice: 69,
119
- pillValues: [createDeparturePillValue('lake-louise-adventure', strings), createDurationPillValue('lake-louise-adventure', strings), createAddTimePillValue('lake-louise-adventure', strings), createHikePillValue(strings), createCanoePillValue(strings), createMoneyPillValue('lake-louise-adventure', strings)]
120
- },
121
- EMERALD_LAKE_ESCAPE: {
122
- id: 'emerald-lake-escape',
123
- videoUrl: { src: '/videos/emerald-lake-escape-tour.mp4', webm: '/videos/emerald-lake-escape-tour.webm', longSrc: '/videos/emerald-lake-escape-tour-long.mp4', longWebm: '/videos/emerald-lake-escape-tour-long.webm' },
124
- name: strings.products.productNames.emeraldLakeEscapeTour,
125
- shortName: strings.products.productShortNames.emeraldLakeEscapeTourShort,
126
- images: [
127
- IMAGES.EMERALD_LAKE_LODGE_VIEWPOINT,
128
- ],
129
- bookingLink: '#book-now/emerald-lake-escape',
130
- currentStartTime: strings.products.startTimes.emeraldLakeEscape,
131
- description: strings.productThemePages.emeraldLakeShuttle.description,
132
- path: '/emerald-lake-shuttle',
133
- avgPrice: 175,
134
- pillValues: [createDeparturePillValue('emerald-lake-escape', strings), createDurationPillValue('emerald-lake-escape', strings), ...createemeraldLakeEscapeTourLocationsPillValues(strings), createLunchPillValue(strings), createMoneyPillValue('emerald-lake-escape', strings)]
135
- },
136
- PRIVATE_TOUR: {
137
- id: 'private-tour',
138
- videoUrl: { src: '/videos/private-tour.mp4', webm: '/videos/private-tour.webm', longSrc: '/videos/private-tour-long.mp4', longWebm: '/videos/private-tour-long.webm' },
139
- name: strings.products.productNames.privateTour,
140
- shortName: strings.products.productShortNames.privateTourShort,
141
- images: [
142
- IMAGES.PRIVATE_TOUR_FAMILY_MORAINE_LAKE,
143
- ],
144
- bookingLink: '#book-now/private-tour',
145
- currentStartTime: strings.products.startTimes.privateTour,
146
- description: strings.productThemePages.privateTours.description,
147
- path: '/private-shuttle',
148
- avgPrice: 1699,
149
- pillValues: [createDeparturePillValue('private-tour', strings), createDurationPillValue('private-tour', strings), createCroissantPillValue(strings), createHotDrinksPillValue(strings), createMoneyPillValue('private-tour', strings)]
150
- }
151
- });
152
-
153
- // Default export for backward compatibility (uses English strings)
154
- import defaultStrings from '@/strings';
155
- export const PRODUCTS = getProducts(defaultStrings);
@@ -1,125 +0,0 @@
1
- 'use client';
2
-
3
- import {
4
- createContext,
5
- useContext,
6
- useRef,
7
- useCallback,
8
- type ReactNode,
9
- } from 'react';
10
- import type { Availability, PricingConfig, PrecomputedPricesByCategory } from '@/lib/booking-api';
11
-
12
- /** Cache TTL in milliseconds (5 minutes). Entries older than this are considered stale. */
13
- export const AVAILABILITIES_CACHE_TTL_MS = 5 * 60 * 1000;
14
-
15
- export interface CachedAvailabilitiesData {
16
- fetchedRanges: Array<{ start: Date; end: Date }>;
17
- availabilities: Availability[];
18
- pricingConfig: PricingConfig | null;
19
- precomputedPricesByOption: Record<string, PrecomputedPricesByCategory> | null;
20
- /** Private Shuttle: shared precomputed prices (category -> currency -> price). */
21
- precomputedPrices?: PrecomputedPricesByCategory | null;
22
- /** Private Shuttle: resource price by currency. */
23
- resourcePriceByCurrency?: Record<string, number> | null;
24
- /** Private Shuttle: resource price by option (optionId -> currency -> price). */
25
- resourcePriceByOption?: Record<string, Record<string, number>> | null;
26
- /** Timestamp when this entry was cached (for TTL / stale-while-revalidate). */
27
- cachedAt: number;
28
- }
29
-
30
- interface AvailabilitiesCacheContextValue {
31
- /** Get cached data for a product+options+promo. Returns undefined if not cached. */
32
- get: (cacheKey: string) => CachedAvailabilitiesData | undefined;
33
- /** Check if cached entry is stale (older than TTL). Use for stale-while-revalidate. */
34
- isStale: (cached: CachedAvailabilitiesData) => boolean;
35
- /** Replace or set cache entry. */
36
- set: (cacheKey: string, data: CachedAvailabilitiesData) => void;
37
- /** Merge new availabilities into existing cache (or create new entry). */
38
- merge: (
39
- cacheKey: string,
40
- update: {
41
- fetchedRanges?: Array<{ start: Date; end: Date }>;
42
- availabilities?: Availability[];
43
- pricingConfig?: PricingConfig | null;
44
- precomputedPricesByOption?: Record<string, PrecomputedPricesByCategory> | null;
45
- precomputedPrices?: PrecomputedPricesByCategory | null;
46
- resourcePriceByCurrency?: Record<string, number> | null;
47
- resourcePriceByOption?: Record<string, Record<string, number>> | null;
48
- }
49
- ) => void;
50
- }
51
-
52
- const AvailabilitiesCacheContext = createContext<AvailabilitiesCacheContextValue | null>(null);
53
-
54
- export function useAvailabilitiesCache() {
55
- const ctx = useContext(AvailabilitiesCacheContext);
56
- return ctx; // May be null if not wrapped
57
- }
58
-
59
- /** Build cache key from product + options + promo + optional partner pricing profile. */
60
- export function buildAvailabilitiesCacheKey(
61
- productId: string,
62
- optionIdsKey: string,
63
- promoCode: string | null,
64
- pricingProfileId?: string | null
65
- ): string {
66
- const pp = (pricingProfileId ?? '').trim();
67
- return `${productId}|${optionIdsKey}|${promoCode ?? ''}|pp:${pp}`;
68
- }
69
-
70
- export function AvailabilitiesCacheProvider({ children }: { children: ReactNode }) {
71
- const cacheRef = useRef<Map<string, CachedAvailabilitiesData>>(new Map());
72
-
73
- const get = useCallback((cacheKey: string) => {
74
- return cacheRef.current.get(cacheKey);
75
- }, []);
76
-
77
- const isStale = useCallback((cached: CachedAvailabilitiesData) => {
78
- const age = Date.now() - (cached.cachedAt ?? 0);
79
- return age > AVAILABILITIES_CACHE_TTL_MS;
80
- }, []);
81
-
82
- const set = useCallback((cacheKey: string, data: CachedAvailabilitiesData) => {
83
- cacheRef.current.set(cacheKey, data);
84
- }, []);
85
-
86
- const merge = useCallback(
87
- (
88
- cacheKey: string,
89
- update: {
90
- fetchedRanges?: Array<{ start: Date; end: Date }>;
91
- availabilities?: Availability[];
92
- pricingConfig?: PricingConfig | null;
93
- precomputedPricesByOption?: Record<string, PrecomputedPricesByCategory> | null;
94
- precomputedPrices?: PrecomputedPricesByCategory | null;
95
- resourcePriceByCurrency?: Record<string, number> | null;
96
- resourcePriceByOption?: Record<string, Record<string, number>> | null;
97
- }
98
- ) => {
99
- const existing = cacheRef.current.get(cacheKey);
100
- const next: CachedAvailabilitiesData = {
101
- fetchedRanges: update.fetchedRanges ?? existing?.fetchedRanges ?? [],
102
- availabilities: update.availabilities ?? existing?.availabilities ?? [],
103
- pricingConfig: update.pricingConfig !== undefined ? update.pricingConfig : existing?.pricingConfig ?? null,
104
- precomputedPricesByOption:
105
- update.precomputedPricesByOption !== undefined
106
- ? update.precomputedPricesByOption
107
- : existing?.precomputedPricesByOption ?? null,
108
- precomputedPrices: update.precomputedPrices !== undefined ? update.precomputedPrices : existing?.precomputedPrices ?? null,
109
- resourcePriceByCurrency: update.resourcePriceByCurrency !== undefined ? update.resourcePriceByCurrency : existing?.resourcePriceByCurrency ?? null,
110
- resourcePriceByOption: update.resourcePriceByOption !== undefined ? update.resourcePriceByOption : existing?.resourcePriceByOption ?? null,
111
- cachedAt: Date.now(),
112
- };
113
- cacheRef.current.set(cacheKey, next);
114
- },
115
- []
116
- );
117
-
118
- const value: AvailabilitiesCacheContextValue = { get, set, merge, isStale };
119
-
120
- return (
121
- <AvailabilitiesCacheContext.Provider value={value}>
122
- {children}
123
- </AvailabilitiesCacheContext.Provider>
124
- );
125
- }
@@ -1,70 +0,0 @@
1
- 'use client';
2
-
3
- import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
4
- import { getCompany, type Company } from '@/lib/booking-api';
5
- import { ENV } from '@/lib/env';
6
-
7
- interface CompanyContextType {
8
- company: Company | null;
9
- loading: boolean;
10
- error: string | null;
11
- }
12
-
13
- const CompanyContext = createContext<CompanyContextType | undefined>(undefined);
14
-
15
- interface CompanyProviderProps {
16
- children: ReactNode;
17
- }
18
-
19
- /**
20
- * Provider component that fetches company data once and makes it available via context
21
- */
22
- export function CompanyProvider({ children }: CompanyProviderProps) {
23
- const [company, setCompany] = useState<Company | null>(null);
24
- const [loading, setLoading] = useState(true);
25
- const [error, setError] = useState<string | null>(null);
26
-
27
- useEffect(() => {
28
- async function fetchCompany() {
29
- try {
30
- const companyData = await getCompany(ENV.COMPANY_ID);
31
- setCompany(companyData);
32
- setError(null);
33
- } catch (err) {
34
- console.warn('Failed to fetch company data, using defaults:', err);
35
- setError(err instanceof Error ? err.message : 'Failed to fetch company');
36
- // Don't set company to null - let components use defaults
37
- } finally {
38
- setLoading(false);
39
- }
40
- }
41
-
42
- fetchCompany();
43
- }, []);
44
-
45
- return (
46
- <CompanyContext.Provider value={{ company, loading, error }}>
47
- {children}
48
- </CompanyContext.Provider>
49
- );
50
- }
51
-
52
- /**
53
- * Hook to access company context
54
- * Returns company data, loading state, and error
55
- */
56
- export function useCompany() {
57
- const context = useContext(CompanyContext);
58
- if (context === undefined) {
59
- throw new Error('useCompany must be used within a CompanyProvider');
60
- }
61
- return context;
62
- }
63
-
64
- /**
65
- * Hook to get company timezone with fallback
66
- */
67
- export function useCompanyTimezone(): string {
68
- const { company } = useCompany();
69
- return company?.settings?.timezone || 'America/Edmonton';
70
- }
@@ -1,61 +0,0 @@
1
- {
2
- "paragraphs": [
3
- "Looking to create memories with your loved ones in one of the most breathtaking places on earth? Let us help you turn it into an unforgettable experience.",
4
- "Whether it's a joyful family gathering, a meaningful moment with friends, or a romantic couples shoot, we know how to blend into the moment while capturing it beautifully - candid, natural, and full of connection."
5
- ],
6
- "sections": [
7
- {
8
- "title": "What's included",
9
- "content": [
10
- {
11
- "title": "30-minute session",
12
- "items": [
13
- "πŸ“Έ 20 to 25 minutes of photography time",
14
- "πŸ“ 2 Locations within walking distance",
15
- "πŸ–ΌοΈ 25 professionally edited photos delivered in a personalized online gallery"
16
- ]
17
- },
18
- {
19
- "title": "60-minute session",
20
- "items": [
21
- "πŸ“Έ 50 to 55 minutes of photography time",
22
- "πŸ“ 2-3 Locations within walking distance",
23
- "πŸ–ΌοΈ 50 professionally edited photos delivered in a personalized online gallery"
24
- ]
25
- },
26
- {
27
- "title": "90-minute session",
28
- "items": [
29
- "πŸ“Έ 80-90 mins of Photography Time",
30
- "πŸ“ 5 Locations within walking distance",
31
- "πŸ–ΌοΈ 75 professionally edited photos delivered in a personalized online gallery"
32
- ]
33
- },
34
- {
35
- "title": "Other details",
36
- "items": [
37
- "πŸ–₯️ Editing: Basic exposure and lighting adjustments + colour corrections",
38
- "πŸ‘­πŸ½ 10 People Max per session"
39
- ]
40
- }
41
- ]
42
- },
43
- {
44
- "title": "Available add ons",
45
- "content": [
46
- "πŸƒβ€β™€οΈ Express Service - 7 Day turnaround on images - $300",
47
- "πŸƒβ€β™€οΈπŸ’¨ Super Express Service - 48-hour turnaround on images - $900",
48
- "πŸ”¬ Additional Editing: Object Removal / Skin Retouching - $199/item",
49
- "βž• Additional Images: 15 Additional Images - $99",
50
- "♾️ The Works - Get ALL additional images taken on the day - $199"
51
- ]
52
- },
53
- {
54
- "title": "Exclusions",
55
- "content": [
56
- "πŸ’΅ Tips or Gratuities",
57
- "πŸ…ΏοΈ Parking Fees"
58
- ]
59
- }
60
- ]
61
- }
@@ -1,60 +0,0 @@
1
- {
2
- "paragraphs": [
3
- "Planning to elope in one of the most breathtaking places on earth? Let us help you make it truly unforgettable. We know how to blend into the moment while capturing the joy of an intimate celebration beautifully."
4
- ],
5
- "sections": [
6
- {
7
- "title": "What's included",
8
- "content": [
9
- {
10
- "title": "30-minute session",
11
- "items": [
12
- "πŸ“Έ 20 to 25 minutes of photography time",
13
- "πŸ“ 2 Locations within walking distance",
14
- "πŸ–ΌοΈ 25 professionally edited photos delivered in a personalized online gallery"
15
- ]
16
- },
17
- {
18
- "title": "60-minute session",
19
- "items": [
20
- "πŸ“Έ 50 to 55 minutes of photography time",
21
- "πŸ“ 2-3 Locations within walking distance",
22
- "πŸ–ΌοΈ 50 professionally edited photos delivered in a personalized online gallery"
23
- ]
24
- },
25
- {
26
- "title": "90-minute session",
27
- "items": [
28
- "πŸ“Έ 80-90 mins of Photography Time",
29
- "πŸ“ 5 Locations within walking distance",
30
- "πŸ–ΌοΈ 75 professionally edited photos delivered in a personalized online gallery"
31
- ]
32
- },
33
- {
34
- "title": "Other details",
35
- "items": [
36
- "πŸ–₯️ Editing: Basic exposure and lighting adjustments + colour corrections",
37
- "πŸ‘­πŸ½ 2 People Max per session"
38
- ]
39
- }
40
- ]
41
- },
42
- {
43
- "title": "Available add ons",
44
- "content": [
45
- "πŸƒβ€β™€οΈ Express Service - 7 Day turnaround on images - $300",
46
- "πŸƒβ€β™€οΈπŸ’¨ Super Express Service - 48-hour turnaround on images - $900",
47
- "πŸ”¬ Additional Editing: Object Removal / Skin Retouching - $199/item",
48
- "βž• Additional Images: 15 Additional Images - $99",
49
- "♾️ The Works - Get ALL additional images taken on the day - $199"
50
- ]
51
- },
52
- {
53
- "title": "Exclusions",
54
- "content": [
55
- "πŸ’΅ Tips or Gratuities",
56
- "πŸ…ΏοΈ Parking Fees"
57
- ]
58
- }
59
- ]
60
- }