@ticketboothapp/booking 0.1.11 → 0.1.13

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 (255) hide show
  1. package/package.json +2 -1
  2. package/src/app/photo-sessions/photo-packages.ts +75 -0
  3. package/src/assets/icons/partner-logos/getyourguide.svg +8 -0
  4. package/src/assets/icons/plus.svg +3 -0
  5. package/src/colours.css +23 -0
  6. package/src/components/BookingDetails.module.css +1591 -0
  7. package/src/components/BookingDetails.tsx +2072 -354
  8. package/src/components/BookingWidget.tsx +28 -248
  9. package/src/components/JobApplicationDialog.module.css +440 -0
  10. package/src/components/JobApplicationDialog.tsx +620 -0
  11. package/src/components/ManageBookingView.tsx +28 -36
  12. package/src/components/PhoneInputWithCountry.module.css +131 -0
  13. package/src/components/PhoneInputWithCountry.tsx +44 -0
  14. package/src/components/PickupLocationDialog.module.css +360 -0
  15. package/src/components/PickupLocationDialog.tsx +357 -0
  16. package/src/components/PickupLocationMap.tsx +110 -0
  17. package/src/components/PostBookingDependentAddOnUpsell.module.css +174 -0
  18. package/src/components/PostBookingDependentAddOnUpsell.tsx +407 -0
  19. package/src/components/accordion.css +27 -0
  20. package/src/components/accordion.tsx +29 -0
  21. package/src/components/analytics/AnalyticsConsentRestore.tsx +19 -0
  22. package/src/components/analytics/AnalyticsScripts.tsx +106 -0
  23. package/src/components/analytics/CookieConsentBanner.css +86 -0
  24. package/src/components/analytics/CookieConsentBanner.tsx +102 -0
  25. package/src/components/booking/AddOnsSection.module.css +10 -0
  26. package/src/components/booking/AddOnsSection.tsx +184 -0
  27. package/src/components/booking/AdminPaymentChoiceModal.tsx +98 -0
  28. package/src/components/booking/BookingDialog.module.css +643 -0
  29. package/src/components/booking/BookingDialog.tsx +356 -0
  30. package/src/components/booking/BookingFlow.tsx +4385 -0
  31. package/src/components/booking/BookingFlowCollage.module.css +148 -0
  32. package/src/components/booking/BookingFlowCollage.tsx +184 -0
  33. package/src/components/booking/BookingFlowPlaceholder.module.css +27 -0
  34. package/src/components/booking/BookingFlowPlaceholder.tsx +25 -0
  35. package/src/components/booking/BookingFlowPreview.tsx +51 -0
  36. package/src/components/booking/BookingProductGrid.module.css +359 -0
  37. package/src/components/booking/BookingProductGrid.tsx +497 -0
  38. package/src/components/booking/Calendar.module.css +616 -0
  39. package/src/components/{Calendar.tsx → booking/Calendar.tsx} +464 -247
  40. package/src/components/booking/CancellationPolicySelector.module.css +124 -0
  41. package/src/components/booking/CancellationPolicySelector.tsx +142 -0
  42. package/src/components/booking/ChangeBookingDialog.tsx +562 -0
  43. package/src/components/booking/CheckoutForm.module.css +244 -0
  44. package/src/components/booking/CheckoutForm.tsx +364 -0
  45. package/src/components/{CheckoutModal.tsx → booking/CheckoutModal.tsx} +176 -19
  46. package/src/components/booking/DapFlowCollage.tsx +88 -0
  47. package/src/components/booking/DapTourDescription.tsx +35 -0
  48. package/src/components/booking/DependentAddOnBookingDialog.tsx +1350 -0
  49. package/src/components/booking/DependentAddOnPaymentForm.tsx +124 -0
  50. package/src/components/booking/InfoTooltip.tsx +108 -0
  51. package/src/components/booking/ItineraryBox.module.css +258 -0
  52. package/src/components/booking/ItineraryBox.tsx +550 -0
  53. package/src/components/{ItineraryBuilder.tsx → booking/ItineraryBuilder.tsx} +1 -2
  54. package/src/components/booking/ItineraryPlaceholder.module.css +45 -0
  55. package/src/components/booking/ItineraryPlaceholder.tsx +26 -0
  56. package/src/components/{MealDrinkAddOnSelector.tsx → booking/MealDrinkAddOnSelector.tsx} +21 -13
  57. package/src/components/booking/PickupLocationSelector.module.css +124 -0
  58. package/src/components/{PickupLocationSelector.tsx → booking/PickupLocationSelector.tsx} +315 -290
  59. package/src/components/booking/PickupTimeSelector.module.css +134 -0
  60. package/src/components/booking/PickupTimeSelector.tsx +112 -0
  61. package/src/components/{PriceBreakdown.tsx → booking/PriceBreakdown.tsx} +3 -3
  62. package/src/components/{PriceSummary.tsx → booking/PriceSummary.tsx} +51 -28
  63. package/src/components/booking/PrivateShuttleBookingFlow.module.css +357 -0
  64. package/src/components/booking/PrivateShuttleBookingFlow.tsx +2662 -0
  65. package/src/components/booking/PromoCodeInput.module.css +166 -0
  66. package/src/components/booking/PromoCodeInput.tsx +99 -0
  67. package/src/components/booking/ReturnTimeSelector.module.css +173 -0
  68. package/src/components/booking/ReturnTimeSelector.tsx +145 -0
  69. package/src/components/{TermsAcceptance.tsx → booking/TermsAcceptance.tsx} +9 -8
  70. package/src/components/booking/TicketSelector.module.css +164 -0
  71. package/src/components/booking/TicketSelector.tsx +199 -0
  72. package/src/components/booking/TourDescription.module.css +304 -0
  73. package/src/components/booking/TourDescription.tsx +273 -0
  74. package/src/components/booking/booking-flow-ui.ts +15 -1
  75. package/src/components/booking/booking-flow.css +944 -0
  76. package/src/components/bottom-sheet.module.css +78 -0
  77. package/src/components/bottom-sheet.tsx +60 -0
  78. package/src/components/breadcrumb.module.css +40 -0
  79. package/src/components/breadcrumb.tsx +36 -0
  80. package/src/components/button.css +245 -0
  81. package/src/components/button.tsx +152 -0
  82. package/src/components/client-bottom-sheet.tsx +14 -0
  83. package/src/components/colorable-svg.tsx +29 -0
  84. package/src/components/conditional-footer.tsx +27 -0
  85. package/src/components/contact-us.module.css +147 -0
  86. package/src/components/contact-us.tsx +49 -0
  87. package/src/components/email-signup.css +151 -0
  88. package/src/components/email-signup.tsx +63 -0
  89. package/src/components/faq-wrapper.module.css +47 -0
  90. package/src/components/faq-wrapper.tsx +15 -0
  91. package/src/components/footer.css +187 -0
  92. package/src/components/footer.tsx +143 -0
  93. package/src/components/global-simple-modal.tsx +33 -0
  94. package/src/components/google-review-summary.module.css +77 -0
  95. package/src/components/google-review-summary.tsx +50 -0
  96. package/src/components/hero-image.css +13 -0
  97. package/src/components/hero-image.tsx +44 -0
  98. package/src/components/image.css +29 -0
  99. package/src/components/image.tsx +113 -0
  100. package/src/components/language-aware-link.tsx +72 -0
  101. package/src/components/language-switcher.module.css +124 -0
  102. package/src/components/language-switcher.tsx +75 -0
  103. package/src/components/map-section.css +59 -0
  104. package/src/components/map-section.tsx +63 -0
  105. package/src/components/navbar.module.css +152 -0
  106. package/src/components/navbar.tsx +125 -0
  107. package/src/components/parallax-provider.tsx +11 -0
  108. package/src/components/partner/PartnerBookingPage.module.css +130 -0
  109. package/src/components/partner/PartnerBookingPage.tsx +390 -0
  110. package/src/components/partner/PartnerBookingPageWithBrowserMetadata.tsx +19 -35
  111. package/src/components/product-tag.module.css +30 -0
  112. package/src/components/product-tag.tsx +34 -0
  113. package/src/components/product-theme-pages/best-option.module.css +70 -0
  114. package/src/components/product-theme-pages/best-option.tsx +35 -0
  115. package/src/components/product-theme-pages/extended-tour-options.module.css +22 -0
  116. package/src/components/product-theme-pages/extended-tour-options.tsx +11 -0
  117. package/src/components/product-theme-pages/image-modal.tsx +248 -0
  118. package/src/components/product-theme-pages/photo-gallery.module.css +200 -0
  119. package/src/components/product-theme-pages/photo-gallery.tsx +90 -0
  120. package/src/components/product-theme-pages/product-theme-page-layout.module.css +13 -0
  121. package/src/components/product-theme-pages/product-theme-page-layout.tsx +67 -0
  122. package/src/components/product-theme-pages/top-of-fold.module.css +179 -0
  123. package/src/components/product-theme-pages/top-of-fold.tsx +80 -0
  124. package/src/components/product-tile/image-only-product-tile-desktop.module.css +106 -0
  125. package/src/components/product-tile/image-only-product-tile-desktop.tsx +56 -0
  126. package/src/components/product-tile/image-only-product-tile-mobile.module.css +122 -0
  127. package/src/components/product-tile/image-only-product-tile-mobile.tsx +89 -0
  128. package/src/components/product-tile/image-only-product-tile.tsx +44 -0
  129. package/src/components/product-tile/product-tile-card.module.css +84 -0
  130. package/src/components/product-tile/product-tile-card.tsx +61 -0
  131. package/src/components/review-highlights-section.css +85 -0
  132. package/src/components/review-highlights-section.tsx +127 -0
  133. package/src/components/season-closure-overlay.module.css +99 -0
  134. package/src/components/season-closure-overlay.tsx +98 -0
  135. package/src/components/simple-modal.tsx +69 -0
  136. package/src/components/simple-top-of-fold.module.css +76 -0
  137. package/src/components/simple-top-of-fold.tsx +34 -0
  138. package/src/components/spacer.css +41 -0
  139. package/src/components/spacer.tsx +23 -0
  140. package/src/components/star-rating.module.css +74 -0
  141. package/src/components/star-rating.tsx +48 -0
  142. package/src/components/terms/TermsContent.tsx +178 -0
  143. package/src/components/title-subtitle.module.css +10 -0
  144. package/src/components/title-subtitle.tsx +30 -0
  145. package/src/components/translatable-reviews.tsx +75 -0
  146. package/src/components/value-pill.module.css +59 -0
  147. package/src/components/value-pill.tsx +46 -0
  148. package/src/components/value-props.css +185 -0
  149. package/src/components/value-props.tsx +88 -0
  150. package/src/constants/booking-guide-quiz.ts +64 -0
  151. package/src/constants/contact-info.ts +2 -0
  152. package/src/constants/faq.ts +44 -0
  153. package/src/constants/images.ts +556 -0
  154. package/src/constants/json-ld/faq-json-ld.tsx +170 -0
  155. package/src/constants/json-ld/homepage-json-ld.tsx +138 -0
  156. package/src/constants/json-ld/job-posting-json-ld.tsx +92 -0
  157. package/src/constants/json-ld/organization-json-ld.tsx +62 -0
  158. package/src/constants/json-ld/page-json-ld.tsx +6 -0
  159. package/src/constants/json-ld/product-json-ld.tsx +154 -0
  160. package/src/constants/json-ld/review-json-ld.tsx +377 -0
  161. package/src/constants/navigation-links/footer-links.ts +48 -0
  162. package/src/constants/navigation-links/nav-bar-links.ts +41 -0
  163. package/src/constants/navigation-links/navigation-link.ts +6 -0
  164. package/src/constants/pill-values.ts +210 -0
  165. package/src/constants/products.ts +155 -0
  166. package/src/constants/quiz-recommendations.ts +506 -0
  167. package/src/constants/reviews.ts +75 -0
  168. package/src/constants/staff.ts +197 -0
  169. package/src/constants/value-props.ts +58 -0
  170. package/src/data/dap-descriptions/session-couples-families-friends.en.json +61 -0
  171. package/src/data/dap-descriptions/session-elopements.en.json +60 -0
  172. package/src/data/dap-descriptions/session-proposals.en.json +60 -0
  173. package/src/data/product-descriptions/afternoon-delight.en.json +35 -0
  174. package/src/data/product-descriptions/emerald-lake-escape.en.json +68 -0
  175. package/src/data/product-descriptions/lake-louise-adventure.en.json +74 -0
  176. package/src/data/product-descriptions/moraine-lake-adventure.en.json +78 -0
  177. package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +65 -0
  178. package/src/data/product-descriptions/moraine-lake-sunrise.en.json +64 -0
  179. package/src/data/product-descriptions/private-tour.en.json +80 -0
  180. package/src/data/product-descriptions/two-lakes-combo.en.json +65 -0
  181. package/src/data/products-config.json +101 -0
  182. package/src/hooks/use-bottom-sheet.tsx +15 -0
  183. package/src/hooks/use-simple-modal.tsx +27 -0
  184. package/src/hooks/useBookingSourceMetadataFromLocation.ts +21 -0
  185. package/src/hooks/useEmailSubscription.tsx +103 -0
  186. package/src/hooks/useEmbeddedInIframe.ts +16 -0
  187. package/src/hooks/useIsBookingLaunchLive.ts +49 -0
  188. package/src/hooks/useQuiz.tsx +210 -0
  189. package/src/index.ts +27 -2
  190. package/src/lib/analytics.ts +197 -0
  191. package/src/lib/booking/booking-source.ts +20 -2
  192. package/src/lib/{checkout-breakdown.ts → booking/checkout-breakdown.ts} +1 -1
  193. package/src/lib/booking/correlation-id.ts +46 -0
  194. package/src/lib/{i18n → booking/i18n}/messages/en.json +48 -4
  195. package/src/lib/{i18n → booking/i18n}/messages/fr.json +48 -4
  196. package/src/lib/booking/itinerary-display.ts +36 -0
  197. package/src/lib/{itinerary-labels.ts → booking/itinerary-labels.ts} +1 -1
  198. package/src/lib/{location-calculations.ts → booking/location-calculations.ts} +4 -4
  199. package/src/lib/{location-utils.ts → booking/location-utils.ts} +26 -0
  200. package/src/lib/{map-utils.ts → booking/map-utils.ts} +3 -3
  201. package/src/lib/booking/normalize-booking-product-id.ts +7 -0
  202. package/src/lib/{pickup-location-types.ts → booking/pickup-location-types.ts} +2 -2
  203. package/src/lib/{pricing.ts → booking/pricing.ts} +2 -2
  204. package/src/lib/booking/product-option-id.ts +35 -0
  205. package/src/lib/booking/source-metadata.ts +72 -7
  206. package/src/lib/booking/sunday-week.ts +14 -0
  207. package/src/lib/booking/trace-context.ts +62 -0
  208. package/src/lib/booking-api.ts +1793 -0
  209. package/src/lib/{constants.ts → booking-constants.ts} +11 -5
  210. package/src/lib/booking-types.ts +36 -0
  211. package/src/lib/currency.ts +38 -45
  212. package/src/lib/dap-descriptions.ts +50 -0
  213. package/src/lib/dap-itinerary-preview.ts +315 -0
  214. package/src/lib/dependent-add-on-api.ts +434 -0
  215. package/src/lib/env.ts +89 -5
  216. package/src/lib/firebase.ts +20 -0
  217. package/src/lib/job-application-api.ts +83 -0
  218. package/src/lib/manage-booking-embed-print.ts +16 -0
  219. package/src/lib/manage-booking-post-checkout.ts +68 -0
  220. package/src/lib/photo-dap-config.ts +228 -0
  221. package/src/lib/pickup/map-utils.ts +56 -0
  222. package/src/lib/pickup/marker-icons.ts +19 -0
  223. package/src/lib/product-descriptions.ts +66 -0
  224. package/src/lib/products-config.ts +73 -0
  225. package/src/providers/booking-dialog-provider.tsx +107 -38
  226. package/src/providers/bottom-sheet-provider.tsx +40 -0
  227. package/src/providers/dependent-add-on-dialog-provider.tsx +105 -0
  228. package/src/radius.css +5 -0
  229. package/src/spacing.css +7 -0
  230. package/src/strings/en.json +1774 -0
  231. package/src/strings/es.json +1573 -0
  232. package/src/strings/fr.json +1573 -0
  233. package/src/strings/index.js +23 -0
  234. package/src/text-style.css +97 -0
  235. package/src/types/fareharbor.d.ts +12 -0
  236. package/src/types/quiz.ts +59 -0
  237. package/src/utils/currency-converter.ts +101 -0
  238. package/src/components/BookingFlow.tsx +0 -2952
  239. package/src/components/LanguageSwitcher.tsx +0 -30
  240. package/src/components/PrivateShuttleBookingFlow.tsx +0 -2290
  241. package/src/components/ProductList.tsx +0 -78
  242. package/src/components/WhatsAppPhoneInput.tsx +0 -224
  243. package/src/components/index.ts +0 -31
  244. package/src/lib/api.ts +0 -801
  245. package/src/lib/booking-api-auth.ts +0 -9
  246. package/src/lib/checkout-breakdown.test.ts +0 -70
  247. package/src/types/google-maps.d.ts +0 -2
  248. /package/src/components/{CurrencySwitcher.tsx → booking/CurrencySwitcher.tsx} +0 -0
  249. /package/src/components/{ErrorBoundary.tsx → booking/ErrorBoundary.tsx} +0 -0
  250. /package/src/lib/{i18n → booking/i18n}/config.ts +0 -0
  251. /package/src/lib/{i18n → booking/i18n}/index.tsx +0 -0
  252. /package/src/lib/{marker-icons.ts → booking/marker-icons.ts} +0 -0
  253. /package/src/lib/{places-api.ts → booking/places-api.ts} +0 -0
  254. /package/src/lib/{theme.ts → booking/theme.ts} +0 -0
  255. /package/src/lib/{utils.ts → booking/utils.ts} +0 -0
@@ -0,0 +1,155 @@
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);
@@ -0,0 +1,506 @@
1
+ import { RecommendationRule, AnswerMatch } from "@/types/quiz";
2
+ import { PRODUCTS } from "./products";
3
+
4
+ // Helper function to check if answers include all specified values
5
+ const includesAll = (answers: (string | undefined)[], values: string[]) =>
6
+ values.every(v => v && answers.filter((a): a is string => a !== undefined).includes(v));
7
+
8
+ // Helper function to check if answers include any of specified values
9
+ const includesAny = (answers: (string | undefined)[], values: string[]) =>
10
+ values.some(v => v && answers.filter((a): a is string => a !== undefined).includes(v));
11
+
12
+ // Helper function to check if answers exclude all specified values
13
+ const excludesAll = (answers: (string | undefined)[], values: string[]) =>
14
+ values.every(v => v && !answers.filter((a): a is string => a !== undefined).includes(v));
15
+
16
+ export const RECOMMENDATION_RULES: RecommendationRule[] = [
17
+ {
18
+ answers: {
19
+ what_lakes: {
20
+ values: ["moraine_lake"],
21
+ matchType: 'AND'
22
+ },
23
+ sunrise: "yes",
24
+ exclude: {
25
+ what_lakes: ["lake_louise"],
26
+ hiking: ["moderate_hikes", "challenging_hikes", "expert_hikes"],
27
+ what_activities: ["canoeing"]
28
+ }
29
+ },
30
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_SUNRISE.id],
31
+ recommendationDetails: {
32
+ [
33
+ PRODUCTS.MORAINE_LAKE_SUNRISE.id]: [
34
+ "Book a **regular ticket** for **3 hours at Moraine Lake**",
35
+ "Arrives at Moraine Lake before sunrise",
36
+ "Hot drinks and blankets provided",
37
+ "Enough time for an easy walk around the lake and photos at the lake's most famous viewpoints"
38
+ ]
39
+ }
40
+ },
41
+ {
42
+ answers: {
43
+ what_lakes: {
44
+ values: ["moraine_lake"],
45
+ matchType: 'AND'
46
+ },
47
+ sunrise: "yes",
48
+ exclude: {
49
+ what_lakes: ["lake_louise"],
50
+ what_activities: ["canoeing"]
51
+ },
52
+ hiking: {
53
+ values: ["moderate_hikes", "challenging_hikes", "expert_hikes"],
54
+ matchType: 'OR'
55
+ }
56
+ },
57
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_SUNRISE.id],
58
+ recommendationDetails: {
59
+ [
60
+ PRODUCTS.MORAINE_LAKE_SUNRISE.id]: [
61
+ "Book an **extended ticket** for **9.5 hours at Moraine Lake**",
62
+ "Arrives at Moraine Lake before sunrise",
63
+ "Hot drinks and blankets provided",
64
+ "Enough time to watch sunrise then go for a longer day hike"
65
+ ]
66
+ }
67
+ },
68
+ {
69
+ answers: {
70
+ what_lakes: {
71
+ values: ["moraine_lake"],
72
+ matchType: 'AND'
73
+ },
74
+ sunrise: "yes",
75
+ exclude: {
76
+ what_lakes: ["lake_louise"],
77
+ hiking: ["expert_hikes"],
78
+ },
79
+ what_activities: {
80
+ values: ["canoeing"],
81
+ matchType: 'OR'
82
+ }
83
+ },
84
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_SUNRISE.id],
85
+ recommendationDetails: {
86
+ [
87
+ PRODUCTS.MORAINE_LAKE_SUNRISE.id]: [
88
+ "Book an **extended ticket** for **9.5 hours at Moraine Lake**",
89
+ "Arrives at Moraine Lake before sunrise",
90
+ "Hot drinks and blankets provided",
91
+ "Enough time to watch sunrise, canoe around the lake, and even hike a short trail"
92
+ ]
93
+ }
94
+ },
95
+ {
96
+ answers: {
97
+ what_lakes: {
98
+ values: ["moraine_lake", "lake_louise"],
99
+ matchType: 'AND'
100
+ },
101
+ sunrise: "yes",
102
+ exclude: {
103
+ hiking: ["moderate_hikes", "challenging_hikes", "expert_hikes"],
104
+ what_activities: ["canoeing"]
105
+ }
106
+ },
107
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR.id],
108
+ recommendationDetails: {
109
+ [
110
+ PRODUCTS.MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR.id]: [
111
+ "Book a **regular ticket** for **2 hours at Moraine Lake + 1 hour at Lake Louise**",
112
+ "Arrives at Moraine Lake to watch sunrise before departing to Lake Louise",
113
+ "Hot drinks and blankets provided",
114
+ "See the most 2 famous lakes of Banff National Park with the rising sun"
115
+ ]
116
+ }
117
+ },
118
+ {
119
+ answers: {
120
+ what_lakes: {
121
+ values: ["moraine_lake", "lake_louise"],
122
+ matchType: 'AND'
123
+ },
124
+ sunrise: "yes",
125
+ hiking: {
126
+ values: ["moderate_hikes", "challenging_hikes", "expert_hikes"],
127
+ matchType: 'OR'
128
+ },
129
+ exclude: {
130
+ what_activities: ["canoeing"]
131
+ }
132
+ },
133
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR.id],
134
+ recommendationDetails: {
135
+ [
136
+ PRODUCTS.MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR.id]: [
137
+ "Book an **extended ticket** for **2 hours at Moraine Lake + 7.5 hours at Lake Louise**",
138
+ "Arrives at Moraine Lake to watch sunrise before departing to Lake Louise",
139
+ "Hot drinks and blankets provided",
140
+ "See the most 2 famous lakes of Banff National Park with the rising sun, then go for a longer day hike at Lake Louise"
141
+ ]
142
+ }
143
+ },
144
+ {
145
+ answers: {
146
+ what_lakes: {
147
+ values: ["moraine_lake", "lake_louise"],
148
+ matchType: 'AND'
149
+ },
150
+ sunrise: "yes",
151
+ hiking: {
152
+ values: ["moderate_hikes", "challenging_hikes"],
153
+ matchType: 'OR'
154
+ },
155
+ what_activities: {
156
+ values: ["canoeing"],
157
+ matchType: 'OR'
158
+ }
159
+ },
160
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR.id],
161
+ recommendationDetails: {
162
+ [
163
+ PRODUCTS.MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR.id]: [
164
+ "Book an **extended ticket** for **2 hours at Moraine Lake + 7.5 hours at Lake Louise**",
165
+ "Arrives at Moraine Lake to watch sunrise before departing to Lake Louise",
166
+ "Hot drinks and blankets provided",
167
+ "See the most 2 famous lakes of Banff National Park with the rising sun, then go pair a canoeing trip with a a hike at Lake Louise"
168
+ ]
169
+ }
170
+ },
171
+ {
172
+ answers: {
173
+ what_lakes: {
174
+ values: ["moraine_lake", "lake_louise"],
175
+ matchType: 'AND'
176
+ },
177
+ sunrise: "yes",
178
+ what_activities: {
179
+ values: ["canoeing"],
180
+ matchType: 'OR'
181
+ },
182
+ },
183
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR.id],
184
+ recommendationDetails: {
185
+ [
186
+ PRODUCTS.MORAINE_LAKE_SUNRISE_LAKE_LOUISE_GOLDEN_HOUR.id]: [
187
+ "Book an **extended ticket** for **2 hours at Moraine Lake + 7.5 hours at Lake Louise**",
188
+ "Arrives at Moraine Lake to watch sunrise before departing to Lake Louise",
189
+ "Hot drinks and blankets provided",
190
+ "See the most 2 famous lakes of Banff National Park with the rising sun, then go for a canoe around Lake Louise"
191
+ ]
192
+ }
193
+ },
194
+ {
195
+ answers: {
196
+ what_lakes: {
197
+ values: ["moraine_lake", "lake_louise"],
198
+ matchType: 'AND'
199
+ },
200
+ sunrise: "no",
201
+ exclude: {
202
+ hiking: ["moderate_hikes", "challenging_hikes", "expert_hikes"]
203
+ }
204
+ },
205
+ recommendationIds: [PRODUCTS.TWO_LAKES_COMBO.id],
206
+ recommendationDetails: {
207
+ [
208
+ PRODUCTS.TWO_LAKES_COMBO.id]: [
209
+ "Departs daily at 9am from Canmore with pickups in Banff and Lake Louise",
210
+ "Book a **regular ticket** for **2 hours at Moraine Lake + 2 hours at Lake Louise**",
211
+ "See the most 2 famous lakes of Banff National Park in one day",
212
+ "Enough time to canoe at either or both lakes"
213
+ ]
214
+ }
215
+ },
216
+ {
217
+ answers: {
218
+ what_lakes: {
219
+ values: ["moraine_lake", "lake_louise"],
220
+ matchType: 'AND'
221
+ },
222
+ sunrise: "no",
223
+ hiking: {
224
+ values: ["moderate_hikes", "challenging_hikes", "expert_hikes"],
225
+ matchType: 'OR'
226
+ },
227
+ },
228
+ recommendationIds: [PRODUCTS.TWO_LAKES_COMBO.id],
229
+ recommendationDetails: {
230
+ [
231
+ PRODUCTS.TWO_LAKES_COMBO.id]: [
232
+ "Departs daily at 9am from Canmore with pickups in Banff and Lake Louise",
233
+ "Book an **extended ticket** for **2 hours at Moraine Lake + 5.5 hours at Lake Louise**",
234
+ "See the most 2 famous lakes of Banff National Park in one day, then go for a longer day hike at Lake Louise"
235
+ ]
236
+ }
237
+ },
238
+ {
239
+ answers: {
240
+ what_lakes: {
241
+ values: ["moraine_lake"],
242
+ matchType: 'AND'
243
+ },
244
+ sunrise: "no",
245
+ exclude: {
246
+ what_lakes: ["lake_louise"],
247
+ hiking: ["challenging_hikes", "expert_hikes"],
248
+ }
249
+ },
250
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_ADVENTURE.id],
251
+ recommendationDetails: {
252
+ [
253
+ PRODUCTS.MORAINE_LAKE_ADVENTURE.id]: [
254
+ "Departs daily at 9am from Canmore with pickups in Banff and Lake Louise",
255
+ "Book a **regular ticket** for **4 hours at Moraine Lake**",
256
+ "Enough time for a small to moderate hike in Moraine Lake and hit all the famous photo spots or take a canoe out on the lake."
257
+ ]
258
+ }
259
+ },
260
+ {
261
+ answers: {
262
+ what_lakes: {
263
+ values: ["moraine_lake"],
264
+ matchType: 'AND'
265
+ },
266
+ sunrise: "no",
267
+ exclude: {
268
+ what_lakes: ["lake_louise"],
269
+ },
270
+ hiking: {
271
+ values: ["challenging_hikes", "expert_hikes"],
272
+ matchType: 'OR'
273
+ }
274
+ },
275
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_ADVENTURE.id],
276
+ recommendationDetails: {
277
+ [
278
+ PRODUCTS.MORAINE_LAKE_ADVENTURE.id]: [
279
+ "Departs daily at 9am from Canmore with pickups in Banff and Lake Louise",
280
+ "Book an **extended ticket** for **7.5 hours at Moraine Lake**",
281
+ "Enough time for a longer hike in Moraine Lake and hit all the famous photo spots or take a canoe out on the lake."
282
+ ]
283
+ }
284
+ },
285
+ {
286
+ answers: {
287
+ what_lakes: {
288
+ values: ["lake_louise"],
289
+ matchType: 'AND'
290
+ },
291
+ sunrise: "no",
292
+ exclude: {
293
+ what_lakes: ["moraine_lake"],
294
+ hiking: ["moderate_hikes", "challenging_hikes", "expert_hikes"]
295
+ }
296
+ },
297
+ recommendationIds: [PRODUCTS.LAKE_LOUISE_ADVENTURE.id],
298
+ recommendationDetails: {
299
+ [
300
+ PRODUCTS.LAKE_LOUISE_ADVENTURE.id]: [
301
+ "Departs daily at 9am from Canmore with pickups in Banff and Lake Louise",
302
+ "Book a **regular ticket** for **5 hours at Lake Louise**",
303
+ "Enough time for a small to moderate hike in Lake Louise and to hit all the famous photo spots or take a canoe out on the lake."
304
+ ]
305
+ }
306
+ },
307
+ {
308
+ answers: {
309
+ what_lakes: {
310
+ values: ["lake_louise"],
311
+ matchType: 'AND'
312
+ },
313
+ sunrise: "no",
314
+ exclude: {
315
+ what_lakes: ["moraine_lake"],
316
+ },
317
+ hiking: {
318
+ values: ["challenging_hikes", "expert_hikes"],
319
+ matchType: 'OR'
320
+ }
321
+ },
322
+ recommendationIds: [PRODUCTS.LAKE_LOUISE_ADVENTURE.id],
323
+ recommendationDetails: {
324
+ [
325
+ PRODUCTS.LAKE_LOUISE_ADVENTURE.id]: [
326
+ "Departs daily at 9am from Canmore with pickups in Banff and Lake Louise",
327
+ "Book an **extended ticket** for **8.5 hours at Lake Louise**",
328
+ "Enough time for a longer hike in Lake Louise and to hit all the famous photo spots or take a canoe out on the lake."
329
+ ]
330
+ }
331
+ },
332
+ {
333
+ answers: {
334
+ what_lakes: {
335
+ values: ["emerald_lake"],
336
+ matchType: 'AND'
337
+ },
338
+ exclude: {
339
+ what_lakes: ["lake_louise", "moraine_lake"],
340
+ }
341
+ },
342
+ recommendationIds: [PRODUCTS.EMERALD_LAKE_ESCAPE.id],
343
+ recommendationDetails: {
344
+ [
345
+ PRODUCTS.EMERALD_LAKE_ESCAPE.id]: [
346
+ "A 10 hour full day tour that departs daily at 8am from Canmore and Banff",
347
+ "Optionally add on a lunch package at checkout, served at the Emerald Lake Lodge.",
348
+ "Rent a canoe at Emerald Lake",
349
+ "See multiple bucket list destinations in Banff National Park in one day - this tour includes stops at Lake Louise, Takakkaw Falls, Morant's Curve, Johnston Canyon, and Emerald Lake."
350
+ ]
351
+ }
352
+ },
353
+ {
354
+ answers: {
355
+ what_lakes: {
356
+ values: ["other_custom"],
357
+ matchType: 'AND'
358
+ }
359
+ },
360
+ recommendationIds: [PRODUCTS.PRIVATE_TOUR.id],
361
+ recommendationDetails: {
362
+ [PRODUCTS.PRIVATE_TOUR.id]: ["You want a custom itinerary, we'll create one for you! We can visit any destinations you are interested in in Banff National Park and Yoho National Park."]
363
+ }
364
+ },
365
+ {
366
+ answers: {
367
+ what_lakes: {
368
+ values: ["moraine_lake", "lake_louise", "emerald_lake"],
369
+ matchType: 'AND'
370
+ },
371
+ sunrise: "no",
372
+ },
373
+ recommendationIds: [],
374
+ isMultiDayGroup: true,
375
+ dayGroups: [
376
+ {
377
+ day: 1,
378
+ dayTitle: "Day 1: Moraine Lake",
379
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_ADVENTURE.id]
380
+ },
381
+ {
382
+ day: 2,
383
+ dayTitle: "Day 2: Emerald Lake & Lake Louise",
384
+ recommendationIds: [PRODUCTS.EMERALD_LAKE_ESCAPE.id]
385
+ }
386
+ ],
387
+ recommendationDetails: {
388
+ [PRODUCTS.MORAINE_LAKE_ADVENTURE.id]: [
389
+ "Departs daily at 9am from Canmore with pickups in Banff and Lake Louise",
390
+ "Book a **regular ticket** for **4 hours at Moraine Lake**",
391
+ "Enough time for a small to moderate hike in Moraine Lake and hit all the famous photo spots or take a canoe out on the lake."
392
+ ],
393
+ [PRODUCTS.EMERALD_LAKE_ESCAPE.id]: [
394
+ "A 10 hour full day tour that departs daily at 8am from Canmore and Banff",
395
+ "Optionally add on a lunch package at checkout, served at the Emerald Lake Lodge.",
396
+ "Rent a canoe at Emerald Lake",
397
+ "See multiple bucket list destinations in Banff National Park in one day - this tour includes stops at Lake Louise, Takakkaw Falls, Morant's Curve, Johnston Canyon, and Emerald Lake."
398
+ ]
399
+ }
400
+ },
401
+ {
402
+ answers: {
403
+ what_lakes: {
404
+ values: ["moraine_lake", "lake_louise", "emerald_lake"],
405
+ matchType: 'AND'
406
+ },
407
+ sunrise: "yes",
408
+ },
409
+ recommendationIds: [],
410
+ isMultiDayGroup: true,
411
+ dayGroups: [
412
+ {
413
+ day: 1,
414
+ dayTitle: "Day 1: Moraine Lake",
415
+ recommendationIds: [PRODUCTS.MORAINE_LAKE_SUNRISE.id]
416
+ },
417
+ {
418
+ day: 2,
419
+ dayTitle: "Day 2: Emerald Lake & Lake Louise",
420
+ recommendationIds: [PRODUCTS.EMERALD_LAKE_ESCAPE.id]
421
+ }
422
+ ],
423
+ recommendationDetails: {
424
+ [PRODUCTS.MORAINE_LAKE_SUNRISE.id]: [
425
+ "Book a **regular ticket** for **3 hours at Moraine Lake**",
426
+ "Arrives at Moraine Lake before sunrise",
427
+ "Hot drinks and blankets provided",
428
+ "Enough time for an easy walk around the lake and photos at the lake's most famous viewpoints"
429
+ ],
430
+ [PRODUCTS.EMERALD_LAKE_ESCAPE.id]: [
431
+ "A 10 hour full day tour that departs daily at 8am from Canmore and Banff",
432
+ "Optionally add on a lunch package at checkout, served at the Emerald Lake Lodge.",
433
+ "Rent a canoe at Emerald Lake",
434
+ "See multiple bucket list destinations in Banff National Park in one day - this tour includes stops at Lake Louise, Takakkaw Falls, Morant's Curve, Johnston Canyon, and Emerald Lake."
435
+ ]
436
+ }
437
+ },
438
+ {
439
+ answers: {
440
+ what_lakes: {
441
+ values: ["moraine_lake", "lake_louise", "other_custom"],
442
+ matchType: 'AND'
443
+ }
444
+ },
445
+ isAlternativeGroup: true,
446
+ alternativeGroupTitle: "Choose Your Adventure",
447
+ recommendationIds: [
448
+ PRODUCTS.TWO_LAKES_COMBO.id,
449
+ PRODUCTS.PRIVATE_TOUR.id
450
+ ],
451
+ recommendationDetails: {
452
+ [PRODUCTS.TWO_LAKES_COMBO.id]: [
453
+ "Departs daily at 9am from Canmore with pickups in Banff and Lake Louise",
454
+ "Book a **regular ticket** for **2 hours at Moraine Lake + 2 hours at Lake Louise**",
455
+ "See the most 2 famous lakes of Banff National Park in one day"
456
+ ],
457
+ [PRODUCTS.PRIVATE_TOUR.id]: [
458
+ "You want a custom itinerary, we'll create one for you! We can visit any destinations you are interested in in Banff National Park and Yoho National Park."
459
+ ]
460
+ }
461
+ },
462
+ ];
463
+
464
+ export function getQuizRecommendations(answers: Record<string, string | string[]>) {
465
+ const matchingRules = RECOMMENDATION_RULES.filter(rule => {
466
+ // Helper function to check matches based on match type
467
+ const checkMatches = (ruleMatch: AnswerMatch | undefined, userAnswers: string | string[] | undefined): boolean => {
468
+ if (!ruleMatch) return true;
469
+ const userAnswerArray = Array.isArray(userAnswers) ? userAnswers : [userAnswers];
470
+ return ruleMatch.matchType === 'AND'
471
+ ? includesAll(userAnswerArray, ruleMatch.values)
472
+ : includesAny(userAnswerArray, ruleMatch.values);
473
+ };
474
+
475
+ // For lakes
476
+ const lakesMatch = checkMatches(rule.answers.what_lakes, answers.what_lakes);
477
+
478
+ // For sunrise, if rule requires it, check user wants it (single value)
479
+ const sunriseMatches = !rule.answers.sunrise || answers.sunrise === rule.answers.sunrise;
480
+
481
+ // For hiking
482
+ const hikingMatches = checkMatches(rule.answers.hiking, answers.hiking);
483
+
484
+ // For activities
485
+ const activitiesMatch = checkMatches(rule.answers.what_activities, answers.what_activities);
486
+
487
+ // Check exclusions
488
+ let exclusionsMatch = true;
489
+ if (rule.answers.exclude) {
490
+ Object.entries(rule.answers.exclude).forEach(([questionId, excludedAnswers]) => {
491
+ const userAnswers = Array.isArray(answers[questionId]) ? answers[questionId] : [answers[questionId]];
492
+ exclusionsMatch = exclusionsMatch && excludesAll(userAnswers as (string | undefined)[], excludedAnswers as string[]);
493
+ });
494
+ }
495
+
496
+ return lakesMatch && sunriseMatches && hikingMatches && activitiesMatch && exclusionsMatch;
497
+ });
498
+
499
+ // If there's a multi-day group or alternative group in the matching rules, prioritize those
500
+ const specialRule = matchingRules.find(rule => rule.isMultiDayGroup || rule.isAlternativeGroup);
501
+ if (specialRule) {
502
+ return [specialRule];
503
+ }
504
+
505
+ return matchingRules;
506
+ }