@riverbankcms/sdk 0.144.0 → 0.145.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 (162) hide show
  1. package/dist/{PreviewEditorSidebar-CQZNnFiM.mjs → PreviewEditorSidebar-O-V6Pl5a.mjs} +4 -4
  2. package/dist/{PreviewEditorUI-DCNxnmI0.mjs → PreviewEditorUI-1UlX_9cw.mjs} +3 -3
  3. package/dist/{PreviewQueryProvider-BCYICImX.mjs → PreviewQueryProvider-BxGwdOCV.mjs} +1 -1
  4. package/dist/{SdkPreviewModeRuntime-BJolUbr1.mjs → SdkPreviewModeRuntime-DYbA_4iv.mjs} +419 -348
  5. package/dist/{SiteChromeCustomizeContext-CKgEpoxi.mjs → SiteChromeCustomizeContext-CNTKQ9nx.mjs} +2081 -1313
  6. package/dist/_dts/ai/src/contracts/proposals.d.ts +4719 -66
  7. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +429 -6
  8. package/dist/_dts/api/src/appointmentEndpointDefinitions.d.ts +49 -0
  9. package/dist/_dts/api/src/appointmentServiceCategoryEndpoints.d.ts +75 -0
  10. package/dist/_dts/api/src/appointmentServiceContracts.d.ts +80 -0
  11. package/dist/_dts/api/src/appointmentSetup.d.ts +8 -1
  12. package/dist/_dts/api/src/bookingManagementEndpoints.d.ts +2 -12
  13. package/dist/_dts/api/src/communications.d.ts +14 -22
  14. package/dist/_dts/api/src/contentRuntime.d.ts +2 -0
  15. package/dist/_dts/api/src/endpoints.d.ts +105 -58
  16. package/dist/_dts/api/src/indexExtendedTypes.d.ts +1 -1
  17. package/dist/_dts/api/src/launchReadiness.d.ts +2 -0
  18. package/dist/_dts/api/src/newsletterEndpointDefinitions.d.ts +0 -23
  19. package/dist/_dts/api/src/onboarding/confirmationResend.d.ts +3 -0
  20. package/dist/_dts/api/src/publicBookingService.d.ts +35 -1
  21. package/dist/_dts/api/src/publicExperience/newsletterEndpoints.d.ts +13 -0
  22. package/dist/_dts/api/src/publicNewsletterEndpointDefinitions.d.ts +58 -0
  23. package/dist/_dts/api/src/schemas/admin-observability-logs.d.ts +3 -3
  24. package/dist/_dts/api/src/siteManagementEndpoints.d.ts +2 -6
  25. package/dist/_dts/api/src/types.d.ts +2 -1
  26. package/dist/_dts/blocks/src/PageRenderer.d.ts +4 -4
  27. package/dist/_dts/blocks/src/bindings/index.d.ts +2 -2
  28. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +24 -0
  29. package/dist/_dts/blocks/src/index.d.ts +1 -1
  30. package/dist/_dts/blocks/src/pageRenderJoins.d.ts +68 -0
  31. package/dist/_dts/blocks/src/pageRenderPlan.d.ts +8 -27
  32. package/dist/_dts/blocks/src/pageRenderPreparation.d.ts +7 -4
  33. package/dist/_dts/blocks/src/pageRendererBoundaries.d.ts +40 -23
  34. package/dist/_dts/blocks/src/server.d.ts +1 -1
  35. package/dist/_dts/blocks/src/system/blocks/appointment-services.d.ts +2 -0
  36. package/dist/_dts/blocks/src/system/blocks/events/eventSelectionFields.d.ts +2 -2
  37. package/dist/_dts/blocks/src/system/blocks/events/shared/calendarWindow.d.ts +1 -1
  38. package/dist/_dts/blocks/src/system/blocks/location-map.d.ts +2 -2
  39. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +47 -47
  40. package/dist/_dts/blocks/src/system/manifest/schema.d.ts +29 -3
  41. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.d.ts +8 -1
  42. package/dist/_dts/blocks/src/system/runtime/nodes/appointment-services.d.ts +22 -2
  43. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +3 -1
  44. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.interactive.d.ts +57 -2
  45. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.service-categories.d.ts +29 -0
  46. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.service-choices.d.ts +13 -0
  47. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +3 -3
  48. package/dist/_dts/blocks/src/system/runtime/nodes/booking-runtime-sources.d.ts +13 -1
  49. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.d.ts +1 -1
  50. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +3 -3
  51. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.submission.d.ts +20 -1
  52. package/dist/_dts/content-editor/src/ui/editorSnapshot.d.ts +2 -0
  53. package/dist/_dts/content-model/src/content-types/schema.d.ts +1 -1
  54. package/dist/_dts/core/src/appointment-service-categories.d.ts +108 -0
  55. package/dist/_dts/core/src/editor-state.d.ts +6 -0
  56. package/dist/_dts/core/src/index.d.ts +1 -0
  57. package/dist/_dts/core/src/public-profile.d.ts +9 -0
  58. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +663 -241
  59. package/dist/_dts/editor-blocks/src/block-editor/types.d.ts +6 -0
  60. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/loaders.d.ts +16 -1
  61. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/types.d.ts +13 -0
  62. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/utils.d.ts +29 -0
  63. package/dist/_dts/preview-next/src/client/blocks/BoundaryControl.d.ts +7 -2
  64. package/dist/_dts/preview-next/src/client/blocks/composer/mergeDrafts.d.ts +1 -1
  65. package/dist/_dts/preview-next/src/client/blocks/composer/types.d.ts +1 -0
  66. package/dist/_dts/preview-next/src/client/blocks/previewProjection/applyPreviewProjections.d.ts +1 -3
  67. package/dist/_dts/preview-next/src/client/blocks/previewProjection/runtimeContentDecoration.d.ts +2 -2
  68. package/dist/_dts/preview-next/src/client/blocks/previewProjection/types.d.ts +2 -2
  69. package/dist/_dts/preview-next/src/client/preview/PreviewEntryControllerProvider.d.ts +1 -0
  70. package/dist/_dts/sdk/src/client/runtimeCacheKeys.d.ts +8 -0
  71. package/dist/_dts/sdk/src/client/types.d.ts +1 -1
  72. package/dist/_dts/sdk/src/contracts/index.d.ts +1 -1
  73. package/dist/_dts/sdk/src/contracts/page.d.ts +7 -2
  74. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +5 -0
  75. package/dist/_dts/sdk/src/manifest/passes/pass1-fields.d.ts +2 -2
  76. package/dist/_dts/sdk/src/public-api/contracts.d.ts +6 -8
  77. package/dist/_dts/sdk/src/rendering/blocks-page-renderer.d.ts +2 -2
  78. package/dist/_dts/sdk/src/rendering/components/pageShared.d.ts +2 -2
  79. package/dist/_dts/sdk/src/rendering/helpers/loadContent.d.ts +1 -1
  80. package/dist/_dts/sdk/src/rendering/hooks/useContent.d.ts +1 -1
  81. package/dist/_dts/sdk/src/version.d.ts +1 -1
  82. package/dist/_dts/surfaces/src/index.d.ts +1 -0
  83. package/dist/_dts/theme-core/src/layout/boundaryCss.d.ts +3 -0
  84. package/dist/_dts/theme-core/src/site-styles/compositionBudget.d.ts +8 -0
  85. package/dist/_dts/theme-core/src/site-styles/contentTemplateRuntimeCompiler.d.ts +3 -2
  86. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +1 -3
  87. package/dist/_dts/theme-core/src/site-styles/generatedDesignPipeline.d.ts +3 -0
  88. package/dist/_dts/theme-core/src/site-styles/index.d.ts +3 -0
  89. package/dist/_dts/theme-core/src/site-styles/joinTreatment.d.ts +132 -0
  90. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorBoundaries.d.ts +31 -0
  91. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorModel.d.ts +33 -29
  92. package/dist/_dts/theme-core/src/site-styles/pageDesignIntent.d.ts +4 -4
  93. package/dist/_dts/theme-core/src/site-styles/pageFlowPresets.d.ts +36 -0
  94. package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +4 -10
  95. package/dist/_dts/theme-core/src/site-styles/rawStyleFields.d.ts +17 -4
  96. package/dist/_dts/theme-core/src/site-styles/runtimeCompiler.d.ts +20 -74
  97. package/dist/_dts/theme-core/src/site-styles/sectionBoundaryComposition.d.ts +95 -0
  98. package/dist/_dts/theme-core/src/site-styles/sectionTransitions.d.ts +80 -518
  99. package/dist/_dts/theme-core/src/site-styles/siteStyleBlockPolicies.d.ts +9 -4
  100. package/dist/_dts/theme-core/src/site-styles/transitionCatalogue.d.ts +320 -0
  101. package/dist/_dts/theme-core/src/site-styles/types.d.ts +3 -18
  102. package/dist/client/{BookingConsentAgreement-DoAILrkN.mjs → BookingConsentAgreement-DlVOmpW4.mjs} +2 -2
  103. package/dist/client/{DiscountCodeDisclosure-X528hUTf.mjs → DiscountCodeDisclosure-T9q623PQ.mjs} +1 -1
  104. package/dist/client/{EventCombined.client-DSBVwq5m.mjs → EventCombined.client-DnsUeONl.mjs} +6 -6
  105. package/dist/client/{EventListing.client-DmFAX7Vb.mjs → EventListing.client-CNBFqJS4.mjs} +2 -2
  106. package/dist/client/{LeafletStaticMap-BAqXdfhq.mjs → LeafletStaticMap-C7higg52.mjs} +1 -1
  107. package/dist/client/{ThemedButton-BwNFVwi4.mjs → ThemedButton-LAJTNrS8.mjs} +1 -1
  108. package/dist/client/{accordion-DRhMdkbd.mjs → accordion-BcNF9TjX.mjs} +1 -1
  109. package/dist/client/{bookingForm-iJ_nCsNF.mjs → bookingForm-BUaQ3S03.mjs} +142 -50
  110. package/dist/client/bookings.mjs +191 -64
  111. package/dist/client/{carousel-D8zuffZr.mjs → carousel-CTdF6e0V.mjs} +1 -1
  112. package/dist/client/{cart-DeMGJViN.mjs → cart-pGtyf_XU.mjs} +3 -3
  113. package/dist/client/{checkout-DXc06Km2.mjs → checkout-Da9WSn7Y.mjs} +3 -3
  114. package/dist/client/client.mjs +1630 -816
  115. package/dist/client/{context-CDLCfDep.mjs → context-BFeY_Wbm.mjs} +1 -1
  116. package/dist/client/{dates-ClFx7Epy.mjs → dates-BhwVSxDg.mjs} +2 -2
  117. package/dist/client/{eventCalendar-BaGpnOGN.mjs → eventCalendar-BAtdnR4s.mjs} +9 -9
  118. package/dist/client/{eventCombined-ywONOx2m.mjs → eventCombined-RksA3tRC.mjs} +4 -4
  119. package/dist/client/{eventDetails-B2LlOTYY.mjs → eventDetails-BB9hB4wR.mjs} +4 -4
  120. package/dist/client/{eventListing-CMgxWpqP.mjs → eventListing-BpJBkviM.mjs} +4 -4
  121. package/dist/client/{eventRegistration-BarLDNHR.mjs → eventRegistration-B18jFq_o.mjs} +8 -8
  122. package/dist/client/{form-BjqKaBJ7.mjs → form-Dodt8Y8t.mjs} +3 -3
  123. package/dist/client/{headerSection-CP4XDwz1.mjs → headerSection-B0qj9ttg.mjs} +3 -3
  124. package/dist/client/hooks.mjs +550 -99
  125. package/dist/client/{locationMap-C5gW-d-w.mjs → locationMap-gn-iMk7a.mjs} +3 -3
  126. package/dist/client/{newsletterForm-afXlVSA0.mjs → newsletterForm-oByLdFkr.mjs} +26 -5
  127. package/dist/client/{pricing-Bl1CAl28.mjs → pricing-BOU0_Hai.mjs} +1 -1
  128. package/dist/client/{productDetail-CvQ3jWQe.mjs → productDetail-D9v1zH2j.mjs} +3 -3
  129. package/dist/client/{productList-U874PGO7.mjs → productList-D-mItx1n.mjs} +3 -3
  130. package/dist/client/{propsDecoder-DPXtCZWP.mjs → propsDecoder-C3TP_z4x.mjs} +72 -33
  131. package/dist/client/rendering/client.mjs +900 -266
  132. package/dist/client/rendering/islands.mjs +17 -17
  133. package/dist/client/rendering.mjs +1637 -788
  134. package/dist/client/{routeIslandApiConfig-NGYiUWXu.mjs → routeIslandApiConfig-DzN1W0sq.mjs} +1 -1
  135. package/dist/client/{scrollToBlock-0-C3DVhK.mjs → scrollToBlock-CWs8EIdo.mjs} +1 -1
  136. package/dist/client/{shop-BXjXhM9P.mjs → shop-Dufw7N7o.mjs} +7 -7
  137. package/dist/client/{shop-commerce-BZ6UlWJ7.mjs → shop-commerce-gklw_hLF.mjs} +6 -6
  138. package/dist/client/{shop-commerce-store-DlSBXb3D.mjs → shop-commerce-store-Sf1eyTaZ.mjs} +1 -1
  139. package/dist/client/{shopShared-kkEOiLPA.mjs → shopShared-B5nyA41S.mjs} +4 -4
  140. package/dist/client/{useEventFilters-zvnnE1ro.mjs → useEventFilters-CLL829vy.mjs} +6 -6
  141. package/dist/preview-next/before-render.mjs +147 -77
  142. package/dist/preview-next/client/runtime.mjs +4 -4
  143. package/dist/preview-next/keepAlive.mjs +147 -77
  144. package/dist/preview-next/middleware.mjs +147 -77
  145. package/dist/{previewAuthStore-JA6-I_sL.mjs → previewAuthStore-BxAFlu-j.mjs} +147 -77
  146. package/dist/{sdk-runtime-DFJlQt3q.mjs → sdk-runtime-D0uZFev7.mjs} +2 -2
  147. package/dist/{separator-o8GHb1xh.mjs → separator-EvdCAn4V.mjs} +1 -1
  148. package/dist/server/components.mjs +1727 -991
  149. package/dist/server/config-validation.mjs +537 -97
  150. package/dist/server/config.mjs +537 -97
  151. package/dist/server/data.mjs +549 -98
  152. package/dist/server/index.mjs +159 -81
  153. package/dist/server/next.mjs +1460 -748
  154. package/dist/server/page-converter.mjs +384 -54
  155. package/dist/server/prebuild.mjs +1 -1
  156. package/dist/server/rendering/server.mjs +1712 -973
  157. package/dist/server/rendering.mjs +1748 -1001
  158. package/dist/server/routing.mjs +624 -131
  159. package/dist/server/server.mjs +554 -138
  160. package/dist/server/theme-bridge.mjs +1192 -27
  161. package/dist/server/theme.mjs +6 -4
  162. package/package.json +2 -1
@@ -1,19 +1,59 @@
1
1
  "use client";
2
2
  import { C as unsafeAsPassId, b as unsafeAsCustomerPassId, v as unsafeAsAppointmentResourceId, x as unsafeAsFormId, y as unsafeAsAppointmentServiceId } from "./colorStyles-CuJBiS-1.mjs";
3
- import { i as buildBookingPaymentSelectionViewModel, n as StepIndicator, r as usePolling, t as BookingConsentAgreement } from "./BookingConsentAgreement-DoAILrkN.mjs";
4
- import { $t as formatBookingInstant, At as resolveBookingSiteId, Bt as clearWorkflowFeedback, Ct as getBookingServiceSlidingScale, Dt as prepareBookingSubmission, Et as parseBookingCurrencyInputToCents, Ft as BOOKING_FORM_DATA_RESERVED_KEYS, Gt as reportWorkflowSuccess, It as isBookingRequirementConsentFieldId, Mt as toSelectedCollectionOptionKind, Nt as projectSelectedAppointmentConsentIntoForm, Ot as resolveAppointmentCheckoutVerificationState, Pt as resolveSelectedAppointmentConsent, St as getBookingRedirectStateFromLocation, Tt as isBookingServiceSlidingAmountInRange, Ut as okWith, Vt as err, Wt as reportWorkflowError, Yt as addLocalScheduleDays, _n as formatCurrencyAmount, an as resolveBookingTimeZoneId, bt as formatBookingCentsAsCurrencyInput, in as parseBookingLocalDate, jt as resolveDefaultBookingServiceId, kt as resolveBookingPaymentStep, t as defineIslandPropsDecoder, tn as formatLocalScheduleDate, vt as logger, wt as getBookingSuccessMessage, xt as formatBookingSubmissionPreparationError, yt as deriveBookingFormViewState, zt as assertNever } from "./propsDecoder-DPXtCZWP.mjs";
5
- import { n as useBlockApi, t as BlockApiProvider } from "./context-CDLCfDep.mjs";
3
+ import { i as buildBookingPaymentSelectionViewModel, n as StepIndicator, r as usePolling, t as BookingConsentAgreement } from "./BookingConsentAgreement-DlVOmpW4.mjs";
4
+ import { $t as formatBookingInstant, At as resolveBookingSiteId, Bt as clearWorkflowFeedback, Ct as getBookingServiceSlidingScale, Dt as prepareBookingSubmission, Et as parseBookingCurrencyInputToCents, Ft as BOOKING_FORM_DATA_RESERVED_KEYS, Gt as reportWorkflowSuccess, It as isBookingRequirementConsentFieldId, Mt as toSelectedCollectionOptionKind, Nt as projectSelectedAppointmentConsentIntoForm, Ot as resolveAppointmentCheckoutVerificationState, Pt as resolveSelectedAppointmentConsent, St as getBookingRedirectStateFromLocation, Tt as isBookingServiceSlidingAmountInRange, Ut as okWith, Vt as err, Wt as reportWorkflowError, Yt as addLocalScheduleDays, _n as formatCurrencyAmount, an as resolveBookingTimeZoneId, bt as formatBookingCentsAsCurrencyInput, in as parseBookingLocalDate, jt as resolveDefaultBookingServiceId, kt as resolveBookingPaymentStep, t as defineIslandPropsDecoder, tn as formatLocalScheduleDate, vt as logger, wt as getBookingSuccessMessage, xt as formatBookingSubmissionPreparationError, yt as deriveBookingFormViewState, zt as assertNever } from "./propsDecoder-C3TP_z4x.mjs";
5
+ import { n as useBlockApi, t as BlockApiProvider } from "./context-BFeY_Wbm.mjs";
6
6
  import { n as isSpamProtectionEnabled, r as AltchaWidget, t as getChallengeUrl } from "./src-B833rYRr.mjs";
7
- import { a as CheckIcon, i as StateIcon, n as useDiscountCodeUi, r as TerminalPanel, t as scrollToBlock } from "./scrollToBlock-0-C3DVhK.mjs";
8
- import { t as DiscountCodeDisclosure } from "./DiscountCodeDisclosure-X528hUTf.mjs";
9
- import { i as SpinnerNode, t as ThemedButton } from "./ThemedButton-BwNFVwi4.mjs";
7
+ import { a as CheckIcon, i as StateIcon, n as useDiscountCodeUi, r as TerminalPanel, t as scrollToBlock } from "./scrollToBlock-CWs8EIdo.mjs";
8
+ import { t as DiscountCodeDisclosure } from "./DiscountCodeDisclosure-T9q623PQ.mjs";
9
+ import { i as SpinnerNode, t as ThemedButton } from "./ThemedButton-LAJTNrS8.mjs";
10
10
  import { t as FeedbackMessage } from "./FeedbackMessage-CtLvvS_d.mjs";
11
11
  import { n as getFieldValidationControlProps, t as FieldValidationMessage } from "./FieldValidationMessage-AYanWFb-.mjs";
12
12
  import { t as buildCheckoutReturnUrls } from "./checkoutUrls-CW3-78uu.mjs";
13
13
  import { t as mountReactIsland } from "./reactMount-SY8AVRof.mjs";
14
- import { t as buildRouteIslandApiConfig } from "./routeIslandApiConfig-NGYiUWXu.mjs";
14
+ import { t as buildRouteIslandApiConfig } from "./routeIslandApiConfig-DzN1W0sq.mjs";
15
15
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
17
+ //#region ../core/src/appointment-service-categories.ts
18
+ /**
19
+ * Groups services under their categories.
20
+ *
21
+ * A service whose `categoryId` names no category in `categories` lands in
22
+ * Other. The composite foreign key keeps that from happening with stored data,
23
+ * so this is only the total, safe answer for a caller holding a stale list.
24
+ */
25
+ function layoutServicesByCategory(categories, services, options) {
26
+ if (categories.length === 0) return {
27
+ kind: "uncategorized-site",
28
+ services: [...services]
29
+ };
30
+ const buckets = new Map(categories.map((category) => [category.id, []]));
31
+ const other = [];
32
+ for (const service of services) {
33
+ const bucket = service.categoryId === null ? void 0 : buckets.get(service.categoryId);
34
+ if (bucket !== void 0) bucket.push(service);
35
+ else other.push(service);
36
+ }
37
+ const groups = [];
38
+ for (const category of categories) {
39
+ const groupServices = buckets.get(category.id) ?? [];
40
+ if (groupServices.length === 0 && !options.keepEmptyGroups) continue;
41
+ groups.push({
42
+ category,
43
+ services: groupServices
44
+ });
45
+ }
46
+ if (groups.length === 0 && !options.keepEmptyGroups) return {
47
+ kind: "uncategorized-site",
48
+ services: other
49
+ };
50
+ return {
51
+ kind: "categorized-site",
52
+ groups,
53
+ other
54
+ };
55
+ }
56
+ //#endregion
17
57
  //#region ../blocks/src/system/runtime/components/booking/SuccessMessage.tsx
18
58
  const SuccessMessage = ({ message, resourceName, className }) => {
19
59
  return /* @__PURE__ */ jsx(TerminalPanel, {
@@ -1897,6 +1937,7 @@ function useHeadlessBookingFlow(options) {
1897
1937
  data,
1898
1938
  services: {
1899
1939
  items: options.services,
1940
+ categories: readServiceCategories(options),
1900
1941
  selected: selectedService,
1901
1942
  defaultServiceId,
1902
1943
  requiresSelection: Boolean(options.requiresServiceSelection)
@@ -1961,6 +2002,10 @@ function useHeadlessBookingFlow(options) {
1961
2002
  actions
1962
2003
  };
1963
2004
  }
2005
+ const EMPTY_SERVICE_CATEGORIES = [];
2006
+ function readServiceCategories(options) {
2007
+ return options.serviceCategories ?? EMPTY_SERVICE_CATEGORIES;
2008
+ }
1964
2009
  function navigateToCheckout(checkoutUrl) {
1965
2010
  window.location.href = checkoutUrl;
1966
2011
  }
@@ -2182,10 +2227,97 @@ const TimeSlotSelector = ({ value, onChange, slots, isLoading, timezone, require
2182
2227
  });
2183
2228
  };
2184
2229
  //#endregion
2185
- //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
2230
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-choices.tsx
2186
2231
  function optionCardClassName(isSelected) {
2187
2232
  return `rb-option-card${isSelected ? " rb-option-card--selected" : ""}`;
2188
2233
  }
2234
+ function SelectedCheck() {
2235
+ return /* @__PURE__ */ jsx("div", {
2236
+ className: "rb-option-check",
2237
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
2238
+ });
2239
+ }
2240
+ /**
2241
+ * The service buttons. A site with no categories keeps its single grid; a
2242
+ * categorised site gets a heading per category, then "Other", above the same
2243
+ * grid (issue 1403). Empty categories are skipped, and there are no intros
2244
+ * here, to keep the step compact.
2245
+ */
2246
+ function DefaultServiceChoices({ booking }) {
2247
+ const layout = layoutServicesByCategory(booking.services.categories, booking.services.items.map((service) => ({
2248
+ service,
2249
+ categoryId: service.categoryId ?? null
2250
+ })), { keepEmptyGroups: false });
2251
+ switch (layout.kind) {
2252
+ case "uncategorized-site": return /* @__PURE__ */ jsxs("div", {
2253
+ className: "rb-reg-stack rb-reg-stack--sm",
2254
+ children: [/* @__PURE__ */ jsx("span", {
2255
+ className: "ab-subtitle",
2256
+ children: "Service"
2257
+ }), /* @__PURE__ */ jsx(ServiceChoiceGrid, {
2258
+ booking,
2259
+ choices: layout.services
2260
+ })]
2261
+ });
2262
+ case "categorized-site": return /* @__PURE__ */ jsx("div", {
2263
+ className: "rb-reg-stack",
2264
+ children: [...layout.groups.map((group) => ({
2265
+ key: group.category.id,
2266
+ heading: group.category.name,
2267
+ choices: group.services
2268
+ })), ...layout.other.length > 0 ? [{
2269
+ key: "other",
2270
+ heading: "Other",
2271
+ choices: layout.other
2272
+ }] : []].map((group) => /* @__PURE__ */ jsxs("div", {
2273
+ className: "rb-reg-stack rb-reg-stack--sm",
2274
+ "data-booking-service-group": group.key,
2275
+ children: [/* @__PURE__ */ jsx("h3", {
2276
+ className: "ab-subtitle",
2277
+ children: group.heading
2278
+ }), /* @__PURE__ */ jsx(ServiceChoiceGrid, {
2279
+ booking,
2280
+ choices: group.choices
2281
+ })]
2282
+ }, group.key))
2283
+ });
2284
+ default: return assertNever(layout);
2285
+ }
2286
+ }
2287
+ function ServiceChoiceGrid({ booking, choices }) {
2288
+ const selectedServiceId = booking.services.selected?.id;
2289
+ return /* @__PURE__ */ jsx("div", {
2290
+ className: "rb-reg-grid",
2291
+ children: choices.map(({ service }) => {
2292
+ const isSelected = selectedServiceId === service.id;
2293
+ return /* @__PURE__ */ jsxs("button", {
2294
+ type: "button",
2295
+ onClick: () => booking.actions.selectService(service.id),
2296
+ className: optionCardClassName(isSelected),
2297
+ "aria-pressed": isSelected,
2298
+ children: [/* @__PURE__ */ jsxs("div", {
2299
+ className: "rb-flex-1",
2300
+ children: [
2301
+ /* @__PURE__ */ jsx("div", {
2302
+ className: "ab-subtitle",
2303
+ children: service.title
2304
+ }),
2305
+ service.description ? /* @__PURE__ */ jsx("div", {
2306
+ className: "ab-text rb-on-surface--muted",
2307
+ children: service.description
2308
+ }) : null,
2309
+ service.durationMinutes ? /* @__PURE__ */ jsxs("div", {
2310
+ className: "ab-text rb-on-surface--muted",
2311
+ children: [service.durationMinutes, " minutes"]
2312
+ }) : null
2313
+ ]
2314
+ }), isSelected ? /* @__PURE__ */ jsx(SelectedCheck, {}) : null]
2315
+ }, service.id);
2316
+ })
2317
+ });
2318
+ }
2319
+ //#endregion
2320
+ //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
2189
2321
  function formatBookingAmount(amount, currency) {
2190
2322
  return formatCurrencyAmount(amount, currency, { decimals: "auto" });
2191
2323
  }
@@ -2276,7 +2408,6 @@ function DefaultBookingStepContent({ booking, errorSummaryId }) {
2276
2408
  function DefaultServiceSelectionStep({ booking }) {
2277
2409
  const services = booking.services.items;
2278
2410
  const resources = booking.resources.items;
2279
- const selectedServiceId = booking.services.selected?.id;
2280
2411
  const selectedResourceId = booking.resources.selectedResourceId;
2281
2412
  const showServiceSelect = booking.services.requiresSelection || services.length > 1;
2282
2413
  const showResourceSelect = booking.resources.requiresSelection || resources.length > 1;
@@ -2291,41 +2422,7 @@ function DefaultServiceSelectionStep({ booking }) {
2291
2422
  className: "ab-text rb-on-surface--muted",
2292
2423
  children: "Choose the service you'd like to book"
2293
2424
  })] }),
2294
- showServiceSelect ? /* @__PURE__ */ jsxs("div", {
2295
- className: "rb-reg-stack rb-reg-stack--sm",
2296
- children: [/* @__PURE__ */ jsx("span", {
2297
- className: "ab-subtitle",
2298
- children: "Service"
2299
- }), /* @__PURE__ */ jsx("div", {
2300
- className: "rb-reg-grid",
2301
- children: services.map((service) => {
2302
- const isSelected = selectedServiceId === service.id;
2303
- return /* @__PURE__ */ jsxs("button", {
2304
- type: "button",
2305
- onClick: () => booking.actions.selectService(service.id),
2306
- className: optionCardClassName(isSelected),
2307
- "aria-pressed": isSelected,
2308
- children: [/* @__PURE__ */ jsxs("div", {
2309
- className: "rb-flex-1",
2310
- children: [
2311
- /* @__PURE__ */ jsx("div", {
2312
- className: "ab-subtitle",
2313
- children: service.title
2314
- }),
2315
- service.description ? /* @__PURE__ */ jsx("div", {
2316
- className: "ab-text rb-on-surface--muted",
2317
- children: service.description
2318
- }) : null,
2319
- service.durationMinutes ? /* @__PURE__ */ jsxs("div", {
2320
- className: "ab-text rb-on-surface--muted",
2321
- children: [service.durationMinutes, " minutes"]
2322
- }) : null
2323
- ]
2324
- }), isSelected ? /* @__PURE__ */ jsx(SelectedCheck, {}) : null]
2325
- }, service.id);
2326
- })
2327
- })]
2328
- }) : null,
2425
+ showServiceSelect ? /* @__PURE__ */ jsx(DefaultServiceChoices, { booking }) : null,
2329
2426
  booking.resources.state.tag === "loading" ? /* @__PURE__ */ jsxs("div", {
2330
2427
  className: "ab-center",
2331
2428
  children: [/* @__PURE__ */ jsx(SpinnerNode, {
@@ -2904,12 +3001,6 @@ function DefaultBookingErrorSummary({ errors, id }) {
2904
3001
  children: [errors.formError ? /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: errors.formError }) }) : null, Object.entries(errors.fieldErrors).map(([key, message]) => /* @__PURE__ */ jsx("p", { children: message }, key))]
2905
3002
  });
2906
3003
  }
2907
- function SelectedCheck() {
2908
- return /* @__PURE__ */ jsx("div", {
2909
- className: "rb-option-check",
2910
- children: /* @__PURE__ */ jsx(CheckIcon, {})
2911
- });
2912
- }
2913
3004
  //#endregion
2914
3005
  //#region ../blocks/src/system/runtime/nodes/booking-form.client.tsx
2915
3006
  const PORTAL_REBOOK_QUERY_PARAM = "rebook";
@@ -2962,6 +3053,7 @@ const BookingFormClient = ({ siteId: siteIdProp, render }) => {
2962
3053
  formId: render.hydration.formId ?? form?.id ?? "booking-form",
2963
3054
  form,
2964
3055
  services,
3056
+ serviceCategories: render.hydration.serviceCategories,
2965
3057
  initialServiceId,
2966
3058
  initialResourceId,
2967
3059
  initialData: render.hydration.initialData ?? void 0,
@@ -2786,7 +2786,7 @@ function parseEventPageSurfaceDestinationMode(value) {
2786
2786
  return value === "cms-managed" || value === "system-managed" ? value : void 0;
2787
2787
  }
2788
2788
  function parseEventPageSurfaceSubject(value) {
2789
- if (!isRecord$6(value)) return err$1({ kind: "notObject" });
2789
+ if (!isRecord$7(value)) return err$1({ kind: "notObject" });
2790
2790
  switch (value.kind) {
2791
2791
  case "eventSeries": {
2792
2792
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
@@ -2841,7 +2841,7 @@ function parseEventSeriesIdList(values) {
2841
2841
  }
2842
2842
  return ok$1(parsed);
2843
2843
  }
2844
- function isRecord$6(value) {
2844
+ function isRecord$7(value) {
2845
2845
  return typeof value === "object" && value !== null && !Array.isArray(value);
2846
2846
  }
2847
2847
  //#endregion
@@ -3122,6 +3122,46 @@ function addLocalScheduleDays(date, days) {
3122
3122
  day: utcDate.getUTCDate()
3123
3123
  };
3124
3124
  }
3125
+ //#endregion
3126
+ //#region ../core/src/appointment-service-categories.ts
3127
+ /**
3128
+ * Groups services under their categories.
3129
+ *
3130
+ * A service whose `categoryId` names no category in `categories` lands in
3131
+ * Other. The composite foreign key keeps that from happening with stored data,
3132
+ * so this is only the total, safe answer for a caller holding a stale list.
3133
+ */
3134
+ function layoutServicesByCategory(categories, services, options) {
3135
+ if (categories.length === 0) return {
3136
+ kind: "uncategorized-site",
3137
+ services: [...services]
3138
+ };
3139
+ const buckets = new Map(categories.map((category) => [category.id, []]));
3140
+ const other = [];
3141
+ for (const service of services) {
3142
+ const bucket = service.categoryId === null ? void 0 : buckets.get(service.categoryId);
3143
+ if (bucket !== void 0) bucket.push(service);
3144
+ else other.push(service);
3145
+ }
3146
+ const groups = [];
3147
+ for (const category of categories) {
3148
+ const groupServices = buckets.get(category.id) ?? [];
3149
+ if (groupServices.length === 0 && !options.keepEmptyGroups) continue;
3150
+ groups.push({
3151
+ category,
3152
+ services: groupServices
3153
+ });
3154
+ }
3155
+ if (groups.length === 0 && !options.keepEmptyGroups) return {
3156
+ kind: "uncategorized-site",
3157
+ services: other
3158
+ };
3159
+ return {
3160
+ kind: "categorized-site",
3161
+ groups,
3162
+ other
3163
+ };
3164
+ }
3125
3165
  function sessionStartMs(session) {
3126
3166
  return Date.parse(session.startsAt);
3127
3167
  }
@@ -6777,16 +6817,18 @@ const themeV2RuntimeStyleFieldNames = [
6777
6817
  "_frameStyles",
6778
6818
  "_sectionStyles",
6779
6819
  "_containerStyles",
6780
- "_cardStyles",
6781
- "_transitionStyles",
6782
- "styleGroup"
6820
+ "_cardStyles"
6783
6821
  ];
6784
6822
  const themeV2BlockSpecificRawStyleFieldNames = [
6785
6823
  "_contentCardStyles",
6786
6824
  "_heroLayoutStyles",
6787
6825
  "_textCardStyles"
6788
6826
  ];
6789
- const themeV2NormalEditorRawStyleFieldNames = [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
6827
+ const themeV2NormalEditorRawStyleFieldNames = [
6828
+ ...themeV2RuntimeStyleFieldNames,
6829
+ ...themeV2BlockSpecificRawStyleFieldNames,
6830
+ "styleGroup"
6831
+ ];
6790
6832
  new Set(themeV2NormalEditorRawStyleFieldNames);
6791
6833
  [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
6792
6834
  const pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
@@ -6975,7 +7017,7 @@ const boxBackgroundSchema = z.discriminatedUnion("type", [
6975
7017
  })
6976
7018
  ]);
6977
7019
  function normalizeAuthoringOpacity(value) {
6978
- if (!isRecord$5(value)) return value;
7020
+ if (!isRecord$6(value)) return value;
6979
7021
  return {
6980
7022
  ...value,
6981
7023
  opacity: normalizeOpacity(value.opacity)
@@ -6984,7 +7026,7 @@ function normalizeAuthoringOpacity(value) {
6984
7026
  function normalizeOpacity(value) {
6985
7027
  return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
6986
7028
  }
6987
- function isRecord$5(value) {
7029
+ function isRecord$6(value) {
6988
7030
  return typeof value === "object" && value !== null && !Array.isArray(value);
6989
7031
  }
6990
7032
  //#endregion
@@ -8666,7 +8708,7 @@ var BlockApiError = class extends Error {
8666
8708
  this.name = "BlockApiError";
8667
8709
  }
8668
8710
  };
8669
- function isRecord$4(value) {
8711
+ function isRecord$5(value) {
8670
8712
  return typeof value === "object" && value !== null;
8671
8713
  }
8672
8714
  function isJsonResponseContentType(contentType) {
@@ -8690,15 +8732,15 @@ async function parseJsonBody(response) {
8690
8732
  }
8691
8733
  }
8692
8734
  function isFieldError(value) {
8693
- return isRecord$4(value) && !Array.isArray(value) && typeof value.field === "string" && typeof value.code === "string" && typeof value.message === "string";
8735
+ return isRecord$5(value) && !Array.isArray(value) && typeof value.field === "string" && typeof value.code === "string" && typeof value.message === "string";
8694
8736
  }
8695
8737
  function fieldErrorsFromUnknown(value) {
8696
8738
  if (!Array.isArray(value)) return void 0;
8697
8739
  return value.every(isFieldError) ? value : void 0;
8698
8740
  }
8699
8741
  function getBlockApiErrorDetails(data) {
8700
- if (!isRecord$4(data)) return {};
8701
- const nestedError = isRecord$4(data.error) ? data.error : void 0;
8742
+ if (!isRecord$5(data)) return {};
8743
+ const nestedError = isRecord$5(data.error) ? data.error : void 0;
8702
8744
  const message = typeof nestedError?.message === "string" ? nestedError.message : typeof data.message === "string" ? data.message : void 0;
8703
8745
  const code = typeof nestedError?.code === "string" ? nestedError.code : typeof data.code === "string" ? data.code : void 0;
8704
8746
  const fieldErrors = fieldErrorsFromUnknown(nestedError?.fieldErrors) ?? fieldErrorsFromUnknown(data.fieldErrors);
@@ -9669,12 +9711,12 @@ function resolveBookingSiteId(siteId, form) {
9669
9711
  return siteId || form?.siteId || "";
9670
9712
  }
9671
9713
  function normalizeBookingFormConfig(form) {
9672
- if (!isRecord$3(form)) return null;
9714
+ if (!isRecord$4(form)) return null;
9673
9715
  const id = readNonEmptyString(form.id);
9674
9716
  const name = readNonEmptyString(form.name);
9675
9717
  if (!id || !name) return null;
9676
- const schemaSource = isRecord$3(form.schema) ? form.schema : isRecord$3(form.schemaJson) ? form.schemaJson : null;
9677
- const settingsSource = isRecord$3(form.settings) ? form.settings : isRecord$3(form.settingsJson) ? form.settingsJson : null;
9718
+ const schemaSource = isRecord$4(form.schema) ? form.schema : isRecord$4(form.schemaJson) ? form.schemaJson : null;
9719
+ const settingsSource = isRecord$4(form.settings) ? form.settings : isRecord$4(form.settingsJson) ? form.settingsJson : null;
9678
9720
  return {
9679
9721
  id,
9680
9722
  name,
@@ -9694,8 +9736,8 @@ function normalizeBookingServices(services) {
9694
9736
  };
9695
9737
  if (!services) return [];
9696
9738
  if (Array.isArray(services)) return services.filter(isBookingFormService).map(normalizeService);
9697
- if (isRecord$3(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
9698
- return isRecord$3(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
9739
+ if (isRecord$4(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
9740
+ return isRecord$4(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
9699
9741
  }
9700
9742
  function normalizeBookingSchema(value) {
9701
9743
  if (!value) return void 0;
@@ -9706,7 +9748,7 @@ function normalizeBookingSchema(value) {
9706
9748
  };
9707
9749
  }
9708
9750
  function readBookingFormField(value) {
9709
- if (!isRecord$3(value)) return null;
9751
+ if (!isRecord$4(value)) return null;
9710
9752
  const id = readNonEmptyString(value.id);
9711
9753
  const label = readNonEmptyString(value.label);
9712
9754
  const type = readBookingFieldType(value.type);
@@ -9749,7 +9791,7 @@ function readBookingFieldOptions(value) {
9749
9791
  return options.length > 0 ? options : void 0;
9750
9792
  }
9751
9793
  function readBookingFieldOption(value) {
9752
- if (!isRecord$3(value)) return null;
9794
+ if (!isRecord$4(value)) return null;
9753
9795
  const optionValue = readNonEmptyString(value.value);
9754
9796
  const label = readNonEmptyString(value.label);
9755
9797
  return optionValue && label ? {
@@ -9769,7 +9811,7 @@ function normalizeBookingSettings(value) {
9769
9811
  };
9770
9812
  }
9771
9813
  function isBookingFormService(value) {
9772
- return isRecord$3(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
9814
+ return isRecord$4(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
9773
9815
  }
9774
9816
  function collectBookingCustomFields(data) {
9775
9817
  const customFields = {};
@@ -9819,7 +9861,7 @@ function readStringArray(value) {
9819
9861
  const strings = value.filter((item) => typeof item === "string");
9820
9862
  return strings.length > 0 ? strings : void 0;
9821
9863
  }
9822
- function isRecord$3(value) {
9864
+ function isRecord$4(value) {
9823
9865
  return typeof value === "object" && value !== null;
9824
9866
  }
9825
9867
  function getBookingServicePaymentSelectionMode(service) {
@@ -11078,6 +11120,38 @@ function formatScheduleDate(isoString, locale) {
11078
11120
  return new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(date);
11079
11121
  }
11080
11122
  //#endregion
11123
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-categories.ts
11124
+ function isRecord$3(value) {
11125
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11126
+ }
11127
+ function readCategory(value) {
11128
+ if (!isRecord$3(value)) return [];
11129
+ const { id, name, description } = value;
11130
+ if (typeof id !== "string" || id.trim() === "" || typeof name !== "string") return [];
11131
+ return [{
11132
+ id,
11133
+ name,
11134
+ description: typeof description === "string" ? description : null
11135
+ }];
11136
+ }
11137
+ /**
11138
+ * The ordered service categories carried by a catalogue-shaped services
11139
+ * source (`{ services, categories }`). Every other shape has none, which
11140
+ * reads as a site without categories. Order is preserved: the database owns
11141
+ * it.
11142
+ */
11143
+ function normalizeBookingServiceCategories(services) {
11144
+ if (!isRecord$3(services) || !Array.isArray(services.categories)) return [];
11145
+ return services.categories.flatMap(readCategory);
11146
+ }
11147
+ /**
11148
+ * The ordered service categories a headless booking override was given in
11149
+ * its loader data (`data.services`).
11150
+ */
11151
+ function readOverrideServiceCategories(data) {
11152
+ return isRecord$3(data) ? normalizeBookingServiceCategories(data.services) : [];
11153
+ }
11154
+ //#endregion
11081
11155
  //#region ../blocks/src/system/runtime/nodes/booking-form.validationErrors.ts
11082
11156
  function omitRecordKey(record, key) {
11083
11157
  if (!(key in record)) return record;
@@ -11687,6 +11761,7 @@ function getAppointmentBookingOverrideInput(props) {
11687
11761
  formId,
11688
11762
  form,
11689
11763
  services,
11764
+ serviceCategories: readOverrideServiceCategories(props.data),
11690
11765
  initialServiceId: defaultServiceId,
11691
11766
  initialResourceId: toOptionalAppointmentResourceId(content.resourceId ?? form.settings?.resourceId),
11692
11767
  requiresServiceSelection: Boolean(content.requiresServiceSelection),
@@ -12400,6 +12475,7 @@ function useHeadlessBookingFlow(options) {
12400
12475
  data,
12401
12476
  services: {
12402
12477
  items: options.services,
12478
+ categories: readServiceCategories(options),
12403
12479
  selected: selectedService,
12404
12480
  defaultServiceId,
12405
12481
  requiresSelection: Boolean(options.requiresServiceSelection)
@@ -12464,6 +12540,10 @@ function useHeadlessBookingFlow(options) {
12464
12540
  actions
12465
12541
  };
12466
12542
  }
12543
+ const EMPTY_SERVICE_CATEGORIES = [];
12544
+ function readServiceCategories(options) {
12545
+ return options.serviceCategories ?? EMPTY_SERVICE_CATEGORIES;
12546
+ }
12467
12547
  function navigateToCheckout(checkoutUrl) {
12468
12548
  window.location.href = checkoutUrl;
12469
12549
  }
@@ -14026,10 +14106,97 @@ function BookingConsentAgreement({ agreement, showTitle = true, title, titleId }
14026
14106
  })] });
14027
14107
  }
14028
14108
  //#endregion
14029
- //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
14109
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-choices.tsx
14030
14110
  function optionCardClassName(isSelected) {
14031
14111
  return `rb-option-card${isSelected ? " rb-option-card--selected" : ""}`;
14032
14112
  }
14113
+ function SelectedCheck() {
14114
+ return /* @__PURE__ */ jsx("div", {
14115
+ className: "rb-option-check",
14116
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
14117
+ });
14118
+ }
14119
+ /**
14120
+ * The service buttons. A site with no categories keeps its single grid; a
14121
+ * categorised site gets a heading per category, then "Other", above the same
14122
+ * grid (issue 1403). Empty categories are skipped, and there are no intros
14123
+ * here, to keep the step compact.
14124
+ */
14125
+ function DefaultServiceChoices({ booking }) {
14126
+ const layout = layoutServicesByCategory(booking.services.categories, booking.services.items.map((service) => ({
14127
+ service,
14128
+ categoryId: service.categoryId ?? null
14129
+ })), { keepEmptyGroups: false });
14130
+ switch (layout.kind) {
14131
+ case "uncategorized-site": return /* @__PURE__ */ jsxs("div", {
14132
+ className: "rb-reg-stack rb-reg-stack--sm",
14133
+ children: [/* @__PURE__ */ jsx("span", {
14134
+ className: "ab-subtitle",
14135
+ children: "Service"
14136
+ }), /* @__PURE__ */ jsx(ServiceChoiceGrid, {
14137
+ booking,
14138
+ choices: layout.services
14139
+ })]
14140
+ });
14141
+ case "categorized-site": return /* @__PURE__ */ jsx("div", {
14142
+ className: "rb-reg-stack",
14143
+ children: [...layout.groups.map((group) => ({
14144
+ key: group.category.id,
14145
+ heading: group.category.name,
14146
+ choices: group.services
14147
+ })), ...layout.other.length > 0 ? [{
14148
+ key: "other",
14149
+ heading: "Other",
14150
+ choices: layout.other
14151
+ }] : []].map((group) => /* @__PURE__ */ jsxs("div", {
14152
+ className: "rb-reg-stack rb-reg-stack--sm",
14153
+ "data-booking-service-group": group.key,
14154
+ children: [/* @__PURE__ */ jsx("h3", {
14155
+ className: "ab-subtitle",
14156
+ children: group.heading
14157
+ }), /* @__PURE__ */ jsx(ServiceChoiceGrid, {
14158
+ booking,
14159
+ choices: group.choices
14160
+ })]
14161
+ }, group.key))
14162
+ });
14163
+ default: return assertNever(layout);
14164
+ }
14165
+ }
14166
+ function ServiceChoiceGrid({ booking, choices }) {
14167
+ const selectedServiceId = booking.services.selected?.id;
14168
+ return /* @__PURE__ */ jsx("div", {
14169
+ className: "rb-reg-grid",
14170
+ children: choices.map(({ service }) => {
14171
+ const isSelected = selectedServiceId === service.id;
14172
+ return /* @__PURE__ */ jsxs("button", {
14173
+ type: "button",
14174
+ onClick: () => booking.actions.selectService(service.id),
14175
+ className: optionCardClassName(isSelected),
14176
+ "aria-pressed": isSelected,
14177
+ children: [/* @__PURE__ */ jsxs("div", {
14178
+ className: "rb-flex-1",
14179
+ children: [
14180
+ /* @__PURE__ */ jsx("div", {
14181
+ className: "ab-subtitle",
14182
+ children: service.title
14183
+ }),
14184
+ service.description ? /* @__PURE__ */ jsx("div", {
14185
+ className: "ab-text rb-on-surface--muted",
14186
+ children: service.description
14187
+ }) : null,
14188
+ service.durationMinutes ? /* @__PURE__ */ jsxs("div", {
14189
+ className: "ab-text rb-on-surface--muted",
14190
+ children: [service.durationMinutes, " minutes"]
14191
+ }) : null
14192
+ ]
14193
+ }), isSelected ? /* @__PURE__ */ jsx(SelectedCheck, {}) : null]
14194
+ }, service.id);
14195
+ })
14196
+ });
14197
+ }
14198
+ //#endregion
14199
+ //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
14033
14200
  function formatBookingAmount(amount, currency) {
14034
14201
  return formatCurrencyAmount(amount, currency, { decimals: "auto" });
14035
14202
  }
@@ -14120,7 +14287,6 @@ function DefaultBookingStepContent({ booking, errorSummaryId }) {
14120
14287
  function DefaultServiceSelectionStep({ booking }) {
14121
14288
  const services = booking.services.items;
14122
14289
  const resources = booking.resources.items;
14123
- const selectedServiceId = booking.services.selected?.id;
14124
14290
  const selectedResourceId = booking.resources.selectedResourceId;
14125
14291
  const showServiceSelect = booking.services.requiresSelection || services.length > 1;
14126
14292
  const showResourceSelect = booking.resources.requiresSelection || resources.length > 1;
@@ -14135,41 +14301,7 @@ function DefaultServiceSelectionStep({ booking }) {
14135
14301
  className: "ab-text rb-on-surface--muted",
14136
14302
  children: "Choose the service you'd like to book"
14137
14303
  })] }),
14138
- showServiceSelect ? /* @__PURE__ */ jsxs("div", {
14139
- className: "rb-reg-stack rb-reg-stack--sm",
14140
- children: [/* @__PURE__ */ jsx("span", {
14141
- className: "ab-subtitle",
14142
- children: "Service"
14143
- }), /* @__PURE__ */ jsx("div", {
14144
- className: "rb-reg-grid",
14145
- children: services.map((service) => {
14146
- const isSelected = selectedServiceId === service.id;
14147
- return /* @__PURE__ */ jsxs("button", {
14148
- type: "button",
14149
- onClick: () => booking.actions.selectService(service.id),
14150
- className: optionCardClassName(isSelected),
14151
- "aria-pressed": isSelected,
14152
- children: [/* @__PURE__ */ jsxs("div", {
14153
- className: "rb-flex-1",
14154
- children: [
14155
- /* @__PURE__ */ jsx("div", {
14156
- className: "ab-subtitle",
14157
- children: service.title
14158
- }),
14159
- service.description ? /* @__PURE__ */ jsx("div", {
14160
- className: "ab-text rb-on-surface--muted",
14161
- children: service.description
14162
- }) : null,
14163
- service.durationMinutes ? /* @__PURE__ */ jsxs("div", {
14164
- className: "ab-text rb-on-surface--muted",
14165
- children: [service.durationMinutes, " minutes"]
14166
- }) : null
14167
- ]
14168
- }), isSelected ? /* @__PURE__ */ jsx(SelectedCheck, {}) : null]
14169
- }, service.id);
14170
- })
14171
- })]
14172
- }) : null,
14304
+ showServiceSelect ? /* @__PURE__ */ jsx(DefaultServiceChoices, { booking }) : null,
14173
14305
  booking.resources.state.tag === "loading" ? /* @__PURE__ */ jsxs("div", {
14174
14306
  className: "ab-center",
14175
14307
  children: [/* @__PURE__ */ jsx(SpinnerNode, {
@@ -14748,12 +14880,6 @@ function DefaultBookingErrorSummary({ errors, id }) {
14748
14880
  children: [errors.formError ? /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: errors.formError }) }) : null, Object.entries(errors.fieldErrors).map(([key, message]) => /* @__PURE__ */ jsx("p", { children: message }, key))]
14749
14881
  });
14750
14882
  }
14751
- function SelectedCheck() {
14752
- return /* @__PURE__ */ jsx("div", {
14753
- className: "rb-option-check",
14754
- children: /* @__PURE__ */ jsx(CheckIcon, {})
14755
- });
14756
- }
14757
14883
  //#endregion
14758
14884
  //#region ../blocks/src/system/runtime/shared/utils/checkoutUrls.ts
14759
14885
  /**
@@ -14871,6 +14997,7 @@ const BookingFormClient$1 = ({ siteId: siteIdProp, render }) => {
14871
14997
  formId: render.hydration.formId ?? form?.id ?? "booking-form",
14872
14998
  form,
14873
14999
  services,
15000
+ serviceCategories: render.hydration.serviceCategories,
14874
15001
  initialServiceId,
14875
15002
  initialResourceId,
14876
15003
  initialData: render.hydration.initialData ?? void 0,