@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,377 @@
1
+ // Review and Rating JSON-LD schemas for Via Via Moraine Lake Shuttle
2
+
3
+ // Aggregate rating for the business
4
+ export const aggregateRatingJsonLd = {
5
+ "@context": "https://schema.org",
6
+ "@type": "AggregateRating",
7
+ "@id": "https://viaviamorainelake.com/#aggregateRating",
8
+ "itemReviewed": {
9
+ "@type": "Organization",
10
+ "@id": "https://viaviamorainelake.com/#business",
11
+ "name": "Via Via Moraine Lake Shuttle"
12
+ },
13
+ "ratingValue": "5.0",
14
+ "bestRating": "5",
15
+ "worstRating": "2",
16
+ "ratingCount": "841",
17
+ "reviewCount": "841",
18
+ "url": "https://viaviamorainelake.com",
19
+ "author": {
20
+ "@type": "Organization",
21
+ "@id": "https://viaviamorainelake.com/#business",
22
+ "name": "Via Via Moraine Lake Shuttle"
23
+ }
24
+ };
25
+
26
+ // Individual review schemas (exactly matching reviews.ts)
27
+ export const individualReviewJsonLd = [
28
+ {
29
+ "@context": "https://schema.org",
30
+ "@type": "Review",
31
+ "@id": "https://viaviamorainelake.com/#review-1",
32
+ "itemReviewed": {
33
+ "@type": "Organization",
34
+ "@id": "https://viaviamorainelake.com/#business",
35
+ "name": "Via Via Moraine Lake Shuttle"
36
+ },
37
+ "reviewRating": {
38
+ "@type": "Rating",
39
+ "ratingValue": "5",
40
+ "bestRating": "5"
41
+ },
42
+ "author": {
43
+ "@type": "Person",
44
+ "name": "Meyrem"
45
+ },
46
+ "reviewBody": "Via Via Moraine is a great shuttle service to both Moraine and Lake Louise and beyond. Comfortable, efficient, friendly and lots of tips on where to go and what to see.We had plenty of time to hike around Moraine lake on an amazing sunny day. Definitely the best stress free way to visit special places in and around the Banff area. Thank you Nathan, Jess and our driver Rebecca, we had a great time! Warm regards, Meyrem Almaci & family",
47
+ "datePublished": "2025-07-31T00:58:27+00:00",
48
+ "publisher": {
49
+ "@type": "Organization",
50
+ "@id": "https://viaviamorainelake.com/#business",
51
+ "name": "Via Via Moraine Lake Shuttle"
52
+ }
53
+ },
54
+ {
55
+ "@context": "https://schema.org",
56
+ "@type": "Review",
57
+ "@id": "https://viaviamorainelake.com/#review-2",
58
+ "itemReviewed": {
59
+ "@type": "Organization",
60
+ "@id": "https://viaviamorainelake.com/#business",
61
+ "name": "Via Via Moraine Lake Shuttle"
62
+ },
63
+ "reviewRating": {
64
+ "@type": "Rating",
65
+ "ratingValue": "5",
66
+ "bestRating": "5"
67
+ },
68
+ "author": {
69
+ "@type": "Person",
70
+ "name": "Ryan G."
71
+ },
72
+ "reviewBody": "We recently did the Moraine Lake and Lake Louise shuttle from Canmore, and it was great. Leo was our driver, and he was awesome. I was sitting in the first row so I got to chat with him a bit, he gave me some cool facts about the mountains and areas along the drives, and gave us some tips of things we could do on other days of our vacation to Banff National Park. When we arrived at each destination Leo would tell the full group from the van about the various things to do/see at each stop so we didn't feel lost when arriving. Leo was also mindful of bumps and things along the mountain roads, and I noticed he was good at following all the rules during the entry process to the restricted areas (like where to stop, waiting for gates to fully open or close, etc). I noticed many other tour vansbuses didn't do that, and while that doesn't have any impact on the trip itself, it did provide some comfort in trusting Leo and his driving.",
73
+ "datePublished": "2025-07-29T17:33:27+00:00",
74
+ "publisher": {
75
+ "@type": "Organization",
76
+ "@id": "https://viaviamorainelake.com/#business",
77
+ "name": "Via Via Moraine Lake Shuttle"
78
+ }
79
+ },
80
+ {
81
+ "@context": "https://schema.org",
82
+ "@type": "Review",
83
+ "@id": "https://viaviamorainelake.com/#review-3",
84
+ "itemReviewed": {
85
+ "@type": "Organization",
86
+ "@id": "https://viaviamorainelake.com/#business",
87
+ "name": "Via Via Moraine Lake Shuttle"
88
+ },
89
+ "reviewRating": {
90
+ "@type": "Rating",
91
+ "ratingValue": "5",
92
+ "bestRating": "5"
93
+ },
94
+ "author": {
95
+ "@type": "Person",
96
+ "name": "Ariel K."
97
+ },
98
+ "reviewBody": "My sister and I were unable to book tickets through the parks Canada service during our visit. We were so thankful to be able to see both Moraine and Lake Louise with Via Via! They were excellent! Our driver Kai was informative and kind, plus drove well and was on time. We loved that this tour allowed 2 hours at each lake and you could also extend your time at Lake Louise if you wanted to hike farther. We would highly recommend using this local company!",
99
+ "datePublished": "2025-07-28T22:25:03+00:00",
100
+ "publisher": {
101
+ "@type": "Organization",
102
+ "@id": "https://viaviamorainelake.com/#business",
103
+ "name": "Via Via Moraine Lake Shuttle"
104
+ }
105
+ },
106
+ {
107
+ "@context": "https://schema.org",
108
+ "@type": "Review",
109
+ "@id": "https://viaviamorainelake.com/#review-4",
110
+ "itemReviewed": {
111
+ "@type": "Organization",
112
+ "@id": "https://viaviamorainelake.com/#business",
113
+ "name": "Via Via Moraine Lake Shuttle"
114
+ },
115
+ "reviewRating": {
116
+ "@type": "Rating",
117
+ "ratingValue": "5",
118
+ "bestRating": "5"
119
+ },
120
+ "author": {
121
+ "@type": "Person",
122
+ "name": "Lori S."
123
+ },
124
+ "reviewBody": "Pickup at our hotel in Lake Louise was on time for sunrise. Jess was a great guide and made sure we were taken care of. Hot chocolate and blankets were a nice addition, especially when it was 36 degrees F. She checked on us at each location and made sure we had the perfect pictures taken at Moraine Lake and Lake Louise. She answered all our questions and we were able to get a picture of a bear on the side of the road.",
125
+ "datePublished": "2025-07-18T01:48:39+00:00",
126
+ "publisher": {
127
+ "@type": "Organization",
128
+ "@id": "https://viaviamorainelake.com/#business",
129
+ "name": "Via Via Moraine Lake Shuttle"
130
+ }
131
+ },
132
+ {
133
+ "@context": "https://schema.org",
134
+ "@type": "Review",
135
+ "@id": "https://viaviamorainelake.com/#review-5",
136
+ "itemReviewed": {
137
+ "@type": "Organization",
138
+ "@id": "https://viaviamorainelake.com/#business",
139
+ "name": "Via Via Moraine Lake Shuttle"
140
+ },
141
+ "reviewRating": {
142
+ "@type": "Rating",
143
+ "ratingValue": "5",
144
+ "bestRating": "5"
145
+ },
146
+ "author": {
147
+ "@type": "Person",
148
+ "name": "Jack P."
149
+ },
150
+ "reviewBody": "What an incredible experience watching the sunrise at Moraine Lake and a stop off at Lake Louise! The Via Via team were all so helpful; from the smooth process of booking on, to our informative guide Peter. The early morning was worth it, and made a little easier thanks to a complimentary coffee! With there being so many options for a Moraine Lake shuttle, I couldn't recommend Via Via enough.",
151
+ "datePublished": "2025-07-17T19:40:17+00:00",
152
+ "publisher": {
153
+ "@type": "Organization",
154
+ "@id": "https://viaviamorainelake.com/#business",
155
+ "name": "Via Via Moraine Lake Shuttle"
156
+ }
157
+ },
158
+ {
159
+ "@context": "https://schema.org",
160
+ "@type": "Review",
161
+ "@id": "https://viaviamorainelake.com/#review-6",
162
+ "itemReviewed": {
163
+ "@type": "Organization",
164
+ "@id": "https://viaviamorainelake.com/#business",
165
+ "name": "Via Via Moraine Lake Shuttle"
166
+ },
167
+ "reviewRating": {
168
+ "@type": "Rating",
169
+ "ratingValue": "5",
170
+ "bestRating": "5"
171
+ },
172
+ "author": {
173
+ "@type": "Person",
174
+ "name": "Andrew M."
175
+ },
176
+ "reviewBody": "I had an amazing experience taking the Via Via shuttle to both Moraine Lake and Lake Louise! The service was smooth, punctual, and incredibly convenient. Special shoutout to our drivers, Kathy and Kai! They were both so friendly, professional, and informative throughout the trip. Kathy made the early morning ride feel warm and welcoming, and Kai was equally kind and made the return trip just as enjoyable. Highly recommend Via Via for anyone looking to visit the lakes stress-free!",
177
+ "datePublished": "2025-07-15T22:42:13+00:00",
178
+ "publisher": {
179
+ "@type": "Organization",
180
+ "@id": "https://viaviamorainelake.com/#business",
181
+ "name": "Via Via Moraine Lake Shuttle"
182
+ }
183
+ },
184
+ {
185
+ "@context": "https://schema.org",
186
+ "@type": "Review",
187
+ "@id": "https://viaviamorainelake.com/#review-7",
188
+ "itemReviewed": {
189
+ "@type": "Organization",
190
+ "@id": "https://viaviamorainelake.com/#business",
191
+ "name": "Via Via Moraine Lake Shuttle"
192
+ },
193
+ "reviewRating": {
194
+ "@type": "Rating",
195
+ "ratingValue": "5",
196
+ "bestRating": "5"
197
+ },
198
+ "author": {
199
+ "@type": "Person",
200
+ "name": "Sadie W."
201
+ },
202
+ "reviewBody": "Moraine lake sunrise shuttle was awesome! We had so much fun. It was raining but Carrie had us covered. Hot chocolate, coffee, blankets and umbrellas! She was so nice in telling us where to go and how to get around. Then we went to Lake Louise, and saw people jumping in the water and enjoying the views. We left at 3:30AM from Canmore and got back around 10:00AM.",
203
+ "datePublished": "2025-07-14T14:37:54+00:00",
204
+ "publisher": {
205
+ "@type": "Organization",
206
+ "@id": "https://viaviamorainelake.com/#business",
207
+ "name": "Via Via Moraine Lake Shuttle"
208
+ }
209
+ },
210
+ {
211
+ "@context": "https://schema.org",
212
+ "@type": "Review",
213
+ "@id": "https://viaviamorainelake.com/#review-8",
214
+ "itemReviewed": {
215
+ "@type": "Organization",
216
+ "@id": "https://viaviamorainelake.com/#business",
217
+ "name": "Via Via Moraine Lake Shuttle"
218
+ },
219
+ "reviewRating": {
220
+ "@type": "Rating",
221
+ "ratingValue": "5",
222
+ "bestRating": "5"
223
+ },
224
+ "author": {
225
+ "@type": "Person",
226
+ "name": "Aubry G."
227
+ },
228
+ "reviewBody": "Had such a great experience with Grace and Via Via. I highly recommend using them and their sunrise tour to Lake Moraine.",
229
+ "datePublished": "2024-09-15T17:18:03+00:00",
230
+ "publisher": {
231
+ "@type": "Organization",
232
+ "@id": "https://viaviamorainelake.com/#business",
233
+ "name": "Via Via Moraine Lake Shuttle"
234
+ }
235
+ },
236
+ {
237
+ "@context": "https://schema.org",
238
+ "@type": "Review",
239
+ "@id": "https://viaviamorainelake.com/#review-9",
240
+ "itemReviewed": {
241
+ "@type": "Organization",
242
+ "@id": "https://viaviamorainelake.com/#business",
243
+ "name": "Via Via Moraine Lake Shuttle"
244
+ },
245
+ "reviewRating": {
246
+ "@type": "Rating",
247
+ "ratingValue": "5",
248
+ "bestRating": "5"
249
+ },
250
+ "author": {
251
+ "@type": "Person",
252
+ "name": "Pam M."
253
+ },
254
+ "reviewBody": "Our trip to Lake Louise and Moraine Lake was wonderful and worry free. Nathan took care of everything and was very informative along the way. The van was very comfortable and we enjoyed the intimacy of a small group. We highly recommend Via Via Moraine Lake Shuttle!!!! Paul and Pam from Trabuco Canyon, CA USA",
255
+ "datePublished": "2024-09-14T00:11:57+00:00",
256
+ "publisher": {
257
+ "@type": "Organization",
258
+ "@id": "https://viaviamorainelake.com/#business",
259
+ "name": "Via Via Moraine Lake Shuttle"
260
+ }
261
+ },
262
+ {
263
+ "@context": "https://schema.org",
264
+ "@type": "Review",
265
+ "@id": "https://viaviamorainelake.com/#review-10",
266
+ "itemReviewed": {
267
+ "@type": "Organization",
268
+ "@id": "https://viaviamorainelake.com/#business",
269
+ "name": "Via Via Moraine Lake Shuttle"
270
+ },
271
+ "reviewRating": {
272
+ "@type": "Rating",
273
+ "ratingValue": "5",
274
+ "bestRating": "5"
275
+ },
276
+ "author": {
277
+ "@type": "Person",
278
+ "name": "Laura W."
279
+ },
280
+ "reviewBody": "We did the sunrise tour with extended stay at Lake Louise. Everything was super well organized. Sure, it costs more than the Parks Canada shuttle, but you don't have to worry about a thing – totally worth it. You get plenty of time at Moraine Lake to catch the sunrise, and the blankets and hot drinks are great for the cold. With the extended tour, we had more than enough time to do the Beehive hike. We'd book with Via Via again in a heartbeat – everything went smoothly and everyone was really friendly.",
281
+ "datePublished": "2025-08-11T03:54:26+00:00",
282
+ "publisher": {
283
+ "@type": "Organization",
284
+ "@id": "https://viaviamorainelake.com/#business",
285
+ "name": "Via Via Moraine Lake Shuttle"
286
+ }
287
+ },
288
+ {
289
+ "@context": "https://schema.org",
290
+ "@type": "Review",
291
+ "@id": "https://viaviamorainelake.com/#review-11",
292
+ "itemReviewed": {
293
+ "@type": "Organization",
294
+ "@id": "https://viaviamorainelake.com/#business",
295
+ "name": "Via Via Moraine Lake Shuttle"
296
+ },
297
+ "reviewRating": {
298
+ "@type": "Rating",
299
+ "ratingValue": "5",
300
+ "bestRating": "5"
301
+ },
302
+ "author": {
303
+ "@type": "Person",
304
+ "name": "Nichole V."
305
+ },
306
+ "reviewBody": "Via Via is a fantastic tour company - I went on their Emerald Lake Escape: Waterfalls & Canyon Wonders Tour yesterday and had the best time! The locations were stunning and the bus was very comfortable. Our leader Léo was kind, personable, professional and a great driver - I was very happy with the service he and his colleagues provided throughout the day.",
307
+ "datePublished": "2025-07-14T00:35:46+00:00",
308
+ "publisher": {
309
+ "@type": "Organization",
310
+ "@id": "https://viaviamorainelake.com/#business",
311
+ "name": "Via Via Moraine Lake Shuttle"
312
+ }
313
+ }
314
+ ];
315
+
316
+ // Combined review schema that includes both aggregate rating and individual reviews
317
+ export const combinedReviewJsonLd = {
318
+ "@context": "https://schema.org",
319
+ "@graph": [
320
+ aggregateRatingJsonLd,
321
+ ...individualReviewJsonLd
322
+ ]
323
+ };
324
+
325
+ // Utility function to generate aggregate rating for a specific service/product
326
+ export const generateServiceAggregateRating = (
327
+ serviceName: string,
328
+ serviceUrl: string,
329
+ ratingValue: string = "5.0",
330
+ reviewCount: string = "800"
331
+ ) => {
332
+ return {
333
+ "@context": "https://schema.org",
334
+ "@type": "AggregateRating",
335
+ "@id": `${serviceUrl}#aggregateRating`,
336
+ "itemReviewed": {
337
+ "@type": "Service",
338
+ "@id": `${serviceUrl}#service`,
339
+ "name": serviceName,
340
+ "provider": {
341
+ "@type": "Organization",
342
+ "@id": "https://viaviamorainelake.com/#business",
343
+ "name": "Via Via Moraine Lake Shuttle"
344
+ }
345
+ },
346
+ "ratingValue": ratingValue,
347
+ "bestRating": "5",
348
+ "worstRating": "1",
349
+ "ratingCount": reviewCount,
350
+ "reviewCount": reviewCount,
351
+ "url": serviceUrl,
352
+ "author": {
353
+ "@type": "Organization",
354
+ "@id": "https://viaviamorainelake.com/#business",
355
+ "name": "Via Via Moraine Lake Shuttle"
356
+ }
357
+ };
358
+ };
359
+
360
+ // Utility function to add reviews to any page
361
+ export const addReviewsToPage = (pageJsonLd: any, includeIndividualReviews: boolean = false) => {
362
+ const reviewSchemas = includeIndividualReviews ? combinedReviewJsonLd : aggregateRatingJsonLd;
363
+
364
+ if (pageJsonLd["@graph"]) {
365
+ // If page already has a graph structure, add reviews to it
366
+ return {
367
+ ...pageJsonLd,
368
+ "@graph": [...pageJsonLd["@graph"], reviewSchemas]
369
+ };
370
+ } else {
371
+ // If page doesn't have a graph, create one
372
+ return {
373
+ "@context": "https://schema.org",
374
+ "@graph": [pageJsonLd, reviewSchemas]
375
+ };
376
+ }
377
+ };
@@ -0,0 +1,48 @@
1
+ import { NavigationLink } from './navigation-link';
2
+
3
+ export const getFooterLinks = (strings: any): Record<string, NavigationLink> => ({
4
+ AboutUs: {
5
+ name: strings.navigationLinks.aboutUs,
6
+ path: '/about',
7
+ },
8
+ OurTeam: {
9
+ name: strings.navigationLinks.ourTeam,
10
+ path: '/our-team',
11
+ },
12
+ Careers: {
13
+ name: strings.navigationLinks.careers,
14
+ path: '/careers',
15
+ },
16
+ PickupLocations: {
17
+ name: strings.navigationLinks.pickupLocations,
18
+ path: '/pick-up-locations',
19
+ },
20
+ AllShuttlesAndTours: {
21
+ name: strings.navigationLinks.allShuttlesAndTours,
22
+ path: '/all-shuttles-and-tours',
23
+ },
24
+ FAQ: {
25
+ name: strings.navigationLinks.faq,
26
+ path: '/faq',
27
+ },
28
+ BanffTravelDiaries: {
29
+ name: strings.navigationLinks.banffTravelDiaries,
30
+ path: '/banff-travel-diaries',
31
+ },
32
+ BanffNationalParkRecommendations: {
33
+ name: strings.navigationLinks.banffNationalParkRecommendations,
34
+ path: '/banff-travel-diaries/banff-national-park-recommendations',
35
+ },
36
+ BookingQuiz: {
37
+ name: strings.navigationLinks.bookingQuiz,
38
+ path: '/banff-travel-diaries/moraine-lake-booking-guide',
39
+ },
40
+ LivePickups: {
41
+ name: strings.navigationLinks.livePickups,
42
+ path: '/live-pickups',
43
+ },
44
+ });
45
+
46
+ // Default export for backward compatibility (uses English strings)
47
+ import defaultStrings from '@/strings';
48
+ export const FOOTER_LINKS = getFooterLinks(defaultStrings);
@@ -0,0 +1,41 @@
1
+ import { NavigationLink } from './navigation-link';
2
+ import { getProducts } from '../products';
3
+
4
+ export const getNavbarLinks = (strings: any): Record<string, NavigationLink> => ({
5
+ Home: {
6
+ name: strings.navigationLinks.home,
7
+ path: '/',
8
+ },
9
+ Sunrise: {
10
+ name: strings.navigationLinks.sunriseTours,
11
+ path: getProducts(strings).MORAINE_LAKE_SUNRISE.path,
12
+ },
13
+ MoraineLake: {
14
+ name: strings.navigationLinks.moraineLake,
15
+ path: getProducts(strings).MORAINE_LAKE_ADVENTURE.path,
16
+ },
17
+ LakeLouise: {
18
+ name: strings.navigationLinks.lakeLouise,
19
+ path: getProducts(strings).LAKE_LOUISE_ADVENTURE.path,
20
+ },
21
+ EmeraldLake: {
22
+ name: strings.navigationLinks.emeraldLake,
23
+ path: getProducts(strings).EMERALD_LAKE_ESCAPE.path,
24
+ },
25
+ PrivateTour: {
26
+ name: strings.navigationLinks.privateTours,
27
+ path: getProducts(strings).PRIVATE_TOUR.path,
28
+ },
29
+ PhotoSessions: {
30
+ name: strings.navigationLinks.photoSessions,
31
+ path: '/photo-sessions',
32
+ },
33
+ FAQ: {
34
+ name: strings.navigationLinks.faq,
35
+ path: '/faq',
36
+ },
37
+ });
38
+
39
+ // Default export for backward compatibility (uses English strings)
40
+ import defaultStrings from '@/strings';
41
+ export const NAVBAR_LINKS = getNavbarLinks(defaultStrings);
@@ -0,0 +1,6 @@
1
+ export interface NavigationLink {
2
+ name: string;
3
+ path?: string;
4
+ external?: boolean;
5
+ url?: string; // For external links
6
+ }
@@ -0,0 +1,210 @@
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
+ }