@coras-io/embed 0.2.0 → 0.4.0

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 (237) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/README.md +8 -7
  3. package/dist/components/accordion/accordion-item/accordion-item.styles.js +1 -1
  4. package/dist/components/alert/alert.styles.js +14 -14
  5. package/dist/components/attraction-card/base/attraction-card.styles.js +7 -3
  6. package/dist/components/attraction-discovery/attraction-discovery.styles.js +1 -1
  7. package/dist/components/attraction-header/attraction-header.styles.js +1 -1
  8. package/dist/components/attraction-option/attraction-option.styles.js +4 -4
  9. package/dist/components/basket/basket.styles.js +27 -26
  10. package/dist/components/basket-empty/basket-empty.styles.js +1 -0
  11. package/dist/components/basket-item/basket-item.component.js +10 -4
  12. package/dist/components/basket-item/basket-item.styles.js +9 -3
  13. package/dist/components/basket-item-ticket/basket-item-ticket.styles.js +1 -3
  14. package/dist/components/basket-mobile/basket-mobile.styles.js +5 -5
  15. package/dist/components/better-carousel/better-carousel.styles.js +2 -0
  16. package/dist/components/bottom-drawer/bottom-drawer.styles.js +6 -4
  17. package/dist/components/button/button.component.d.ts +0 -1
  18. package/dist/components/button/button.component.js +1 -8
  19. package/dist/components/button/button.constants.d.ts +1 -1
  20. package/dist/components/button/button.constants.js +1 -1
  21. package/dist/components/button/button.styles.js +66 -39
  22. package/dist/components/calendar/base/calendar.styles.js +3 -5
  23. package/dist/components/calendar/multi/multi.styles.js +7 -0
  24. package/dist/components/category-tag/category-tag.styles.js +10 -7
  25. package/dist/components/checkbox/checkbox.styles.js +2 -2
  26. package/dist/components/chip/chip.styles.js +2 -2
  27. package/dist/components/collect-fida/collect-fida.component.js +5 -3
  28. package/dist/components/collect-fida/collect-fida.styles.js +9 -16
  29. package/dist/components/crypto-auth-modal/crypto-auth-modal.component.js +6 -4
  30. package/dist/components/crypto-auth-modal/crypto-auth-modal.styles.js +2 -2
  31. package/dist/components/dialog/dialog.styles.js +10 -17
  32. package/dist/components/drawer/drawer.component.js +6 -0
  33. package/dist/components/drawer/drawer.styles.js +17 -3
  34. package/dist/components/dropdown/dropdown.component.js +7 -0
  35. package/dist/components/dropdown/dropdown.styles.js +3 -6
  36. package/dist/components/expiration-timer/expiration-timer.styles.js +3 -3
  37. package/dist/components/explore-card/explore-card.styles.js +1 -1
  38. package/dist/components/filter-chip/filter-chip.styles.js +11 -9
  39. package/dist/components/filters-drawer/filters-drawer.component.js +1 -1
  40. package/dist/components/filters-drawer/filters-drawer.styles.js +6 -5
  41. package/dist/components/image/image.styles.js +3 -3
  42. package/dist/components/image-chip/image-chip.styles.js +1 -1
  43. package/dist/components/input-field/input-field.styles.js +22 -17
  44. package/dist/components/itinerary/itinerary-item/itinerary-item.styles.js +1 -1
  45. package/dist/components/joined-fields/joined-fields.styles.js +10 -6
  46. package/dist/components/loading-spinner/loading-spinner.styles.js +1 -1
  47. package/dist/components/location-image/location-image.component.d.ts +6 -0
  48. package/dist/components/location-image/location-image.component.js +15 -2
  49. package/dist/components/location-image/location-image.styles.js +1 -1
  50. package/dist/components/locations-results/locations-results.styles.js +4 -6
  51. package/dist/components/marketing-tag/marketing-tag.styles.js +1 -1
  52. package/dist/components/message-card/message-card.styles.js +6 -6
  53. package/dist/components/option-group/option-chip/option-chip.styles.js +4 -3
  54. package/dist/components/order-confirmation/order-confirmation-item/order-confirmation-item.styles.js +9 -0
  55. package/dist/components/order-confirmation/order-confirmation.component.js +4 -3
  56. package/dist/components/order-confirmation/order-confirmation.styles.js +1 -1
  57. package/dist/components/otp/otp.styles.js +6 -6
  58. package/dist/components/overlay/overlay.styles.js +5 -1
  59. package/dist/components/pay-in/pay-in.styles.js +2 -2
  60. package/dist/components/payment-fiat-method/payment-fiat-method.styles.js +20 -4
  61. package/dist/components/payment-option/payment-option.styles.js +10 -12
  62. package/dist/components/performance-time/performance-time.styles.js +2 -2
  63. package/dist/components/phone-country-code/phone-country-code.styles.js +1 -1
  64. package/dist/components/points/points.styles.js +1 -1
  65. package/dist/components/search-button/search-button.styles.js +15 -7
  66. package/dist/components/seated-selector/seated-selector.component.d.ts +2 -0
  67. package/dist/components/seated-selector/seated-selector.component.js +53 -10
  68. package/dist/components/seated-selector/seated-selector.styles.js +77 -14
  69. package/dist/components/seats-io/seats-io.component.d.ts +2 -0
  70. package/dist/components/seats-io/seats-io.component.js +25 -2
  71. package/dist/components/seats-io/seats-io.constants.d.ts +2 -1
  72. package/dist/components/seats-io/seats-io.constants.js +6 -1
  73. package/dist/components/seats-io/seats-io.transform.d.ts +8 -2
  74. package/dist/components/seats-io/seats-io.transform.js +12 -4
  75. package/dist/components/select-list/select-list.styles.js +1 -0
  76. package/dist/components/select-list/select-option/select-option.styles.js +3 -3
  77. package/dist/components/sort-dropdown/sort-dropdown.styles.js +1 -1
  78. package/dist/components/sort-list/sort-list.styles.js +1 -1
  79. package/dist/components/sport-options/sport-option/sport-option.styles.js +2 -2
  80. package/dist/components/star-rating/star-rating.component.d.ts +1 -0
  81. package/dist/components/star-rating/star-rating.component.js +19 -23
  82. package/dist/components/star-rating/star-rating.styles.js +32 -6
  83. package/dist/components/tab/tab.styles.js +1 -1
  84. package/dist/components/tabs/tabs.styles.js +1 -1
  85. package/dist/components/tag/tag.styles.js +7 -7
  86. package/dist/components/text-section/text-section.styles.js +2 -1
  87. package/dist/components/time-tag/time-tag.styles.js +1 -2
  88. package/dist/components/tooltip/tooltip.styles.js +3 -3
  89. package/dist/components/tour-ticket-type-selector/tour-ticket-type/tour-ticket-type.styles.js +2 -2
  90. package/dist/components/tour-ticket-type-selector/tour-ticket-type-selector.component.js +1 -1
  91. package/dist/components/tour-ticket-type-selector/tour-ticket-type-selector.styles.js +1 -10
  92. package/dist/components/transfer-modal/transfer-modal.component.js +3 -3
  93. package/dist/components/transfer-modal/transfer-modal.styles.js +6 -8
  94. package/dist/containers/basket/basket.component.d.ts +1 -1
  95. package/dist/containers/basket/basket.component.js +56 -25
  96. package/dist/containers/footer/footer.component.d.ts +2 -0
  97. package/dist/containers/footer/footer.component.js +8 -0
  98. package/dist/containers/footer/footer.styles.js +12 -0
  99. package/dist/containers/navbar/navbar.component.d.ts +6 -1
  100. package/dist/containers/navbar/navbar.component.js +41 -9
  101. package/dist/containers/navbar/navbar.styles.js +47 -13
  102. package/dist/contexts/basket/basket.provider.d.ts +1 -0
  103. package/dist/contexts/basket/basket.provider.js +31 -4
  104. package/dist/contexts/properties.context.d.ts +4 -4
  105. package/dist/core/api/middlewares/booking-session/booking-session.constants.d.ts +1 -0
  106. package/dist/core/api/middlewares/booking-session/booking-session.constants.js +1 -0
  107. package/dist/core/api/middlewares/booking-session/booking-session.js +18 -27
  108. package/dist/core/api/middlewares/booking-session/booking-session.utils.d.ts +4 -4
  109. package/dist/core/api/middlewares/booking-session/booking-session.utils.js +13 -10
  110. package/dist/core/api/queries.js +3 -2
  111. package/dist/core/color-scheme.d.ts +14 -0
  112. package/dist/core/color-scheme.js +48 -0
  113. package/dist/core/currency/constants.d.ts +1 -1
  114. package/dist/core/currency/constants.js +4 -3
  115. package/dist/core/i18n/date.js +20 -9
  116. package/dist/core/i18n/locale-codes.d.ts +2 -2
  117. package/dist/core/i18n/locale-codes.js +4 -0
  118. package/dist/core/images/constants.d.ts +0 -5
  119. package/dist/core/images/constants.js +0 -5
  120. package/dist/core/images/helpers.d.ts +8 -0
  121. package/dist/core/images/helpers.js +14 -6
  122. package/dist/core/images/srcset.js +2 -2
  123. package/dist/core/storybook/demo.d.ts +4 -0
  124. package/dist/core/storybook/demo.js +17 -0
  125. package/dist/core/types/locales.d.ts +7 -0
  126. package/dist/core/utils/datetime.js +10 -4
  127. package/dist/core/utils/locale.d.ts +8 -1
  128. package/dist/core/utils/locale.js +10 -0
  129. package/dist/helpers/helpers.d.ts +1 -1
  130. package/dist/index.d.ts +2 -3
  131. package/dist/index.js +1 -2
  132. package/dist/locales/ar-SA.d.ts +279 -0
  133. package/dist/locales/ar-SA.js +288 -0
  134. package/dist/locales/da-DK.d.ts +100 -82
  135. package/dist/locales/da-DK.js +104 -89
  136. package/dist/locales/de-DE.d.ts +100 -82
  137. package/dist/locales/de-DE.js +104 -89
  138. package/dist/locales/en-IE.d.ts +250 -232
  139. package/dist/locales/en-IE.js +254 -239
  140. package/dist/locales/en-US.d.ts +100 -82
  141. package/dist/locales/en-US.js +107 -92
  142. package/dist/locales/es-ES.d.ts +100 -82
  143. package/dist/locales/es-ES.js +104 -89
  144. package/dist/locales/fr-FR.d.ts +100 -82
  145. package/dist/locales/fr-FR.js +104 -89
  146. package/dist/locales/fr-MA.d.ts +100 -82
  147. package/dist/locales/fr-MA.js +104 -89
  148. package/dist/locales/is-IS.d.ts +100 -82
  149. package/dist/locales/is-IS.js +104 -89
  150. package/dist/locales/it-IT.d.ts +100 -82
  151. package/dist/locales/it-IT.js +104 -89
  152. package/dist/locales/ms-MY.d.ts +100 -82
  153. package/dist/locales/ms-MY.js +106 -91
  154. package/dist/locales/nb-NO.d.ts +100 -82
  155. package/dist/locales/nb-NO.js +104 -89
  156. package/dist/locales/pl-PL.d.ts +100 -82
  157. package/dist/locales/pl-PL.js +104 -89
  158. package/dist/locales/pt-PT.d.ts +100 -82
  159. package/dist/locales/pt-PT.js +104 -89
  160. package/dist/locales/sv-SE.d.ts +100 -82
  161. package/dist/locales/sv-SE.js +104 -89
  162. package/dist/locales/th-TH.d.ts +100 -82
  163. package/dist/locales/th-TH.js +104 -89
  164. package/dist/locales/tr-TR.d.ts +100 -82
  165. package/dist/locales/tr-TR.js +104 -89
  166. package/dist/locales/zh-CN.d.ts +100 -82
  167. package/dist/locales/zh-CN.js +105 -90
  168. package/dist/locales/zh-HK.d.ts +279 -0
  169. package/dist/locales/zh-HK.js +288 -0
  170. package/dist/mixins/coinbase.mixin.js +1 -1
  171. package/dist/mixins/error-modal.styles.js +2 -2
  172. package/dist/mixins/location-search/location-search.js +3 -3
  173. package/dist/mixins/location-search/location-search.styles.js +6 -5
  174. package/dist/sdk/chrome.d.ts +9 -3
  175. package/dist/sdk/chrome.js +21 -9
  176. package/dist/sdk/element-adapter.js +11 -8
  177. package/dist/sdk/mount.d.ts +3 -3
  178. package/dist/sdk/mount.js +153 -48
  179. package/dist/sdk/navigation-intent.js +3 -0
  180. package/dist/sdk/page-registry.d.ts +14 -2
  181. package/dist/sdk/page-registry.js +33 -13
  182. package/dist/sdk/theme.d.ts +2 -16
  183. package/dist/sdk/theme.js +12 -44
  184. package/dist/sdk/types.d.ts +59 -30
  185. package/dist/sdk/url.d.ts +24 -3
  186. package/dist/sdk/url.js +66 -27
  187. package/dist/sdk/validation.d.ts +0 -7
  188. package/dist/sdk/validation.js +104 -48
  189. package/dist/styles/contrast-fixtures.d.ts +0 -5
  190. package/dist/styles/contrast-fixtures.js +79 -9
  191. package/dist/styles/contrast.js +4 -3
  192. package/dist/styles/defaults/component.js +17 -1
  193. package/dist/styles/primitives.d.ts +0 -10
  194. package/dist/styles/primitives.js +2 -12
  195. package/dist/styles/tokens.d.ts +1 -1
  196. package/dist/styles/tokens.generated.d.ts +1 -1
  197. package/dist/styles/tokens.generated.js +1 -1
  198. package/dist/styles/tokens.metadata.d.ts +1 -1
  199. package/dist/styles/tokens.metadata.js +378 -90
  200. package/dist/views/details-page/details-page.component.d.ts +5 -5
  201. package/dist/views/details-page/details-page.component.js +77 -80
  202. package/dist/views/details-page/details-page.styles.js +64 -9
  203. package/dist/views/help-page/help-page.component.d.ts +1 -0
  204. package/dist/views/help-page/help-page.component.js +11 -4
  205. package/dist/views/help-page/help-page.styles.js +21 -8
  206. package/dist/views/landing-page/landing-page.component.d.ts +4 -4
  207. package/dist/views/landing-page/landing-page.component.js +65 -65
  208. package/dist/views/landing-page/landing-page.styles.js +70 -3
  209. package/dist/views/payment-page/partials/activity-details/activity-details.styles.js +10 -33
  210. package/dist/views/payment-page/partials/billing-address/billing-address.styles.js +4 -9
  211. package/dist/views/payment-page/partials/confirmation/confirmation.styles.js +6 -6
  212. package/dist/views/payment-page/partials/hotel-pickup-list/hotel-pickup-list.styles.js +4 -4
  213. package/dist/views/payment-page/partials/payment-type-selector/payment-type-selector.styles.js +1 -0
  214. package/dist/views/payment-page/partials/stripe/stripe.component.d.ts +1 -1
  215. package/dist/views/payment-page/partials/stripe/stripe.component.js +11 -12
  216. package/dist/views/payment-page/partials/stripe/stripe.constants.d.ts +2 -15
  217. package/dist/views/payment-page/partials/stripe/stripe.constants.js +4 -17
  218. package/dist/views/payment-page/partials/stripe/stripe.styles.js +16 -23
  219. package/dist/views/payment-page/partials/stripe/stripe.utils.d.ts +25 -14
  220. package/dist/views/payment-page/partials/stripe/stripe.utils.js +111 -62
  221. package/dist/views/payment-page/partials/terms-checkbox/terms-checkbox.styles.js +1 -1
  222. package/dist/views/payment-page/payment-page.component.d.ts +1 -0
  223. package/dist/views/payment-page/payment-page.component.js +19 -9
  224. package/dist/views/payment-page/payment-page.styles.js +37 -32
  225. package/dist/views/payment-page/payment-page.utils.d.ts +7 -0
  226. package/dist/views/payment-page/payment-page.utils.js +29 -14
  227. package/dist/views/payment-page/payment-page.validators.js +9 -3
  228. package/dist/views/payment-page/services/payment-api.service.d.ts +1 -1
  229. package/dist/views/search-page/search-page.component.d.ts +1 -0
  230. package/dist/views/search-page/search-page.component.js +12 -6
  231. package/dist/views/search-page/search-page.styles.js +24 -2
  232. package/dist/views/suggestion-widget/suggestion-widget.styles.js +1 -1
  233. package/package.json +63 -76
  234. package/dist/custom-elements.json +0 -37397
  235. package/dist/sdk/meta.d.ts +0 -7
  236. package/dist/sdk/meta.js +0 -9
  237. package/dist/styles/tokens.css +0 -359
@@ -14,7 +14,7 @@ import { map } from "lit/directives/map.js";
14
14
  import { consume } from "@lit/context";
15
15
  import { SignalWatcher } from "@lit-labs/signals";
16
16
  import { isBasketDropdownOpen, isBasketMobileOpen, } from "../../core/signals.js";
17
- import { format, formatISO, getDate, isAfter, isBefore, parseISO, } from "date-fns";
17
+ import { formatISO, getDate, isAfter, isBefore, parseISO } from "date-fns";
18
18
  import { capitalize } from "../../core/utils/text.js";
19
19
  import { apiGet, apiPost, withErrorHandling } from "../../core/api/queries.js";
20
20
  import "../../components/button/button.js";
@@ -65,21 +65,19 @@ import { loyaltyPointsContext } from "../../contexts/properties.context.js";
65
65
  import { addRecentlySelectedSearches } from "../../containers/navbar/navbar.utils.js";
66
66
  import { filterMediaForGallery } from "../../core/images/filters.js";
67
67
  import { readSeatsIoConfig } from "../../core/types/seats-config.js";
68
- // TODO: can it be dynamic/responsive?
68
+ import { getApiLanguageCode } from "../../core/utils/locale.js";
69
69
  const IMAGE_CAROUSEL_WIDTH = 480;
70
70
  const IMAGE_CAROUSEL_HEIGHT = 320;
71
71
  /**
72
- * Details page component for viewing attraction details and creating reservations.
73
- *
74
- * @fires coras:navigate - Dispatched when navigating to another page
75
- * @fires coras:state-change - Dispatched when in-page params change (date selection)
76
- * @fires coras:reservation-created - Dispatched when a reservation is successfully created via the API
72
+ * @fires coras:navigate
73
+ * @fires coras:state-change
74
+ * @fires coras:reservation-created
77
75
  */
78
76
  let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixin(BaseMixin(SignalWatcher(LitElement)))) {
79
77
  constructor() {
80
78
  super(...arguments);
81
79
  this.attractionId = "";
82
- this.date = formatISO(new Date(), { representation: "date" });
80
+ this.date = "";
83
81
  this.currentPage = "details";
84
82
  this.loyaltyPoints = false;
85
83
  this.currentMonth = undefined;
@@ -147,15 +145,24 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
147
145
  size: 0,
148
146
  },
149
147
  }));
148
+ // A failed or empty response has no attraction to render. Returning an
149
+ // empty object here would complete the task, so every render branch below
150
+ // would dereference `attraction` on it and throw. Fail the task instead so
151
+ // the error modal is shown.
150
152
  if (!result.data) {
151
- return {};
153
+ throw new Error("Attraction data not available");
152
154
  }
153
155
  this.verifyDate(result.data.first_instance_date, result.data.last_instance_date, result.data.attraction.timezone);
154
156
  this.isSingleDayAvailability = isSingleDayAvailability(result.data.attraction.sale_periods ?? null);
155
157
  this.availabilityDays = getAvailabilityDays({
156
158
  timezone: result.data.attraction.timezone,
157
159
  salePeriods: result.data.attraction.sale_periods ?? null,
158
- targetMonth: getCurrentMonth(this.date || result.data.first_instance_date, this.currentMonth),
160
+ targetMonth: getCurrentMonth(this.date ||
161
+ formatDateWithTimezone({
162
+ dateString: result.data.first_instance_date,
163
+ timezone: result.data.attraction.timezone,
164
+ formatString: "yyyy-MM-dd",
165
+ }), this.currentMonth),
159
166
  });
160
167
  this.checkDateAvailability(result.data.first_instance_date, result.data.attraction.timezone);
161
168
  try {
@@ -165,9 +172,7 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
165
172
  name: result.data.attraction.attraction_name,
166
173
  });
167
174
  }
168
- catch {
169
- // noop
170
- }
175
+ catch { }
171
176
  return result.data;
172
177
  },
173
178
  args: () => [this.locale, this.currency, this.attractionId],
@@ -175,7 +180,7 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
175
180
  this.bookingSessionTask = new Task(this, {
176
181
  autoRun: false,
177
182
  task: async () => {
178
- const bookingSession = getBookingSession();
183
+ const bookingSession = getBookingSession(this.distributorID);
179
184
  if (bookingSession) {
180
185
  return bookingSession;
181
186
  }
@@ -185,7 +190,7 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
185
190
  locale: this.locale,
186
191
  endpoint: "/{api_version}/booking-session",
187
192
  body: {
188
- language: this.locale.split("-")[0],
193
+ language: getApiLanguageCode(this.locale),
189
194
  user_id: null,
190
195
  analytics_id: null,
191
196
  distributor_session_id: null,
@@ -198,22 +203,22 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
198
203
  try {
199
204
  const expirationDate = new Date(expiresHeader);
200
205
  if (!isNaN(expirationDate.getTime())) {
201
- storeBookingSession(newBookingSession, expirationDate);
206
+ storeBookingSession(this.distributorID, newBookingSession, expirationDate);
202
207
  }
203
208
  else {
204
- const defaultExpiration = new Date(Date.now() + 20 * 60 * 1000); // 20 minutes
205
- storeBookingSession(newBookingSession, defaultExpiration);
209
+ const defaultExpiration = new Date(Date.now() + 20 * 60 * 1000);
210
+ storeBookingSession(this.distributorID, newBookingSession, defaultExpiration);
206
211
  }
207
212
  }
208
213
  catch (error) {
209
214
  console.warn("Failed to parse booking session expiration date:", error);
210
- const defaultExpiration = new Date(Date.now() + 20 * 60 * 1000); // 20 minutes
211
- storeBookingSession(newBookingSession, defaultExpiration);
215
+ const defaultExpiration = new Date(Date.now() + 20 * 60 * 1000);
216
+ storeBookingSession(this.distributorID, newBookingSession, defaultExpiration);
212
217
  }
213
218
  }
214
219
  else {
215
- const defaultExpiration = new Date(Date.now() + 20 * 60 * 1000); // 20 minutes
216
- storeBookingSession(newBookingSession, defaultExpiration);
220
+ const defaultExpiration = new Date(Date.now() + 20 * 60 * 1000);
221
+ storeBookingSession(this.distributorID, newBookingSession, defaultExpiration);
217
222
  }
218
223
  return newBookingSession;
219
224
  }
@@ -239,9 +244,9 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
239
244
  bookingSession,
240
245
  body: {
241
246
  currency: this.currency,
242
- language_code: this.locale.split("-")[0],
247
+ language_code: getApiLanguageCode(this.locale),
243
248
  attraction_id: this.attractionId,
244
- date: this.date,
249
+ date: this.resolvedDate,
245
250
  tickets: Object.entries(this.selectedPriceTypes).map(([key, value]) => ({
246
251
  price_type_id: key,
247
252
  number_of_tickets: value,
@@ -261,8 +266,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
261
266
  this.hasAvailabilityError = true;
262
267
  }
263
268
  const availableItems = result.data.filter((item) => item.available) || [];
264
- // Auto-select the first performance time once the availability resolves,
265
- // keeping this state out of the render path.
266
269
  const sortedTimes = sortTimes(Array.from(groupAvailabilityByTime(availableItems, this.locale).keys()));
267
270
  this.selectedTime = sortedTimes[0] ?? null;
268
271
  availableItems.forEach((item) => {
@@ -301,8 +304,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
301
304
  throw new Error("Attraction data not available");
302
305
  }
303
306
  const seatedPayload = seated;
304
- // Seated selection books a specific performance instance with the chosen
305
- // seats; the section flow books a selection_id with ticket-type counts.
306
307
  const reservationFields = seatedPayload
307
308
  ? {
308
309
  selection_id: null,
@@ -323,8 +324,8 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
323
324
  body: {
324
325
  attraction_id: this.attractionId,
325
326
  currency: this.currency,
326
- date: this.date,
327
- language_code: this.locale.split("-")[0],
327
+ date: this.resolvedDate,
328
+ language_code: getApiLanguageCode(this.locale),
328
329
  tour_language: language,
329
330
  to_basket: true,
330
331
  pickup_id: null,
@@ -338,8 +339,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
338
339
  return {};
339
340
  }
340
341
  if (result.data) {
341
- // Emit only a minimal, redacted public payload; never the raw
342
- // reservation API response (P-038 data minimization).
343
342
  const reservation = result.data;
344
343
  fireEvent(this, "coras:reservation-created", {
345
344
  ...(reservation.reservation_id != null
@@ -392,7 +391,7 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
392
391
  this.seatsFallback = false;
393
392
  await this.bookingSessionTask.run();
394
393
  await this.availabilityTask.run();
395
- this.shouldRenderOptions = true; // TODO: move to request?
394
+ this.shouldRenderOptions = true;
396
395
  };
397
396
  this.renderAttractionHeader = () => {
398
397
  const apiResponse = this.attractionTask.value;
@@ -600,20 +599,16 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
600
599
  if (!apiResponse) {
601
600
  return nothing;
602
601
  }
603
- const date = this.date ||
604
- formatISO(parseISO(apiResponse.first_instance_date), {
605
- representation: "date",
606
- });
602
+ const date = this.resolvedDate;
607
603
  if (this.isSingleDayAvailability) {
608
604
  return html `
609
605
  <div id="calendar-dropdown">
610
606
  <coras-button
611
607
  variant="outlined"
612
- radius="small"
608
+ radius="field"
613
609
  height="md"
614
610
  .shadow=${false}
615
611
  .clickable=${false}
616
- background
617
612
  expanded
618
613
  >
619
614
  <coras-icon
@@ -636,10 +631,9 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
636
631
  <coras-button
637
632
  slot="trigger"
638
633
  variant="outlined"
639
- radius="small"
634
+ radius="field"
640
635
  height="md"
641
636
  border-color="light"
642
- background
643
637
  expanded
644
638
  @click=${this.toggleCalendarDropdown}
645
639
  ?error=${this.hasAvailabilityError}
@@ -664,8 +658,16 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
664
658
  <coras-month-calendar
665
659
  assets-url="${this.assetsURL}"
666
660
  locale=${this.locale}
667
- min-date=${format(parseISO(apiResponse.first_instance_date), "yyyy-MM")}
668
- max-date=${format(parseISO(apiResponse.last_instance_date), "yyyy-MM")}
661
+ min-date=${formatDateWithTimezone({
662
+ dateString: apiResponse.first_instance_date,
663
+ timezone: apiResponse.attraction.timezone,
664
+ formatString: "yyyy-MM",
665
+ })}
666
+ max-date=${formatDateWithTimezone({
667
+ dateString: apiResponse.last_instance_date,
668
+ timezone: apiResponse.attraction.timezone,
669
+ formatString: "yyyy-MM",
670
+ })}
669
671
  .selectedDates=${[date]}
670
672
  .availableDays=${this.availabilityDays}
671
673
  selected-month=${getCurrentMonth(date, this.currentMonth)}
@@ -785,12 +787,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
785
787
  };
786
788
  this.renderTourOptions = (items) => {
787
789
  return html `${map(items, ({ name, languages_spoken, selection_id, prices, start_datetime, timezone, max_full_price, }) => {
788
- // The availability API returns one `start_datetime` per item, so each
789
- // tour option is a single timed slot (a tour offered at several times
790
- // arrives as several items). There is thus one time per option: the
791
- // per-option time picker stays collapsed and the slot shows as a badge.
792
- // Grouping items into a single multi-time option is a separate feature
793
- // that also needs the selection_id<->time basket mapping reworked.
794
790
  const hasOnlyOneTimeOption = true;
795
791
  const hasMultipleLanguages = Object.keys(languages_spoken || {}).length > 1;
796
792
  return html `
@@ -849,10 +845,7 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
849
845
  })}`;
850
846
  };
851
847
  this.handleSeatsConfirmed = async (event) => {
852
- // Capture the selector now: currentTarget is nulled once dispatch ends, and
853
- // we only read it after awaiting the reservation.
854
848
  const selector = event.target;
855
- // Show the confirm button's spinner while the basket call is in flight.
856
849
  if (selector) {
857
850
  selector.reserving = true;
858
851
  }
@@ -867,7 +860,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
867
860
  if (this.reservationsTask.status !== TaskStatus.ERROR &&
868
861
  this.reservationsTask.value &&
869
862
  Object.keys(this.reservationsTask.value).length > 0) {
870
- // Close the fixed seat-summary bar so it doesn't sit over the basket.
871
863
  selector?.reservationCreated();
872
864
  const isMobile = window.matchMedia("(max-width: 800px)").matches;
873
865
  if (isMobile) {
@@ -878,10 +870,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
878
870
  isBasketDropdownOpen.set(true);
879
871
  scrollToTop();
880
872
  }
881
- // Return the page to a clean slate: collapse the seat map and reset the
882
- // ticket selector to its default so the shopper starts fresh for any
883
- // further booking. The just-added item lives in the basket now; the
884
- // "Added to basket" summary reads the reservation, not this state.
885
873
  this.shouldRenderOptions = false;
886
874
  this.selectedPriceTypes = {};
887
875
  this.selectedTime = null;
@@ -919,9 +907,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
919
907
  ? Array.from(timeGroups.get(this.selectedTime) || [])
920
908
  : [];
921
909
  const seatedPerformance = this.findSeatedPerformance(filteredOptions);
922
- // Only offer the time picker when there is an actual choice. A single
923
- // performance time is auto-selected above, so showing one lone button
924
- // is noise - render straight to the seat/section options instead.
925
910
  const showTimePicker = sortedTimes.length > 1;
926
911
  return html `
927
912
  <div class="options">
@@ -1023,7 +1008,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1023
1008
  };
1024
1009
  })
1025
1010
  .filter((item) => item !== null);
1026
- // Calculate booking fee
1027
1011
  const bookingFee = convertToLocaleCurrency({
1028
1012
  value: summary.bookingFeePerTicket,
1029
1013
  locale: this.locale,
@@ -1073,7 +1057,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1073
1057
  @click="${async () => {
1074
1058
  await this.handleBasketModalToggle(this.selectedSectionId);
1075
1059
  this.isReservationPreviewModalOpened = false;
1076
- // Defer scroll past the dialog's scroll-lock restore (rAF-deferred)
1077
1060
  setTimeout(() => scrollToTop(), 0);
1078
1061
  }}"
1079
1062
  >
@@ -1119,9 +1102,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1119
1102
  };
1120
1103
  this.renderBasketModal = () => {
1121
1104
  const apiResponse = this.attractionTask.value;
1122
- // Prefer the count actually reserved (seated bookings can differ from the
1123
- // upfront selection, which is also reset once the item is in the basket);
1124
- // fall back to the selection only before a reservation exists.
1125
1105
  const reservedTickets = this.reservationsTask.value && "tickets" in this.reservationsTask.value
1126
1106
  ? this.reservationsTask.value.tickets
1127
1107
  : null;
@@ -1271,8 +1251,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1271
1251
  });
1272
1252
  }
1273
1253
  async handleBasketModalToggle(selection_id) {
1274
- // TODO: check if success, if not show error message
1275
- // TODO: it calls the task once again when modal is closed
1276
1254
  if (selection_id) {
1277
1255
  this.loadingSelectionIds = new Set([
1278
1256
  ...this.loadingSelectionIds,
@@ -1285,7 +1263,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1285
1263
  if (this.reservationsTask.status !== TaskStatus.ERROR &&
1286
1264
  this.reservationsTask.value &&
1287
1265
  Object.keys(this.reservationsTask.value).length > 0) {
1288
- // TODO: make reusable
1289
1266
  const isMobile = window.matchMedia("(max-width: 800px)").matches;
1290
1267
  if (isMobile) {
1291
1268
  this.isBasketModalOpened = !this.isBasketModalOpened;
@@ -1310,6 +1287,18 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1310
1287
  return true;
1311
1288
  }
1312
1289
  }
1290
+ get resolvedDate() {
1291
+ if (this.date)
1292
+ return this.date;
1293
+ const apiResponse = this.attractionTask.value;
1294
+ if (!apiResponse)
1295
+ return "";
1296
+ return formatDateWithTimezone({
1297
+ dateString: apiResponse.first_instance_date,
1298
+ timezone: apiResponse.attraction.timezone,
1299
+ formatString: "yyyy-MM-dd",
1300
+ });
1301
+ }
1313
1302
  buildSectionTickets(selection_id) {
1314
1303
  const apiResponse = this.attractionTask.value;
1315
1304
  const availabilityItems = this.availabilityTask
@@ -1410,12 +1399,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1410
1399
  </coras-breadcrumbs>
1411
1400
  `;
1412
1401
  }
1413
- // Resolves the selected performance to the seat map's one prerequisite: the
1414
- // instance_id (the seats.io event key and the seats endpoint path param).
1415
- // Price bands and per-seat data come from the seats endpoint itself, so the
1416
- // availability item only has to supply the instance. Returns null to fall
1417
- // back to the section picker. All section items of a performance share the
1418
- // same instance_id, so the first option resolves it.
1419
1402
  findSeatedPerformance(options) {
1420
1403
  const apiResponse = this.attractionTask.value;
1421
1404
  if (this.seatsFallback ||
@@ -1423,8 +1406,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1423
1406
  apiResponse?.attraction_type !== "THEATER") {
1424
1407
  return null;
1425
1408
  }
1426
- // attraction_type is "THEATER" | "TOUR" on both variants, so it cannot
1427
- // narrow the union structurally; the runtime check above is the guard.
1428
1409
  const event = apiResponse;
1429
1410
  if (!event.attraction.seats_sales_enabled) {
1430
1411
  return null;
@@ -1441,9 +1422,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1441
1422
  renderSeatedSelector(performance) {
1442
1423
  const seatsConfig = this.configTask.value;
1443
1424
  const event = this.attractionTask.value;
1444
- // Cap seat selection at the venue's per-order maximum rather than the
1445
- // upfront ticket count, so the shopper can revise how many seats they want
1446
- // straight on the map. 0 tells the chart there is no cap.
1447
1425
  const maxSeats = event?.attraction.max_tickets_per_order ?? 0;
1448
1426
  return html `
1449
1427
  <coras-seated-selector
@@ -1486,7 +1464,6 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1486
1464
  }
1487
1465
  handleGoToBasketButtonClicked() {
1488
1466
  this.handleBasketModalToggle();
1489
- // TODO: make reusable
1490
1467
  const isMobile = window.matchMedia("(max-width: 800px)").matches;
1491
1468
  if (isMobile) {
1492
1469
  isBasketMobileOpen.set(true);
@@ -1497,11 +1474,31 @@ let CorasDetailsPage = class CorasDetailsPage extends ErrorModalMixin(PointsMixi
1497
1474
  scrollToTop();
1498
1475
  }
1499
1476
  }
1477
+ renderSkeleton() {
1478
+ return html `
1479
+ <div class="details-skeleton" aria-hidden="true">
1480
+ <div class="coras-skeleton sk-breadcrumb"></div>
1481
+ <div class="coras-skeleton sk-title"></div>
1482
+ <div class="sk-header">
1483
+ <div class="coras-skeleton sk-image"></div>
1484
+ <div class="coras-skeleton sk-panel"></div>
1485
+ </div>
1486
+ <div class="sk-description">
1487
+ <div class="coras-skeleton sk-line"></div>
1488
+ <div class="coras-skeleton sk-line"></div>
1489
+ <div class="coras-skeleton sk-line sk-short"></div>
1490
+ </div>
1491
+ </div>
1492
+ <span class="coras-sr-only" role="status">${msg("Loading")}</span>
1493
+ `;
1494
+ }
1500
1495
  render() {
1501
1496
  return html `
1502
1497
  <coras-layout with-basket>
1503
1498
  <slot name="navbar" slot="navbar"></slot>
1504
1499
  ${this.attractionTask.render({
1500
+ initial: () => this.renderSkeleton(),
1501
+ pending: () => this.renderSkeleton(),
1505
1502
  error: () => {
1506
1503
  this.showErrorModalOnce();
1507
1504
  return html ``;
@@ -1,11 +1,70 @@
1
1
  import { css } from "lit";
2
2
  import component from "../../styles/defaults/component.js";
3
+ import { skeleton, visuallyHidden } from "../../styles/primitives.js";
3
4
  export default css `
4
5
  ${component}
6
+ ${skeleton}
7
+ ${visuallyHidden}
8
+
9
+ .details-skeleton {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 1.75rem;
13
+
14
+ .sk-breadcrumb {
15
+ width: 14rem;
16
+ max-width: 60%;
17
+ height: 1rem;
18
+ }
19
+
20
+ .sk-title {
21
+ width: 26rem;
22
+ max-width: 80%;
23
+ height: 2.25rem;
24
+ }
25
+
26
+ .sk-header {
27
+ display: grid;
28
+ grid-template-columns: 1fr 1fr;
29
+ gap: 2.5rem;
30
+ width: 100%;
31
+
32
+ @container --coras-layout (max-width: 800px) {
33
+ grid-template-columns: 1fr;
34
+ }
35
+ }
36
+
37
+ .sk-image {
38
+ width: 100%;
39
+ max-width: 480px;
40
+ aspect-ratio: 4 / 3;
41
+ }
42
+
43
+ .sk-panel {
44
+ width: 100%;
45
+ max-width: 280px;
46
+ height: 20rem;
47
+ }
48
+
49
+ .sk-description {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: 0.75rem;
53
+ }
54
+
55
+ .sk-line {
56
+ width: 100%;
57
+ height: 1rem;
58
+
59
+ &.sk-short {
60
+ width: 60%;
61
+ }
62
+ }
63
+ }
5
64
 
6
65
  :host {
7
66
  --details-page-ticket-availability-border-color: var(
8
- --coras-color-neutral-200
67
+ --coras-color-card-border
9
68
  );
10
69
  --details-page-ticket-availability-border-radius: var(--coras-radius-md);
11
70
  --details-page-ticket-availability-caption-font-size: var(
@@ -16,9 +75,7 @@ export default css `
16
75
  );
17
76
  --details-page-ticket-availability-caption-color: var(--coras-color-text);
18
77
 
19
- --details-page-date-field-open-chevron-color: var(
20
- --coras-color-primary-500
21
- );
78
+ --details-page-date-field-open-chevron-color: var(--coras-color-primary);
22
79
  --details-page-ticket-availability-caption-font-size-mobile: var(
23
80
  --coras-font-size-sm
24
81
  );
@@ -55,7 +112,7 @@ export default css `
55
112
 
56
113
  --details-page-reviews-footer-font-size: var(--coras-font-size-sm);
57
114
  --details-page-reviews-footer-font-weight: var(--coras-font-weight-normal);
58
- --details-page-reviews-footer-color: var(--coras-color-text-secondary);
115
+ --details-page-reviews-footer-color: var(--coras-color-text-muted);
59
116
 
60
117
  --details-page-top-panel-max-width: 820px;
61
118
  --details-page-details-max-width: 562px;
@@ -375,8 +432,6 @@ export default css `
375
432
  }
376
433
  }
377
434
 
378
- /* Basket modal */
379
-
380
435
  .basket-modal-header {
381
436
  display: flex;
382
437
  align-items: center;
@@ -391,7 +446,7 @@ export default css `
391
446
  height: 28px;
392
447
 
393
448
  &::part(svg) {
394
- fill: var(--coras-color-success-500);
449
+ fill: var(--coras-color-success);
395
450
  width: 28px;
396
451
  height: 28px;
397
452
  }
@@ -430,7 +485,7 @@ export default css `
430
485
 
431
486
  .basket-modal-date {
432
487
  margin-bottom: 1.125rem;
433
- color: var(--coras-color-text-secondary);
488
+ color: var(--coras-color-text-muted);
434
489
  font-size: var(--coras-font-size-sm);
435
490
  font-weight: var(--coras-font-weight-normal);
436
491
  line-height: 1.25rem;
@@ -59,6 +59,7 @@ export declare class CorasHelpPage extends CorasHelpPage_base {
59
59
  private closeSubjectDropdown;
60
60
  private handleModalClose;
61
61
  private handleTabChange;
62
+ private renderHelpSkeleton;
62
63
  private renderFaqAccordion;
63
64
  private renderAttractionsTicketsTab;
64
65
  private renderMusicTicketsTab;
@@ -226,13 +226,21 @@ let CorasHelpPage = class CorasHelpPage extends BaseMixin(LitElement) {
226
226
  content: event.detail.tab,
227
227
  });
228
228
  }
229
+ renderHelpSkeleton() {
230
+ return html `
231
+ <div class="help-skeleton" aria-hidden="true">
232
+ ${Array.from({ length: 5 }, () => html `<div class="coras-skeleton sk-row"></div>`)}
233
+ </div>
234
+ <span class="coras-sr-only" role="status">${msg("Loading")}</span>
235
+ `;
236
+ }
229
237
  renderFaqAccordion() {
230
238
  return this.helpTabTask.render({
231
239
  error: () => {
232
240
  // this.showErrorModalOnce();
233
241
  return html ``;
234
242
  },
235
- loading: () => html `loading`,
243
+ pending: () => this.renderHelpSkeleton(),
236
244
  complete: (result) => {
237
245
  if (!result?.data)
238
246
  return nothing;
@@ -358,7 +366,6 @@ let CorasHelpPage = class CorasHelpPage extends BaseMixin(LitElement) {
358
366
  height="md"
359
367
  border-color="light"
360
368
  expanded
361
- background
362
369
  @click=${this.toggleSubjectDropdown}
363
370
  ?error=${hasError}
364
371
  aria-label=${label}
@@ -375,7 +382,7 @@ let CorasHelpPage = class CorasHelpPage extends BaseMixin(LitElement) {
375
382
  <coras-select-list spacious>
376
383
  ${this.content === "contact"
377
384
  ? this.subjectsTask.render({
378
- loading: () => html `
385
+ pending: () => html `
379
386
  <coras-select-option></coras-select-option>
380
387
  `,
381
388
  error: () => html `
@@ -409,7 +416,7 @@ let CorasHelpPage = class CorasHelpPage extends BaseMixin(LitElement) {
409
416
  renderInfoContent() {
410
417
  return this.helpTabTask.render({
411
418
  error: () => html ``,
412
- loading: () => html `loading`,
419
+ pending: () => this.renderHelpSkeleton(),
413
420
  complete: (result) => {
414
421
  if (!result?.data) {
415
422
  return nothing;
@@ -1,10 +1,25 @@
1
1
  import { css } from "lit";
2
2
  import component from "../../styles/defaults/component.js";
3
+ import { skeleton, visuallyHidden } from "../../styles/primitives.js";
3
4
  export default css `
4
5
  ${component}
6
+ ${skeleton}
7
+ ${visuallyHidden}
8
+
9
+ .help-skeleton {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 1rem;
13
+ width: 100%;
14
+
15
+ .sk-row {
16
+ width: 100%;
17
+ height: 3.5rem;
18
+ }
19
+ }
5
20
 
6
21
  :host {
7
- --help-center-header-font-size: var(--coras-font-size-2xl);
22
+ --help-center-header-font-size: var(--coras-font-size-heading);
8
23
  --help-center-header-font-weight: var(--coras-font-weight-semibold);
9
24
  --help-center-header-line-height: var(--coras-font-size-3xl);
10
25
  --help-center-header-color: var(--coras-color-text);
@@ -17,7 +32,7 @@ export default css `
17
32
  --help-center-modal-message-line-height: var(--coras-font-size-xl);
18
33
  --help-center-modal-message-font-weight: var(--coras-font-weight-normal);
19
34
  --help-center-modal-message-color: var(--coras-color-text);
20
- --help-center-modal-icon-color: var(--coras-color-success-500);
35
+ --help-center-modal-icon-color: var(--coras-color-success);
21
36
  --help-center-modal-icon-size: 32px;
22
37
 
23
38
  display: grid;
@@ -91,14 +106,12 @@ export default css `
91
106
  margin-block: 2rem;
92
107
  }
93
108
 
94
- /* Add spacing between numbered info items */
95
109
  .info-numbered {
96
110
  display: flex;
97
111
  flex-direction: column;
98
112
  gap: 1rem;
99
113
  }
100
114
 
101
- /* Numbering style for nested ordered lists within info content */
102
115
  .info-numbered ol {
103
116
  counter-reset: section;
104
117
  padding-left: 1.25rem;
@@ -107,10 +120,10 @@ export default css `
107
120
 
108
121
  .info-numbered ol > li {
109
122
  counter-increment: section;
110
- list-style: none; /* hide default numbering to use CSS counters */
123
+ list-style: none;
111
124
  position: relative;
112
- padding-left: 1rem; /* 1rem gap between number and content */
113
- margin: 1rem 0; /* ensure 1rem between items */
125
+ padding-left: 1rem;
126
+ margin: 1rem 0;
114
127
  }
115
128
 
116
129
  .info-numbered ol > li::before {
@@ -171,7 +184,7 @@ export default css `
171
184
  align-items: center;
172
185
  justify-content: center;
173
186
  padding: 3rem 1rem;
174
- border-top: 1px solid var(--coras-color-neutral-200);
187
+ border-top: 1px solid var(--coras-color-divider);
175
188
  }
176
189
 
177
190
  .container {