@riverbankcms/sdk 0.138.0 → 0.139.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 (208) hide show
  1. package/README.md +11 -15
  2. package/dist/{PreviewEditorSidebar-BVPur_CU.mjs → PreviewEditorSidebar-BDeenhi3.mjs} +4 -4
  3. package/dist/{PreviewEditorUI-DLhWX8oh.mjs → PreviewEditorUI-DSRhgjF5.mjs} +3 -3
  4. package/dist/{PreviewQueryProvider-BguJY1SW.mjs → PreviewQueryProvider-D2K8GLUG.mjs} +1 -1
  5. package/dist/{SdkPreviewModeRuntime-Tda6qw4z.mjs → SdkPreviewModeRuntime-CL7wSnYu.mjs} +47 -27
  6. package/dist/{SiteChromeCustomizeContext-BHFgMjde.mjs → SiteChromeCustomizeContext-BhDn-5ir.mjs} +7771 -5863
  7. package/dist/_dts/ai/src/contracts/proposals.d.ts +99 -330
  8. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +9 -30
  9. package/dist/_dts/api/src/billing.d.ts +10 -3
  10. package/dist/_dts/api/src/contentRuntime.d.ts +8 -1
  11. package/dist/_dts/api/src/endpoints.d.ts +182 -103
  12. package/dist/_dts/api/src/footerSettings.d.ts +2 -5
  13. package/dist/_dts/api/src/index.d.ts +11 -9
  14. package/dist/_dts/api/src/mailchimpIntegration.d.ts +2 -59
  15. package/dist/_dts/api/src/mailingListIntegration.d.ts +117 -0
  16. package/dist/_dts/api/src/navigation/locations.d.ts +1 -0
  17. package/dist/_dts/api/src/navigation.d.ts +1 -26
  18. package/dist/_dts/api/src/newsletterEndpointDefinitions.d.ts +109 -2
  19. package/dist/_dts/api/src/onboarding/surfaceData.d.ts +185 -0
  20. package/dist/_dts/api/src/privacyPolicy.d.ts +74 -2
  21. package/dist/_dts/api/src/publicBookingAvailabilityEndpointDefinitions.d.ts +6 -6
  22. package/dist/_dts/api/src/publicEventEndpointDefinitions.d.ts +1 -1
  23. package/dist/_dts/api/src/publicExperience/newsletterEndpoints.d.ts +2 -4
  24. package/dist/_dts/api/src/publicExperience/sharedTypes.d.ts +10 -24
  25. package/dist/_dts/api/src/publicReadEndpointDefinitions.d.ts +73 -0
  26. package/dist/_dts/api/src/schemas/site-api-keys.d.ts +1 -1
  27. package/dist/_dts/api/src/sdkContracts.d.ts +2 -10
  28. package/dist/_dts/api/src/sdkThemeWire.d.ts +2 -2
  29. package/dist/_dts/api/src/siteManagementEndpoints.d.ts +0 -29
  30. package/dist/_dts/api/src/siteOnboarding.d.ts +91 -228
  31. package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +1 -9
  32. package/dist/_dts/api/src/siteSettings.d.ts +20 -1
  33. package/dist/_dts/api/src/types.d.ts +5 -4
  34. package/dist/_dts/billing/src/membership/index.d.ts +14 -7
  35. package/dist/_dts/billing/src/plans/display.d.ts +15 -0
  36. package/dist/_dts/billing/src/plans/registry.d.ts +2 -0
  37. package/dist/_dts/billing/src/plans/standing.d.ts +19 -0
  38. package/dist/_dts/billing/src/plans/types.d.ts +1 -7
  39. package/dist/_dts/billing/src/subscriptions/evidence.d.ts +12 -0
  40. package/dist/_dts/billing/src/subscriptions/standingEvidence.d.ts +31 -0
  41. package/dist/_dts/billing/src/subscriptions/store.d.ts +23 -5
  42. package/dist/_dts/blocks/src/PageRenderer.d.ts +6 -15
  43. package/dist/_dts/blocks/src/client.d.ts +1 -1
  44. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +32 -16
  45. package/dist/_dts/blocks/src/pageRendererContracts.d.ts +4 -0
  46. package/dist/_dts/blocks/src/pageRenderingMode.d.ts +12 -6
  47. package/dist/_dts/blocks/src/system/blocks/appointment-services.d.ts +11 -0
  48. package/dist/_dts/blocks/src/system/blocks/contact-newsletter.d.ts +39 -66
  49. package/dist/_dts/blocks/src/system/blocks/newsletter-signup.d.ts +39 -114
  50. package/dist/_dts/blocks/src/system/blocks/offerings-preview/catalogue.d.ts +18 -0
  51. package/dist/_dts/blocks/src/system/blocks/offerings-preview/fields.d.ts +2 -0
  52. package/dist/_dts/blocks/src/system/blocks/offerings-preview/index.d.ts +4 -0
  53. package/dist/_dts/blocks/src/system/blocks/site-footer-runtime.d.ts +7 -13
  54. package/dist/_dts/blocks/src/system/constants/blockKinds.d.ts +1 -1
  55. package/dist/_dts/blocks/src/system/data/index.d.ts +2 -2
  56. package/dist/_dts/blocks/src/system/data/loaderEndpoints.d.ts +1 -1
  57. package/dist/_dts/blocks/src/system/data/publicLoaderCachePolicy.d.ts +2 -74
  58. package/dist/_dts/blocks/src/system/fragments/library/newsletterForm.d.ts +33 -100
  59. package/dist/_dts/blocks/src/system/runtime/api/types/newsletter.d.ts +5 -15
  60. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +48 -24
  61. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +24 -12
  62. package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.d.ts +3 -0
  63. package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarEvents.state.d.ts +1 -1
  64. package/dist/_dts/blocks/src/system/runtime/hooks/useCalendarToday.d.ts +22 -0
  65. package/dist/_dts/blocks/src/system/runtime/nodes/appointment-services.d.ts +51 -0
  66. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +2 -0
  67. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration.interactive.d.ts +0 -1
  68. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCombined.client.d.ts +28 -0
  69. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/bookability.d.ts +9 -0
  70. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/dates.d.ts +14 -0
  71. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/duration.d.ts +2 -0
  72. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/index.d.ts +4 -0
  73. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/location.d.ts +2 -0
  74. package/dist/_dts/blocks/src/system/runtime/nodes/events/domain/quickDates.d.ts +16 -0
  75. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/AgendaEventCard.d.ts +9 -1
  76. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/CalendarDayCell.d.ts +2 -0
  77. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/CalendarDayHeaders.d.ts +4 -0
  78. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/DayEventsModal.d.ts +2 -1
  79. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventCard.d.ts +5 -0
  80. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventModals.d.ts +2 -1
  81. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventQuickDates.d.ts +15 -0
  82. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekAgendaView.d.ts +7 -3
  83. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekTimetableView.d.ts +1 -0
  84. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/index.d.ts +2 -0
  85. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/timetableModel.d.ts +1 -0
  86. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/useHydrationReady.d.ts +6 -0
  87. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.client.state.d.ts +3 -0
  88. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.interactive.d.ts +56 -33
  89. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.submission.d.ts +3 -3
  90. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.types.d.ts +13 -31
  91. package/dist/_dts/blocks/src/system/runtime/nodes/offerings-preview-catalogue.d.ts +14 -0
  92. package/dist/_dts/blocks/src/system/runtime/nodes/offerings-preview-content.d.ts +23 -0
  93. package/dist/_dts/blocks/src/system/runtime/nodes/offerings-preview.d.ts +4 -2
  94. package/dist/_dts/blocks/src/system/transforms/registry/formatting.d.ts +5 -0
  95. package/dist/_dts/commerce/src/purchase/index.d.ts +1 -0
  96. package/dist/_dts/commerce/src/purchase/orderItemMetadata.d.ts +5 -12
  97. package/dist/_dts/commerce/src/purchase/stripeCommissionMetadata.d.ts +34 -0
  98. package/dist/_dts/content-editor/src/footerEngagementDraft.d.ts +6 -24
  99. package/dist/_dts/content-editor/src/useFooterComposerState.d.ts +1 -2
  100. package/dist/_dts/core/src/footer-engagement.d.ts +2 -6
  101. package/dist/_dts/core/src/index.d.ts +9 -1
  102. package/dist/_dts/core/src/mailing-list-identifiers.d.ts +21 -0
  103. package/dist/_dts/core/src/mailing-list-runtime.d.ts +42 -0
  104. package/dist/_dts/core/src/mailing-list-setup-parser.d.ts +14 -0
  105. package/dist/_dts/core/src/mailing-list-setup.d.ts +129 -0
  106. package/dist/_dts/core/src/mailing-list-submission.d.ts +244 -0
  107. package/dist/_dts/core/src/mailing-list-targeting-parser.d.ts +12 -0
  108. package/dist/_dts/core/src/mailing-list-targeting.d.ts +93 -0
  109. package/dist/_dts/core/src/navigation-locations.d.ts +28 -0
  110. package/dist/_dts/core/src/public-profile.d.ts +9 -1
  111. package/dist/_dts/core/src/public-read-cache-contract.d.ts +45 -0
  112. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +529 -373
  113. package/dist/_dts/editor-blocks/src/widgets/index.d.ts +1 -0
  114. package/dist/_dts/editor-blocks/src/widgets/mailing-list-targeting/MailingListTargetingField.d.ts +11 -0
  115. package/dist/_dts/editor-blocks/src/widgets/mailing-list-targeting/context.d.ts +13 -0
  116. package/dist/_dts/editor-blocks/src/widgets/mailing-list-targeting/index.d.ts +3 -0
  117. package/dist/_dts/editor-blocks/src/widgets/mailing-list-targeting/loaders.d.ts +6 -0
  118. package/dist/_dts/preview-next/src/client/widgets/widgetOverrides.d.ts +1 -0
  119. package/dist/_dts/sdk/src/client/publicAppointmentReadOperations.d.ts +12 -0
  120. package/dist/_dts/sdk/src/client/publicEventReadOperations.d.ts +2 -1
  121. package/dist/_dts/sdk/src/client/publicLoaderCacheAccess.d.ts +11 -4
  122. package/dist/_dts/sdk/src/config/content-types.d.ts +3 -13
  123. package/dist/_dts/sdk/src/config/content-validation.d.ts +5 -8
  124. package/dist/_dts/sdk/src/constants.d.ts +3 -3
  125. package/dist/_dts/sdk/src/contracts/index.d.ts +1 -1
  126. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +12 -31
  127. package/dist/_dts/sdk/src/navigation/index.d.ts +11 -29
  128. package/dist/_dts/sdk/src/next/index.d.ts +4 -4
  129. package/dist/_dts/sdk/src/prebuild/types.d.ts +1 -5
  130. package/dist/_dts/sdk/src/public-api/contracts.d.ts +33 -16
  131. package/dist/_dts/sdk/src/rendering/blocks-page-renderer.d.ts +3 -0
  132. package/dist/_dts/sdk/src/rendering/client.d.ts +3 -20
  133. package/dist/_dts/sdk/src/rendering/components/pageShared.d.ts +3 -0
  134. package/dist/_dts/sdk/src/rendering/helpers/loadContent.d.ts +4 -0
  135. package/dist/_dts/sdk/src/rendering/helpers/pageRuntimeSupport.d.ts +1 -0
  136. package/dist/_dts/sdk/src/rendering/index.d.ts +1 -1
  137. package/dist/_dts/sdk/src/types/block-kinds.d.ts +1 -1
  138. package/dist/_dts/sdk/src/version.d.ts +1 -1
  139. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  140. package/dist/_dts/theme-core/src/hosted-booking-appearance.d.ts +41 -0
  141. package/dist/_dts/theme-core/src/index.d.ts +1 -0
  142. package/dist/_dts/theme-core/src/runtime/buildThemeRuntime.d.ts +0 -1
  143. package/dist/_dts/theme-core/src/tokens/tokens.d.ts +7 -0
  144. package/dist/_dts/ui/src/badge.d.ts +1 -1
  145. package/dist/_dts/ui/src/callout.d.ts +1 -1
  146. package/dist/client/{BookingConsentAgreement-DQUik1Mx.mjs → BookingConsentAgreement-CZvTDtsK.mjs} +2 -2
  147. package/dist/client/{DiscountCodeDisclosure-CU67Q4vH.mjs → DiscountCodeDisclosure-D-DsLCMU.mjs} +1 -1
  148. package/dist/client/{EventCombined.client-a02TXUgg.mjs → EventCombined.client-dWqgd5ZO.mjs} +443 -199
  149. package/dist/client/{EventListing.client-99qx7jFl.mjs → EventListing.client-BMRa9IYp.mjs} +2 -2
  150. package/dist/client/{LeafletStaticMap-CWzcq73L.mjs → LeafletStaticMap-D21W9kBZ.mjs} +1 -1
  151. package/dist/client/{accordion-D1nb3-Oj.mjs → accordion-FxF_ta47.mjs} +1 -1
  152. package/dist/client/{bookingForm-BGrmLhaO.mjs → bookingForm-YnwW4TGw.mjs} +5 -5
  153. package/dist/client/bookings.mjs +43 -2
  154. package/dist/client/{carousel-DSmOx3ad.mjs → carousel-vng7fFT5.mjs} +1 -1
  155. package/dist/client/{cart-COWeOIUk.mjs → cart-DEPDsjNk.mjs} +3 -3
  156. package/dist/client/{checkout-CeysaIJK.mjs → checkout-CZRWu9rW.mjs} +3 -3
  157. package/dist/client/client.mjs +3331 -1407
  158. package/dist/client/{context-DBzX85Xc.mjs → context-DvU_WUmd.mjs} +4 -3
  159. package/dist/client/{dates-DCYhZBlW.mjs → dates-wnPMiRl5.mjs} +21 -3
  160. package/dist/client/{eventCalendar-BgsN73Ul.mjs → eventCalendar-BA3Q5L15.mjs} +80 -78
  161. package/dist/client/{eventCombined-DcZQAMQP.mjs → eventCombined-51wgR-1R.mjs} +3 -3
  162. package/dist/client/{eventDetails-BWwGJ54l.mjs → eventDetails-modaetpm.mjs} +4 -4
  163. package/dist/client/{eventListing-DqYqV8vp.mjs → eventListing-D8t2J3Kv.mjs} +3 -3
  164. package/dist/client/{eventRegistration-DQGDgu_e.mjs → eventRegistration-CYqtkCE_.mjs} +49 -9
  165. package/dist/client/{form-ClfpasVT.mjs → form-CBKXN5In.mjs} +2 -2
  166. package/dist/client/{headerSection-BuOMe98N.mjs → headerSection-DylDqNrs.mjs} +2 -2
  167. package/dist/client/hooks.mjs +1259 -555
  168. package/dist/client/{locationMap-CEqu_sfa.mjs → locationMap-CvWrG0vZ.mjs} +2 -2
  169. package/dist/client/{newsletterForm-IbrHjMxy.mjs → newsletterForm-Btwk70zw.mjs} +117 -38
  170. package/dist/client/{pricing-xj7MPZlK.mjs → pricing-Uzw-GQ6Y.mjs} +1 -1
  171. package/dist/client/{productDetail-vxqTt5Xj.mjs → productDetail-JLcUYKnd.mjs} +3 -3
  172. package/dist/client/{productList-OTdZu8sb.mjs → productList-FM0cbUzj.mjs} +3 -3
  173. package/dist/client/{propsDecoder-CY9gGEF7.mjs → propsDecoder-DYzafZb2.mjs} +592 -152
  174. package/dist/client/rendering/client.mjs +2259 -835
  175. package/dist/client/rendering/islands.mjs +17 -17
  176. package/dist/client/rendering.mjs +3308 -1236
  177. package/dist/client/{scrollToBlock--BFV6kl-.mjs → scrollToBlock-CvXszexS.mjs} +1 -1
  178. package/dist/client/{shop-CP4YbDL9.mjs → shop-BIcjvy5a.mjs} +6 -6
  179. package/dist/client/{shop-commerce-APs4XBXE.mjs → shop-commerce-BZQsy7xw.mjs} +5 -5
  180. package/dist/client/{shop-commerce-store-CAVLxLFr.mjs → shop-commerce-store-D5V7XAb5.mjs} +1 -1
  181. package/dist/client/{shopShared-BnSRrwXI.mjs → shopShared-DowkXevB.mjs} +3 -3
  182. package/dist/client/{useEventFilters-1a1by4c8.mjs → useEventFilters-oMoTw_JE.mjs} +56 -9
  183. package/dist/preview-next/before-render.mjs +238 -111
  184. package/dist/preview-next/client/runtime.mjs +4 -4
  185. package/dist/preview-next/keepAlive.mjs +238 -111
  186. package/dist/preview-next/middleware.mjs +238 -111
  187. package/dist/{previewAuthStore-BkEgnYPP.mjs → previewAuthStore-COUyAKaX.mjs} +238 -111
  188. package/dist/{sdk-runtime-DwGfsT0S.mjs → sdk-runtime-D4k2LSGF.mjs} +2 -2
  189. package/dist/{separator-BeqDLneQ.mjs → separator-UGtpag4Z.mjs} +1 -1
  190. package/dist/server/components.mjs +2697 -1023
  191. package/dist/server/config-validation.mjs +2424 -1720
  192. package/dist/server/config.mjs +2429 -1724
  193. package/dist/server/data.mjs +1164 -454
  194. package/dist/server/index.mjs +364 -280
  195. package/dist/server/navigation.mjs +16 -47
  196. package/dist/server/next.mjs +2915 -1380
  197. package/dist/server/page-converter.mjs +969 -522
  198. package/dist/server/prebuild.mjs +2 -3
  199. package/dist/server/rendering/server.mjs +2704 -1030
  200. package/dist/server/rendering.mjs +2742 -1063
  201. package/dist/server/routing.mjs +1386 -595
  202. package/dist/server/server.mjs +2878 -2324
  203. package/dist/server/theme-bridge.mjs +292 -24
  204. package/dist/styles/index.css +289 -25
  205. package/package.json +1 -1
  206. package/dist/_dts/api/src/navigation/identity.d.ts +0 -59
  207. package/dist/_dts/blocks/src/system/blocks/newsletter-signup-destination.d.ts +0 -291
  208. package/dist/_dts/core/src/newsletter-signup-destination.d.ts +0 -38
@@ -1,6 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import "react/jsx-runtime";
3
3
  import "react";
4
+ //#region ../api/src/apiEndpointTypes.ts
5
+ function allowedDemoMutationPolicy() {
6
+ return {
7
+ classification: "allowed",
8
+ publicMessage: ""
9
+ };
10
+ }
4
11
  function blockedDemoMutationPolicy(classification, publicMessage) {
5
12
  if (publicMessage.trim() === "") throw new Error(`Demo mutation policy ${classification} requires publicMessage.`);
6
13
  return {
@@ -13,6 +20,7 @@ function blockedDemoMutationPolicy(classification, publicMessage) {
13
20
  const DEMO_MUTATION_BLOCKED_HIDDEN = blockedDemoMutationPolicy("blocked-hidden", "This action is not available in demo sites.");
14
21
  const DEMO_MUTATION_BLOCKED_DISABLED = blockedDemoMutationPolicy("blocked-disabled", "This setting is disabled in demo sites.");
15
22
  blockedDemoMutationPolicy("internal-only", "This internal operation is not available in demo sites.");
23
+ const DEMO_MUTATION_ALLOWED = allowedDemoMutationPolicy();
16
24
  //#endregion
17
25
  //#region ../api/src/cache-tags.ts
18
26
  /**
@@ -944,14 +952,117 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
944
952
  responseKind: "json",
945
953
  errors: ["auth:forbidden", "server:internal_error"]
946
954
  },
947
- getMailchimpSignupSyncHealth: {
948
- path: "/sites/{siteId}/integrations/mailchimp/signup-sync-health",
955
+ getMailingListSetup: {
956
+ path: "/sites/{siteId}/mailing-list/setup",
957
+ method: "GET",
958
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
959
+ auth: "user",
960
+ responseKind: "json",
961
+ errors: ["auth:forbidden", "server:internal_error"]
962
+ },
963
+ getMailingListAuthoringContext: {
964
+ path: "/sites/{siteId}/mailing-list/authoring-context",
965
+ method: "GET",
966
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
967
+ auth: "user",
968
+ responseKind: "json",
969
+ errors: ["auth:forbidden", "server:internal_error"]
970
+ },
971
+ previewMailingListSetupChange: {
972
+ path: "/sites/{siteId}/mailing-list/setup/preview",
973
+ method: "POST",
974
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
975
+ auth: "user",
976
+ responseKind: "json",
977
+ demoMutation: DEMO_MUTATION_ALLOWED,
978
+ errors: [
979
+ "auth:forbidden",
980
+ "validation:invalid_input",
981
+ "server:internal_error"
982
+ ]
983
+ },
984
+ applyMailingListSetupChange: {
985
+ path: "/sites/{siteId}/mailing-list/setup",
986
+ method: "PUT",
987
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
988
+ auth: "user",
989
+ responseKind: "json",
990
+ demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
991
+ errors: [
992
+ "auth:forbidden",
993
+ "validation:invalid_input",
994
+ "resource:conflict",
995
+ "server:internal_error"
996
+ ]
997
+ },
998
+ getMailingListProviderCatalog: {
999
+ path: "/sites/{siteId}/mailing-list/providers/{provider}/catalog",
1000
+ method: "GET",
1001
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1002
+ auth: "user",
1003
+ responseKind: "json",
1004
+ errors: [
1005
+ "auth:forbidden",
1006
+ "validation:invalid_input",
1007
+ "server:internal_error"
1008
+ ]
1009
+ },
1010
+ refreshMailingListProviderCatalog: {
1011
+ path: "/sites/{siteId}/mailing-list/providers/{provider}/catalog/refresh",
1012
+ method: "POST",
1013
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1014
+ auth: "user",
1015
+ responseKind: "json",
1016
+ demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
1017
+ errors: [
1018
+ "auth:forbidden",
1019
+ "validation:invalid_input",
1020
+ "server:internal_error"
1021
+ ]
1022
+ },
1023
+ getMailingListProviderHealth: {
1024
+ path: "/sites/{siteId}/mailing-list/health",
1025
+ method: "GET",
1026
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1027
+ auth: "user",
1028
+ responseKind: "json",
1029
+ errors: ["auth:forbidden", "server:internal_error"]
1030
+ },
1031
+ getBrevoConnection: {
1032
+ path: "/sites/{siteId}/integrations/brevo/connection",
949
1033
  method: "GET",
950
1034
  tags: [TAG_TEMPLATES.site],
951
1035
  auth: "user",
952
1036
  responseKind: "json",
953
1037
  errors: ["auth:forbidden", "server:internal_error"]
954
1038
  },
1039
+ connectBrevo: {
1040
+ path: "/sites/{siteId}/integrations/brevo/connection",
1041
+ method: "PUT",
1042
+ tags: [TAG_TEMPLATES.site],
1043
+ auth: "user",
1044
+ responseKind: "json",
1045
+ demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
1046
+ errors: [
1047
+ "auth:forbidden",
1048
+ "validation:invalid_input",
1049
+ "resource:conflict",
1050
+ "server:internal_error"
1051
+ ]
1052
+ },
1053
+ disconnectBrevo: {
1054
+ path: "/sites/{siteId}/integrations/brevo/disconnect",
1055
+ method: "POST",
1056
+ tags: [TAG_TEMPLATES.site],
1057
+ auth: "user",
1058
+ responseKind: "json",
1059
+ demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
1060
+ errors: [
1061
+ "auth:forbidden",
1062
+ "resource:conflict",
1063
+ "server:internal_error"
1064
+ ]
1065
+ },
955
1066
  getMailchimpConnection: {
956
1067
  path: "/sites/{siteId}/integrations/mailchimp/connection",
957
1068
  method: "GET",
@@ -1175,994 +1286,329 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1175
1286
  }
1176
1287
  });
1177
1288
  //#endregion
1178
- //#region ../api/src/publicEventEndpointDefinitions.ts
1179
- const PUBLIC_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1180
- listPublicEventCalendarWindow: {
1181
- path: "/public/sites/{siteId}/events/calendar-window",
1182
- method: "GET",
1183
- revalidate: 0,
1184
- auth: "public",
1185
- responseKind: "json"
1186
- },
1187
- resolvePublicEventCheckoutReturn: {
1188
- path: "/public/sites/{siteId}/events/checkout-return",
1189
- method: "POST",
1190
- tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
1191
- auth: "public",
1192
- responseKind: "json"
1193
- },
1194
- getPublicEventRegistrationOptions: {
1195
- path: "/public/sites/{siteId}/events/registration-options",
1196
- method: "POST",
1197
- tags: [TAG_TEMPLATES.publicEvents, "event-registration-options"],
1198
- auth: "public",
1199
- responseKind: "json",
1200
- errors: [
1201
- "validation:invalid_input",
1202
- "resource:not_found",
1203
- "resource:conflict",
1204
- "server:internal_error"
1205
- ]
1206
- },
1207
- resolveEventSubroute: {
1208
- path: "/public/sites/{siteId}/events/subroutes/resolve",
1209
- method: "GET",
1210
- revalidate: 60,
1211
- tags: [TAG_TEMPLATES.publicEvents, "event-subroute"],
1212
- auth: "public",
1213
- responseKind: "json",
1214
- errors: []
1215
- }
1216
- });
1289
+ //#region ../core/src/result.ts
1290
+ function ok(value) {
1291
+ return {
1292
+ ok: true,
1293
+ value
1294
+ };
1295
+ }
1296
+ function err(error) {
1297
+ return {
1298
+ ok: false,
1299
+ error
1300
+ };
1301
+ }
1217
1302
  //#endregion
1218
- //#region ../api/src/publicEventCheckoutEndpointDefinitions.ts
1303
+ //#region ../core/src/branded.ts
1304
+ const UUID_PATTERN$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1305
+ function parseUuidBrand(value, unsafeConstructor) {
1306
+ if (typeof value !== "string") return err({ kind: "not_string" });
1307
+ const normalized = value.trim().toLowerCase();
1308
+ if (normalized.length === 0) return err({ kind: "empty_string" });
1309
+ if (!UUID_PATTERN$1.test(normalized)) return err({ kind: "invalid_uuid" });
1310
+ return ok(unsafeConstructor(normalized));
1311
+ }
1312
+ const unsafeAsSiteId = (id) => id;
1313
+ const unsafeAsApiKeyId = (id) => id;
1314
+ const unsafeAsEventOfferingId = (id) => id;
1315
+ const unsafeAsEventSeriesId = (id) => id;
1316
+ const unsafeAsUserId = (id) => id;
1317
+ const parseSiteId = (value) => parseUuidBrand(value, unsafeAsSiteId);
1318
+ const parseApiKeyId = (value) => parseUuidBrand(value, unsafeAsApiKeyId);
1319
+ const parseEventOfferingId = (value) => parseUuidBrand(value, unsafeAsEventOfferingId);
1320
+ const parseUserId = (value) => parseUuidBrand(value, unsafeAsUserId);
1321
+ const parseEventSeriesId = (value) => parseUuidBrand(value, unsafeAsEventSeriesId);
1322
+ //#endregion
1323
+ //#region ../core/src/assert-never.ts
1324
+ function assertNever$2(value, message = "Unexpected variant") {
1325
+ throw new Error(`${message}: ${String(value)}`);
1326
+ }
1327
+ //#endregion
1328
+ //#region ../core/src/currency-format.ts
1329
+ const CURRENCY_LOCALE_BY_CODE = {
1330
+ USD: "en-US",
1331
+ GBP: "en-GB",
1332
+ EUR: "de-DE",
1333
+ AUD: "en-AU",
1334
+ CAD: "en-CA",
1335
+ NZD: "en-NZ",
1336
+ JPY: "ja-JP",
1337
+ CNY: "zh-CN",
1338
+ CHF: "de-CH"
1339
+ };
1340
+ function resolveCurrencyLocale(currencyCode, override) {
1341
+ return override ?? CURRENCY_LOCALE_BY_CODE[currencyCode] ?? "en";
1342
+ }
1219
1343
  /**
1220
- * The public event registration/checkout endpoints, extracted from
1221
- * `endpoints.ts`.
1344
+ * The single currency-amount formatter for the product: minor units in,
1345
+ * localized string out.
1222
1346
  *
1223
- * They were moved out for the same reason the `sdkMedia*` block was: adding the
1224
- * `errors` metadata `require-api-endpoint-errors` asks of a newly-added endpoint
1225
- * pushed `endpoints.ts` past its `max-lines` baseline. The block is spread back
1226
- * at the exact position it occupied, so `API_ENDPOINTS` key order is unchanged.
1227
- * (The endpoint that originally motivated the move, `checkPublicEventEligibility`,
1228
- * was since deleted on dev by #1326.)
1347
+ * The locale is derived from the currency by default, so output is deterministic
1348
+ * across server and client (no SSR/hydration mismatch). All other money
1349
+ * formatters in the codebase should delegate here rather than constructing their
1350
+ * own `Intl.NumberFormat`.
1229
1351
  */
1230
- const PUBLIC_EVENT_CHECKOUT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1231
- registerForEvent: {
1232
- path: "/public/sites/{siteId}/events/register",
1233
- method: "POST",
1234
- tags: [TAG_TEMPLATES.publicEvents, "event-registration"],
1235
- auth: "public",
1236
- responseKind: "json"
1237
- },
1238
- createPublicEventCheckout: {
1239
- path: "/public/sites/{siteId}/events/checkout",
1240
- method: "POST",
1241
- tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
1242
- auth: "public",
1243
- responseKind: "json"
1352
+ function formatCurrencyAmount(amountCents, currency, options) {
1353
+ const currencyCode = currency.toUpperCase();
1354
+ const locale = resolveCurrencyLocale(currencyCode, options?.locale);
1355
+ try {
1356
+ const exactFormatter = new Intl.NumberFormat(locale, {
1357
+ style: "currency",
1358
+ currency: currencyCode
1359
+ });
1360
+ const fractionDigits = options?.decimals === "auto" ? {
1361
+ minimumFractionDigits: 0,
1362
+ maximumFractionDigits: exactFormatter.resolvedOptions().maximumFractionDigits
1363
+ } : {};
1364
+ return new Intl.NumberFormat(locale, {
1365
+ style: "currency",
1366
+ currency: currencyCode,
1367
+ ...fractionDigits
1368
+ }).format(amountCents / 100);
1369
+ } catch {
1370
+ return `${(amountCents / 100).toFixed(2)} ${currencyCode}`;
1244
1371
  }
1245
- });
1372
+ }
1246
1373
  //#endregion
1247
- //#region ../api/src/publicBookingAvailabilityEndpointDefinitions.ts
1374
+ //#region ../core/src/planner.ts
1375
+ /** Default clock for production callers that do not inject time explicitly. */
1376
+ const systemPlanningClock = { now: () => Date.now() };
1377
+ /** Resolve current time as a Date from an explicit Date, injected clock, or system clock. */
1378
+ function resolvePlanningNowDate(input = {}) {
1379
+ if (input.now !== void 0) return input.now;
1380
+ return new Date((input.clock ?? systemPlanningClock).now());
1381
+ }
1382
+ //#endregion
1383
+ //#region ../core/src/event-pricing.ts
1384
+ /** Capacity closes booking, but does not make an otherwise eligible price unknown. */
1385
+ function isPublicEventPricingOptionDisplayable(option) {
1386
+ if (!option.isActive) return false;
1387
+ if (option.availability.kind === "available") return true;
1388
+ const reason = option.availability.reason;
1389
+ switch (reason) {
1390
+ case "sold_out":
1391
+ case "overallCapacityUnavailable": return true;
1392
+ case "inactive":
1393
+ case "not_on_sale_yet":
1394
+ case "sale_ended":
1395
+ case "attendance_mode_unavailable": return false;
1396
+ default: return assertNever$2(reason);
1397
+ }
1398
+ }
1399
+ function publicEventPricingDisplay(pricing) {
1400
+ switch (pricing.kind) {
1401
+ case "free":
1402
+ case "single":
1403
+ case "sliding_scale": return canonicalEventPricingDisplay(pricing);
1404
+ case "tiered": return resolvePricingOptionsPriceDisplay({
1405
+ options: pricing.options.filter(isPublicEventPricingOptionDisplayable),
1406
+ currency: pricing.currency,
1407
+ purpose: "configured"
1408
+ });
1409
+ case "external": return {
1410
+ kind: "external",
1411
+ label: pricing.label
1412
+ };
1413
+ }
1414
+ }
1415
+ function publicEventPricingType(pricing) {
1416
+ switch (pricing.kind) {
1417
+ case "free": return "free";
1418
+ case "single":
1419
+ case "tiered":
1420
+ case "sliding_scale": return isFreePublicEventPriceDisplay(publicEventPricingDisplay(pricing)) ? "free" : "paid";
1421
+ case "external": return "external";
1422
+ }
1423
+ }
1424
+ function isFreePublicEventPriceDisplay(display) {
1425
+ switch (display.kind) {
1426
+ case "free": return true;
1427
+ case "exact":
1428
+ case "from":
1429
+ case "range":
1430
+ case "mixed":
1431
+ case "unavailable":
1432
+ case "external": return false;
1433
+ }
1434
+ }
1435
+ function resolvePricingOptionSaleWindowState(option, now) {
1436
+ if (option.saleStartsAt !== null && option.saleStartsAt !== void 0 && new Date(option.saleStartsAt) > now) return "not_started";
1437
+ if (option.saleEndsAt !== null && option.saleEndsAt !== void 0 && new Date(option.saleEndsAt) < now) return "ended";
1438
+ return "open";
1439
+ }
1440
+ function resolvePricingOptionsPriceDisplay(input) {
1441
+ const now = input.purpose === "available_now" ? resolvePlanningNowDate({
1442
+ now: input.now,
1443
+ clock: input.clock
1444
+ }) : null;
1445
+ const amounts = input.options.filter((option) => option.isActive && (now === null || resolvePricingOptionSaleWindowState(option, now) === "open")).map((option) => option.priceAmount);
1446
+ return amounts.length === 0 ? {
1447
+ kind: "unavailable",
1448
+ currency: input.currency
1449
+ } : amountRangeDisplay(amounts, input.currency);
1450
+ }
1451
+ function amountRangeDisplay(amounts, currency) {
1452
+ const minAmount = Math.min(...amounts);
1453
+ const maxAmount = Math.max(...amounts);
1454
+ if (minAmount === maxAmount) return minAmount === 0 ? {
1455
+ kind: "free",
1456
+ currency
1457
+ } : {
1458
+ kind: "exact",
1459
+ amount: minAmount,
1460
+ currency
1461
+ };
1462
+ if (minAmount === 0) {
1463
+ const paidAmounts = amounts.filter((amount) => amount > 0);
1464
+ return {
1465
+ kind: "mixed",
1466
+ paidFromAmount: Math.min(...paidAmounts),
1467
+ paidToAmount: maxAmount,
1468
+ currency
1469
+ };
1470
+ }
1471
+ return {
1472
+ kind: "range",
1473
+ minAmount,
1474
+ maxAmount,
1475
+ currency
1476
+ };
1477
+ }
1478
+ /** Derives configured price presentation directly from canonical pricing. */
1479
+ function canonicalEventPricingDisplay(pricing) {
1480
+ switch (pricing.kind) {
1481
+ case "free": return {
1482
+ kind: "free",
1483
+ currency: pricing.currency
1484
+ };
1485
+ case "single": return {
1486
+ kind: "exact",
1487
+ amount: pricing.amount,
1488
+ currency: pricing.currency
1489
+ };
1490
+ case "sliding_scale": return pricing.minimum === pricing.maximum ? {
1491
+ kind: "exact",
1492
+ amount: pricing.maximum,
1493
+ currency: pricing.currency
1494
+ } : {
1495
+ kind: "range",
1496
+ minAmount: pricing.minimum,
1497
+ maxAmount: pricing.maximum,
1498
+ currency: pricing.currency
1499
+ };
1500
+ case "tiered": return resolvePricingOptionsPriceDisplay({
1501
+ options: pricing.options,
1502
+ currency: pricing.currency,
1503
+ purpose: "configured"
1504
+ });
1505
+ default: return assertNever$2(pricing);
1506
+ }
1507
+ }
1508
+ //#endregion
1509
+ //#region ../core/src/event-page-surface-subject.ts
1510
+ function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
1511
+ const mode = eventDestinationMode ? { eventDestinationMode } : {};
1512
+ return bookingScope === "whole_series" ? {
1513
+ kind: "eventSeries",
1514
+ eventSeriesId,
1515
+ bookingScope,
1516
+ isWholeSeriesBooking: true,
1517
+ ...mode
1518
+ } : {
1519
+ kind: "eventSeries",
1520
+ eventSeriesId,
1521
+ bookingScope,
1522
+ ...mode
1523
+ };
1524
+ }
1525
+ function createEventOfferingPageSurfaceSubject(eventOfferingId, input) {
1526
+ const mode = input.eventDestinationMode ? { eventDestinationMode: input.eventDestinationMode } : {};
1527
+ const identity = {
1528
+ kind: "eventOffering",
1529
+ eventOfferingId,
1530
+ catalogueEventSeriesIds: input.catalogueEventSeriesIds
1531
+ };
1532
+ return input.bookingScope === "whole_series" ? {
1533
+ ...identity,
1534
+ bookingScope: input.bookingScope,
1535
+ isWholeSeriesBooking: true,
1536
+ ...mode
1537
+ } : {
1538
+ ...identity,
1539
+ bookingScope: input.bookingScope,
1540
+ ...mode
1541
+ };
1542
+ }
1248
1543
  /**
1249
- * Public appointment reads: the service catalogue and the bookable slot grid.
1250
- *
1251
- * These are the reads that appointment writes invalidate. They carry canonical
1252
- * `riverbank:*` tags because the frontend revalidate endpoint discards anything
1253
- * else, which is why the legacy `site-{siteId}` tag they used to declare could
1254
- * never actually be cleared (issue 1364).
1544
+ * Read the destination mode off a wire value.
1255
1545
  *
1256
- * Slot availability depends on both tags: the grid moves when a booking lands,
1257
- * and again when the service definition behind it changes.
1546
+ * Absent or unrecognised reads as absent, which leaves the routed Event record
1547
+ * as the only authority — the behaviour every caller had before the field
1548
+ * existed.
1258
1549
  */
1259
- const PUBLIC_BOOKING_AVAILABILITY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1260
- getPublicBookingServices: {
1261
- path: "/public/bookings/services",
1262
- method: "GET",
1263
- revalidate: 60,
1264
- tags: [TAG_TEMPLATES.publicBookingServices],
1265
- auth: "public",
1266
- responseKind: "json"
1267
- },
1268
- getPublicBookingService: {
1269
- path: "/public/bookings/services/{serviceId}",
1270
- method: "GET",
1271
- revalidate: 60,
1272
- tags: [TAG_TEMPLATES.publicBookingServices],
1273
- auth: "public",
1274
- responseKind: "json"
1275
- },
1276
- getPublicBookingResourcesReference: {
1277
- path: "/public/bookings/resources/reference",
1278
- method: "GET",
1279
- revalidate: 60,
1280
- tags: [TAG_TEMPLATES.publicBookingServices],
1281
- auth: "public",
1282
- responseKind: "json"
1283
- },
1284
- getPublicBookingAvailableDates: {
1285
- path: "/public/bookings/availability/dates",
1286
- method: "GET",
1287
- revalidate: 60,
1288
- tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
1289
- auth: "public",
1290
- responseKind: "json"
1291
- },
1292
- getPublicBookingAvailableSlots: {
1293
- path: "/public/bookings/availability/slots",
1294
- method: "GET",
1295
- revalidate: 60,
1296
- tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
1297
- auth: "public",
1298
- responseKind: "json"
1299
- },
1300
- getPublicWeeklySlotAvailability: {
1301
- path: "/public/bookings/weekly-slot-availability",
1302
- method: "GET",
1303
- revalidate: 60,
1304
- tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
1305
- auth: "public",
1306
- responseKind: "json",
1307
- errors: []
1550
+ function parseEventPageSurfaceDestinationMode(value) {
1551
+ return value === "cms-managed" || value === "system-managed" ? value : void 0;
1552
+ }
1553
+ function parseEventPageSurfaceSubject(value) {
1554
+ if (!isRecord$10(value)) return err({ kind: "notObject" });
1555
+ switch (value.kind) {
1556
+ case "eventSeries": {
1557
+ const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
1558
+ if (!eventSeriesId.ok) return err({
1559
+ kind: "invalidEventSeriesId",
1560
+ error: eventSeriesId.error
1561
+ });
1562
+ if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") return err({
1563
+ kind: "invalidEventSeriesBookingScope",
1564
+ bookingScope: value.bookingScope
1565
+ });
1566
+ return ok(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope, parseEventPageSurfaceDestinationMode(value.eventDestinationMode)));
1567
+ }
1568
+ case "eventOffering": {
1569
+ const eventOfferingId = parseEventOfferingId(value.eventOfferingId);
1570
+ if (!eventOfferingId.ok) return err({
1571
+ kind: "invalidEventOfferingId",
1572
+ error: eventOfferingId.error
1573
+ });
1574
+ if (!Array.isArray(value.catalogueEventSeriesIds)) return err({
1575
+ kind: "invalidEventOfferingCatalogue",
1576
+ catalogue: value.catalogueEventSeriesIds
1577
+ });
1578
+ if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") return err({
1579
+ kind: "invalidEventOfferingBookingScope",
1580
+ bookingScope: value.bookingScope
1581
+ });
1582
+ const parsedCatalogueIds = parseEventSeriesIdList(value.catalogueEventSeriesIds);
1583
+ if (!parsedCatalogueIds.ok) return parsedCatalogueIds;
1584
+ const eventDestinationMode = parseEventPageSurfaceDestinationMode(value.eventDestinationMode);
1585
+ return ok(createEventOfferingPageSurfaceSubject(eventOfferingId.value, {
1586
+ catalogueEventSeriesIds: parsedCatalogueIds.value,
1587
+ bookingScope: value.bookingScope,
1588
+ ...eventDestinationMode ? { eventDestinationMode } : {}
1589
+ }));
1590
+ }
1591
+ default: return err({
1592
+ kind: "unsupportedSubject",
1593
+ subjectKind: value.kind
1594
+ });
1308
1595
  }
1309
- });
1310
- //#endregion
1311
- //#region ../api/src/siteApiKeyEndpointDefinitions.ts
1312
- const SITE_API_KEY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1313
- listSiteApiKeys: {
1314
- path: "/sites/{siteId}/api-keys",
1315
- method: "GET",
1316
- auth: "user",
1317
- tags: ["site-{siteId}", "api-keys"],
1318
- responseKind: "json"
1319
- },
1320
- createSiteApiKey: {
1321
- path: "/sites/{siteId}/api-keys",
1322
- method: "POST",
1323
- auth: "user",
1324
- tags: ["site-{siteId}", "api-keys"],
1325
- responseKind: "json"
1326
- },
1327
- revokeSiteApiKey: {
1328
- path: "/sites/{siteId}/api-keys/{keyId}",
1329
- method: "DELETE",
1330
- auth: "user",
1331
- tags: ["site-{siteId}", "api-keys"],
1332
- responseKind: "json"
1333
- },
1334
- listSitePreviewKeys: {
1335
- path: "/sites/{siteId}/api-keys/preview",
1336
- method: "GET",
1337
- auth: "user",
1338
- tags: [
1339
- "site-{siteId}",
1340
- "api-keys",
1341
- "preview-key"
1342
- ],
1343
- errors: [],
1344
- responseKind: "json"
1345
- },
1346
- createSitePreviewKey: {
1347
- path: "/sites/{siteId}/api-keys/preview",
1348
- method: "POST",
1349
- auth: "user",
1350
- tags: [
1351
- "site-{siteId}",
1352
- "api-keys",
1353
- "preview-key"
1354
- ],
1355
- responseKind: "json"
1356
- },
1357
- getSiteApiKeyAccessLogs: {
1358
- path: "/sites/{siteId}/api-keys/access-logs",
1359
- method: "GET",
1360
- auth: "user",
1361
- tags: [
1362
- "site-{siteId}",
1363
- "api-keys",
1364
- "access-logs"
1365
- ],
1366
- responseKind: "json"
1367
- },
1368
- listManagementKeys: {
1369
- path: "/sites/{siteId}/api-keys/management",
1370
- method: "GET",
1371
- auth: "user",
1372
- tags: [
1373
- "site-{siteId}",
1374
- "api-keys",
1375
- "management-keys"
1376
- ],
1377
- responseKind: "json"
1378
- },
1379
- createManagementKey: {
1380
- path: "/sites/{siteId}/api-keys/management",
1381
- method: "POST",
1382
- auth: "user",
1383
- tags: [
1384
- "site-{siteId}",
1385
- "api-keys",
1386
- "management-keys"
1387
- ],
1388
- responseKind: "json"
1389
- },
1390
- revokeManagementKey: {
1391
- path: "/sites/{siteId}/api-keys/management",
1392
- method: "DELETE",
1393
- auth: "user",
1394
- tags: [
1395
- "site-{siteId}",
1396
- "api-keys",
1397
- "management-keys"
1398
- ],
1399
- responseKind: "json"
1400
- }
1401
- });
1402
- //#endregion
1403
- //#region ../api/src/eventManagementEndpointDefinitions.ts
1404
- const EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1405
- listEvents: {
1406
- path: "/sites/{siteId}/bookings/events",
1407
- method: "GET",
1408
- tags: ["site-{siteId}", "event-series-{siteId}"],
1409
- auth: "user",
1410
- responseKind: "json",
1411
- errors: []
1412
- },
1413
- createEvent: {
1414
- path: "/sites/{siteId}/bookings/events",
1415
- method: "POST",
1416
- tags: ["site-{siteId}", "event-series-{siteId}"],
1417
- auth: "user",
1418
- responseKind: "json",
1419
- errors: [
1420
- "validation:invalid_input",
1421
- "resource:already_exists",
1422
- "server:internal_error"
1423
- ]
1424
- },
1425
- transitionEventLifecycle: {
1426
- path: "/sites/{siteId}/bookings/events/{eventId}/lifecycle",
1427
- method: "POST",
1428
- tags: ["site-{siteId}", "event-series-{siteId}"],
1429
- auth: "user",
1430
- responseKind: "json",
1431
- errors: [
1432
- "validation:invalid_input",
1433
- "resource:not_found",
1434
- "resource:conflict",
1435
- "server:internal_error"
1436
- ]
1437
- },
1438
- updateEventIdentity: {
1439
- path: "/sites/{siteId}/bookings/events/{eventId}",
1440
- method: "PATCH",
1441
- tags: ["site-{siteId}", "event-series-{siteId}"],
1442
- auth: "user",
1443
- responseKind: "json",
1444
- errors: [
1445
- "validation:invalid_input",
1446
- "resource:not_found",
1447
- "server:internal_error"
1448
- ]
1449
- },
1450
- createWholeSeriesRun: {
1451
- path: "/sites/{siteId}/bookings/events/{eventId}/runs",
1452
- method: "POST",
1453
- tags: ["site-{siteId}", "event-series-{siteId}"],
1454
- auth: "user",
1455
- responseKind: "json",
1456
- errors: [
1457
- "validation:invalid_input",
1458
- "resource:not_found",
1459
- "resource:already_exists",
1460
- "server:internal_error"
1461
- ]
1462
- },
1463
- updateWholeSeriesRun: {
1464
- path: "/sites/{siteId}/bookings/events/{eventId}/runs/{runId}",
1465
- method: "PATCH",
1466
- tags: ["site-{siteId}", "event-series-{siteId}"],
1467
- auth: "user",
1468
- responseKind: "json",
1469
- errors: [
1470
- "validation:invalid_input",
1471
- "resource:not_found",
1472
- "resource:conflict",
1473
- "server:internal_error"
1474
- ]
1475
- },
1476
- cancelWholeSeriesRun: {
1477
- path: "/sites/{siteId}/bookings/events/{eventId}/runs/{runId}/cancellation",
1478
- method: "POST",
1479
- tags: ["site-{siteId}", "event-series-{siteId}"],
1480
- auth: "user",
1481
- responseKind: "json",
1482
- errors: [
1483
- "validation:invalid_input",
1484
- "resource:not_found",
1485
- "resource:conflict",
1486
- "server:internal_error"
1487
- ]
1488
- }
1489
- });
1490
- //#endregion
1491
- //#region ../api/src/giftingEndpointDefinitions.ts
1492
- /**
1493
- * Gifting endpoints (gift cards & vouchers) — the dashboard surface that manages
1494
- * offers, gifts and settings, plus the public buy/claim surface.
1495
- */
1496
- const GIFTING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1497
- createGiftingOffer: {
1498
- path: "/sites/{siteId}/gifting/offers",
1499
- method: "POST",
1500
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1501
- auth: "user",
1502
- responseKind: "json",
1503
- errors: [
1504
- "validation:invalid_input",
1505
- "auth:forbidden",
1506
- "server:internal_error"
1507
- ]
1508
- },
1509
- updateGiftingOffer: {
1510
- path: "/sites/{siteId}/gifting/offers/{offerId}",
1511
- method: "PATCH",
1512
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1513
- auth: "user",
1514
- responseKind: "json",
1515
- errors: [
1516
- "validation:invalid_input",
1517
- "auth:forbidden",
1518
- "resource:not_found",
1519
- "resource:conflict",
1520
- "server:internal_error"
1521
- ]
1522
- },
1523
- deleteGiftingOffer: {
1524
- path: "/sites/{siteId}/gifting/offers/{offerId}",
1525
- method: "DELETE",
1526
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1527
- auth: "user",
1528
- responseKind: "json",
1529
- errors: [
1530
- "auth:forbidden",
1531
- "resource:not_found",
1532
- "resource:conflict",
1533
- "server:internal_error"
1534
- ]
1535
- },
1536
- duplicateGiftingOffer: {
1537
- path: "/sites/{siteId}/gifting/offers/{offerId}/duplicate",
1538
- method: "POST",
1539
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1540
- auth: "user",
1541
- responseKind: "json",
1542
- errors: [
1543
- "auth:forbidden",
1544
- "resource:not_found",
1545
- "server:internal_error"
1546
- ]
1547
- },
1548
- reorderGiftingOffers: {
1549
- path: "/sites/{siteId}/gifting/offers/order",
1550
- method: "PUT",
1551
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1552
- auth: "user",
1553
- responseKind: "json",
1554
- errors: [
1555
- "validation:invalid_input",
1556
- "auth:forbidden",
1557
- "resource:not_found",
1558
- "server:internal_error"
1559
- ]
1560
- },
1561
- sendGift: {
1562
- path: "/sites/{siteId}/gifting/gifts",
1563
- method: "POST",
1564
- tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
1565
- auth: "user",
1566
- demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
1567
- responseKind: "json",
1568
- errors: [
1569
- "validation:invalid_input",
1570
- "auth:forbidden",
1571
- "resource:not_found",
1572
- "server:internal_error"
1573
- ]
1574
- },
1575
- resendGift: {
1576
- path: "/sites/{siteId}/gifting/gifts/{giftId}/resend",
1577
- method: "POST",
1578
- tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
1579
- auth: "user",
1580
- demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
1581
- responseKind: "json",
1582
- errors: [
1583
- "auth:forbidden",
1584
- "resource:not_found",
1585
- "resource:conflict",
1586
- "server:internal_error"
1587
- ]
1588
- },
1589
- correctGiftEmailAndResend: {
1590
- path: "/sites/{siteId}/gifting/gifts/{giftId}/correct-email-and-resend",
1591
- method: "POST",
1592
- tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
1593
- auth: "user",
1594
- demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
1595
- responseKind: "json",
1596
- errors: [
1597
- "validation:invalid_input",
1598
- "auth:forbidden",
1599
- "resource:not_found",
1600
- "resource:conflict",
1601
- "server:internal_error"
1602
- ]
1603
- },
1604
- updateGiftingSettings: {
1605
- path: "/sites/{siteId}/gifting/settings",
1606
- method: "PATCH",
1607
- tags: [
1608
- "site-{siteId}",
1609
- "gifting-offers-{siteId}",
1610
- "gifting-gifts-{siteId}"
1611
- ],
1612
- auth: "user",
1613
- responseKind: "json",
1614
- errors: [
1615
- "validation:invalid_input",
1616
- "auth:forbidden",
1617
- "server:internal_error"
1618
- ]
1619
- },
1620
- listPublicGiftingOffers: {
1621
- path: "/public/sites/{siteId}/gifting/offers",
1622
- method: "GET",
1623
- revalidate: 300,
1624
- tags: [TAG_TEMPLATES.publicGifting],
1625
- errors: [],
1626
- auth: "public",
1627
- responseKind: "json"
1628
- },
1629
- createPublicGiftingCheckout: {
1630
- path: "/public/sites/{siteId}/gifting/checkout",
1631
- method: "POST",
1632
- tags: [TAG_TEMPLATES.publicGifting],
1633
- errors: [],
1634
- auth: "public",
1635
- responseKind: "json"
1636
- },
1637
- claimGiftCardPublic: {
1638
- path: "/public/sites/{siteId}/gifting/claim",
1639
- method: "POST",
1640
- tags: [TAG_TEMPLATES.publicGifting],
1641
- errors: [],
1642
- auth: "public",
1643
- responseKind: "json"
1644
- }
1645
- });
1646
- //#endregion
1647
- //#region ../api/src/sdkEndpointDefinitions.ts
1648
- /** SDK management endpoints that do not belong to a CRUD resource family. */
1649
- const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1650
- sdkUpdateSettings: {
1651
- path: "/sdk/{siteId}/settings",
1652
- method: "PATCH",
1653
- auth: "service",
1654
- responseKind: "json",
1655
- errors: ["validation:invalid_input", "server:internal_error"]
1656
- },
1657
- sdkConfigureBookingVertical: {
1658
- path: "/sdk/{siteId}/settings/booking/vertical",
1659
- method: "POST",
1660
- auth: "service",
1661
- responseKind: "json"
1662
- },
1663
- sdkSetupRevalidationWebhook: {
1664
- path: "/sdk/{siteId}/webhooks/revalidation/setup",
1665
- method: "POST",
1666
- auth: "service",
1667
- responseKind: "json",
1668
- errors: ["validation:invalid_input", "server:internal_error"]
1669
- }
1670
- });
1671
- //#endregion
1672
- //#region ../api/src/sdkMediaEndpointDefinitions.ts
1673
- /** SDK media lookup, listing, deletion and upload endpoints (management-key authenticated). */
1674
- const SDK_MEDIA_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1675
- sdkMediaExists: {
1676
- path: "/sdk/{siteId}/media/exists",
1677
- method: "POST",
1678
- errors: ["validation:invalid_input"],
1679
- auth: "service",
1680
- responseKind: "json"
1681
- },
1682
- sdkMediaFindByChecksum: {
1683
- path: "/sdk/{siteId}/media/find-by-checksum",
1684
- method: "POST",
1685
- errors: ["server:internal_error"],
1686
- auth: "service",
1687
- responseKind: "json"
1688
- },
1689
- sdkMediaResolve: {
1690
- path: "/sdk/{siteId}/media/resolve",
1691
- method: "POST",
1692
- errors: ["server:internal_error"],
1693
- auth: "service",
1694
- responseKind: "json"
1695
- },
1696
- sdkMediaList: {
1697
- path: "/sdk/{siteId}/media",
1698
- method: "GET",
1699
- auth: "service",
1700
- errors: [],
1701
- responseKind: "json"
1702
- },
1703
- sdkMediaDownload: {
1704
- path: "/sdk/{siteId}/media/{identifier}/download",
1705
- method: "GET",
1706
- auth: "service",
1707
- errors: [],
1708
- responseKind: "json"
1709
- },
1710
- sdkMediaDeletePreview: {
1711
- path: "/sdk/{siteId}/media/{identifier}/delete-preview",
1712
- method: "POST",
1713
- auth: "service",
1714
- errors: [],
1715
- responseKind: "json"
1716
- },
1717
- sdkMediaDeleteApply: {
1718
- path: "/sdk/{siteId}/media/{identifier}",
1719
- method: "DELETE",
1720
- auth: "service",
1721
- errors: [],
1722
- responseKind: "json"
1723
- },
1724
- sdkMediaUploadUrl: {
1725
- path: "/sdk/{siteId}/media/upload-url",
1726
- method: "POST",
1727
- errors: [
1728
- "resource:not_found",
1729
- "validation:invalid_input",
1730
- "resource:conflict",
1731
- "validation:payload_too_large",
1732
- "billing:plan_limit_exceeded",
1733
- "server:internal_error"
1734
- ],
1735
- auth: "service",
1736
- responseKind: "json"
1737
- },
1738
- sdkMediaUploadFinalize: {
1739
- path: "/sdk/{siteId}/media/upload",
1740
- method: "POST",
1741
- errors: [
1742
- "resource:not_found",
1743
- "validation:invalid_input",
1744
- "resource:conflict",
1745
- "validation:payload_too_large",
1746
- "billing:plan_limit_exceeded",
1747
- "server:internal_error"
1748
- ],
1749
- auth: "service",
1750
- responseKind: "json"
1751
- }
1752
- });
1753
- ({
1754
- ...ACCOUNT_ENDPOINT_DEFINITIONS,
1755
- ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
1756
- ...GIFTING_ENDPOINT_DEFINITIONS
1757
- }), TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, { ...SITE_API_KEY_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.site, TAG_TEMPLATES.siteBookingSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteBookingSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteEmailSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteEmailSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteEmailSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteEmailSettings, {
1758
- ...NEWSLETTER_ENDPOINT_DEFINITIONS,
1759
- ...APPOINTMENT_ENDPOINT_DEFINITIONS,
1760
- ...EVENT_SERIES_REGISTRATION_ENDPOINT_DEFINITIONS
1761
- }, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.workflowRunById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.routeById, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.formById, TAG_TEMPLATES.siteForms, { ...PUBLIC_BOOKING_AVAILABILITY_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEventFilters, TAG_TEMPLATES.publicTeamMembers, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicPasses, TAG_TEMPLATES.publicMemberships, TAG_TEMPLATES.publicProducts, TAG_TEMPLATES.publicShop, TAG_TEMPLATES.publicCheckout, TAG_TEMPLATES.siteRoutableContent, { ...PUBLIC_EVENT_CHECKOUT_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, { ...EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS }, { ...SDK_MEDIA_ENDPOINT_DEFINITIONS }, { ...SDK_ENDPOINT_DEFINITIONS }, { ...SDK_ENTRY_READ_ENDPOINTS };
1762
- z.object({ refreshToken: z.string().min(1).max(4096) }).strict();
1763
- z.object({
1764
- accessToken: z.string().min(1).max(4096),
1765
- refreshToken: z.string().min(1).max(4096)
1766
- }).strict();
1767
- const ACCOUNT_SITE_CURSOR_MAX_LENGTH = 2048;
1768
- const ACCOUNT_SITE_TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?(Z|[+-]\d{2}:\d{2})$/;
1769
- /** Strict, injection-safe PostgreSQL/RFC3339 timestamp text used in cursors. */
1770
- function isAccountSiteTimestamp(value) {
1771
- if (typeof value !== "string") return false;
1772
- const match = ACCOUNT_SITE_TIMESTAMP_PATTERN.exec(value);
1773
- if (!match) return false;
1774
- const [, year, month, day, hour, minute, second, , offset] = match;
1775
- const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
1776
- const offsetValue = offset ?? "";
1777
- const offsetIsValid = offsetValue === "Z" || (() => {
1778
- const offsetMatch = /^(?:[+-])(\d{2}):(\d{2})$/.exec(offsetValue);
1779
- if (!offsetMatch) return false;
1780
- return Number(offsetMatch[1]) <= 23 && Number(offsetMatch[2]) <= 59;
1781
- })();
1782
- return date.getUTCFullYear() === Number(year) && date.getUTCMonth() === Number(month) - 1 && date.getUTCDate() === Number(day) && Number(hour) <= 23 && Number(minute) <= 59 && Number(second) <= 59 && offsetIsValid;
1783
- }
1784
- const accountSiteTimestampSchema = z.string().refine(isAccountSiteTimestamp, { message: "Expected a strict account site timestamp" });
1785
- z.enum([
1786
- "managed",
1787
- "headless_cms",
1788
- "headless_full"
1789
- ]);
1790
- z.object({ title: z.string().trim().min(1).max(120) });
1791
- const abandonedStandardKeyIdsSchema = z.object({
1792
- live: z.string().uuid().optional(),
1793
- management: z.string().uuid().optional()
1794
- }).strict();
1795
- const abandonedPreviewKeyIdsSchema = z.object({
1796
- live: z.string().uuid().optional(),
1797
- management: z.string().uuid().optional(),
1798
- preview: z.string().uuid().optional()
1799
- }).strict();
1800
- z.discriminatedUnion("operation", [
1801
- z.object({
1802
- operation: z.literal("issue_standard"),
1803
- siteId: z.string().uuid(),
1804
- operationId: z.string().uuid(),
1805
- projectName: z.string().trim().min(1).max(120),
1806
- abandonedKeyIds: abandonedStandardKeyIdsSchema.default({})
1807
- }).strict(),
1808
- z.object({
1809
- operation: z.literal("issue_with_preview"),
1810
- siteId: z.string().uuid(),
1811
- operationId: z.string().uuid(),
1812
- projectName: z.string().trim().min(1).max(120),
1813
- abandonedKeyIds: abandonedPreviewKeyIdsSchema.default({})
1814
- }).strict(),
1815
- z.object({
1816
- operation: z.literal("revalidate_standard"),
1817
- siteId: z.string().uuid(),
1818
- operationId: z.string().uuid(),
1819
- projectName: z.string().trim().min(1).max(120),
1820
- keyIds: z.object({
1821
- live: z.string().uuid(),
1822
- management: z.string().uuid()
1823
- }).strict()
1824
- }).strict(),
1825
- z.object({
1826
- operation: z.literal("revalidate_with_preview"),
1827
- siteId: z.string().uuid(),
1828
- operationId: z.string().uuid(),
1829
- projectName: z.string().trim().min(1).max(120),
1830
- keyIds: z.object({
1831
- live: z.string().uuid(),
1832
- management: z.string().uuid(),
1833
- preview: z.string().uuid()
1834
- }).strict()
1835
- }).strict()
1836
- ]);
1837
- z.object({
1838
- cursor: z.string().min(1).max(ACCOUNT_SITE_CURSOR_MAX_LENGTH).optional(),
1839
- limit: z.coerce.number().int().min(1).max(100).default(50),
1840
- search: z.string().trim().min(1).max(120).optional()
1841
- });
1842
- //#endregion
1843
- //#region ../core/src/result.ts
1844
- function ok(value) {
1845
- return {
1846
- ok: true,
1847
- value
1848
- };
1849
- }
1850
- function err(error) {
1851
- return {
1852
- ok: false,
1853
- error
1854
- };
1855
- }
1856
- //#endregion
1857
- //#region ../core/src/branded.ts
1858
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1859
- function parseUuidBrand(value, unsafeConstructor) {
1860
- if (typeof value !== "string") return err({ kind: "not_string" });
1861
- const normalized = value.trim().toLowerCase();
1862
- if (normalized.length === 0) return err({ kind: "empty_string" });
1863
- if (!UUID_PATTERN.test(normalized)) return err({ kind: "invalid_uuid" });
1864
- return ok(unsafeConstructor(normalized));
1865
- }
1866
- const unsafeAsSiteId = (id) => id;
1867
- const unsafeAsApiKeyId = (id) => id;
1868
- const unsafeAsEventOfferingId = (id) => id;
1869
- const unsafeAsEventSeriesId = (id) => id;
1870
- const unsafeAsUserId = (id) => id;
1871
- const parseSiteId = (value) => parseUuidBrand(value, unsafeAsSiteId);
1872
- const parseApiKeyId = (value) => parseUuidBrand(value, unsafeAsApiKeyId);
1873
- const parseEventOfferingId = (value) => parseUuidBrand(value, unsafeAsEventOfferingId);
1874
- const parseUserId = (value) => parseUuidBrand(value, unsafeAsUserId);
1875
- const parseEventSeriesId = (value) => parseUuidBrand(value, unsafeAsEventSeriesId);
1876
- //#endregion
1877
- //#region ../core/src/assert-never.ts
1878
- function assertNever$2(value, message = "Unexpected variant") {
1879
- throw new Error(`${message}: ${String(value)}`);
1880
- }
1881
- //#endregion
1882
- //#region ../core/src/currency-format.ts
1883
- const CURRENCY_LOCALE_BY_CODE = {
1884
- USD: "en-US",
1885
- GBP: "en-GB",
1886
- EUR: "de-DE",
1887
- AUD: "en-AU",
1888
- CAD: "en-CA",
1889
- NZD: "en-NZ",
1890
- JPY: "ja-JP",
1891
- CNY: "zh-CN",
1892
- CHF: "de-CH"
1893
- };
1894
- function resolveCurrencyLocale(currencyCode, override) {
1895
- return override ?? CURRENCY_LOCALE_BY_CODE[currencyCode] ?? "en";
1896
- }
1897
- /**
1898
- * The single currency-amount formatter for the product: minor units in,
1899
- * localized string out.
1900
- *
1901
- * The locale is derived from the currency by default, so output is deterministic
1902
- * across server and client (no SSR/hydration mismatch). All other money
1903
- * formatters in the codebase should delegate here rather than constructing their
1904
- * own `Intl.NumberFormat`.
1905
- */
1906
- function formatCurrencyAmount(amountCents, currency, options) {
1907
- const currencyCode = currency.toUpperCase();
1908
- const locale = resolveCurrencyLocale(currencyCode, options?.locale);
1909
- try {
1910
- const exactFormatter = new Intl.NumberFormat(locale, {
1911
- style: "currency",
1912
- currency: currencyCode
1913
- });
1914
- const fractionDigits = options?.decimals === "auto" ? {
1915
- minimumFractionDigits: 0,
1916
- maximumFractionDigits: exactFormatter.resolvedOptions().maximumFractionDigits
1917
- } : {};
1918
- return new Intl.NumberFormat(locale, {
1919
- style: "currency",
1920
- currency: currencyCode,
1921
- ...fractionDigits
1922
- }).format(amountCents / 100);
1923
- } catch {
1924
- return `${(amountCents / 100).toFixed(2)} ${currencyCode}`;
1925
- }
1926
- }
1927
- //#endregion
1928
- //#region ../core/src/planner.ts
1929
- /** Default clock for production callers that do not inject time explicitly. */
1930
- const systemPlanningClock = { now: () => Date.now() };
1931
- /** Resolve current time as a Date from an explicit Date, injected clock, or system clock. */
1932
- function resolvePlanningNowDate(input = {}) {
1933
- if (input.now !== void 0) return input.now;
1934
- return new Date((input.clock ?? systemPlanningClock).now());
1935
- }
1936
- //#endregion
1937
- //#region ../core/src/event-pricing.ts
1938
- /** Capacity closes booking, but does not make an otherwise eligible price unknown. */
1939
- function isPublicEventPricingOptionDisplayable(option) {
1940
- if (!option.isActive) return false;
1941
- if (option.availability.kind === "available") return true;
1942
- const reason = option.availability.reason;
1943
- switch (reason) {
1944
- case "sold_out":
1945
- case "overallCapacityUnavailable": return true;
1946
- case "inactive":
1947
- case "not_on_sale_yet":
1948
- case "sale_ended":
1949
- case "attendance_mode_unavailable": return false;
1950
- default: return assertNever$2(reason);
1951
- }
1952
- }
1953
- function publicEventPricingDisplay(pricing) {
1954
- switch (pricing.kind) {
1955
- case "free":
1956
- case "single":
1957
- case "sliding_scale": return canonicalEventPricingDisplay(pricing);
1958
- case "tiered": return resolvePricingOptionsPriceDisplay({
1959
- options: pricing.options.filter(isPublicEventPricingOptionDisplayable),
1960
- currency: pricing.currency,
1961
- purpose: "configured"
1962
- });
1963
- case "external": return {
1964
- kind: "external",
1965
- label: pricing.label
1966
- };
1967
- }
1968
- }
1969
- function publicEventPricingType(pricing) {
1970
- switch (pricing.kind) {
1971
- case "free": return "free";
1972
- case "single":
1973
- case "tiered":
1974
- case "sliding_scale": return isFreePublicEventPriceDisplay(publicEventPricingDisplay(pricing)) ? "free" : "paid";
1975
- case "external": return "external";
1976
- }
1977
- }
1978
- function isFreePublicEventPriceDisplay(display) {
1979
- switch (display.kind) {
1980
- case "free": return true;
1981
- case "exact":
1982
- case "from":
1983
- case "range":
1984
- case "mixed":
1985
- case "unavailable":
1986
- case "external": return false;
1987
- }
1988
- }
1989
- function resolvePricingOptionSaleWindowState(option, now) {
1990
- if (option.saleStartsAt !== null && option.saleStartsAt !== void 0 && new Date(option.saleStartsAt) > now) return "not_started";
1991
- if (option.saleEndsAt !== null && option.saleEndsAt !== void 0 && new Date(option.saleEndsAt) < now) return "ended";
1992
- return "open";
1993
- }
1994
- function resolvePricingOptionsPriceDisplay(input) {
1995
- const now = input.purpose === "available_now" ? resolvePlanningNowDate({
1996
- now: input.now,
1997
- clock: input.clock
1998
- }) : null;
1999
- const amounts = input.options.filter((option) => option.isActive && (now === null || resolvePricingOptionSaleWindowState(option, now) === "open")).map((option) => option.priceAmount);
2000
- return amounts.length === 0 ? {
2001
- kind: "unavailable",
2002
- currency: input.currency
2003
- } : amountRangeDisplay(amounts, input.currency);
2004
- }
2005
- function amountRangeDisplay(amounts, currency) {
2006
- const minAmount = Math.min(...amounts);
2007
- const maxAmount = Math.max(...amounts);
2008
- if (minAmount === maxAmount) return minAmount === 0 ? {
2009
- kind: "free",
2010
- currency
2011
- } : {
2012
- kind: "exact",
2013
- amount: minAmount,
2014
- currency
2015
- };
2016
- if (minAmount === 0) {
2017
- const paidAmounts = amounts.filter((amount) => amount > 0);
2018
- return {
2019
- kind: "mixed",
2020
- paidFromAmount: Math.min(...paidAmounts),
2021
- paidToAmount: maxAmount,
2022
- currency
2023
- };
2024
- }
2025
- return {
2026
- kind: "range",
2027
- minAmount,
2028
- maxAmount,
2029
- currency
2030
- };
2031
- }
2032
- /** Derives configured price presentation directly from canonical pricing. */
2033
- function canonicalEventPricingDisplay(pricing) {
2034
- switch (pricing.kind) {
2035
- case "free": return {
2036
- kind: "free",
2037
- currency: pricing.currency
2038
- };
2039
- case "single": return {
2040
- kind: "exact",
2041
- amount: pricing.amount,
2042
- currency: pricing.currency
2043
- };
2044
- case "sliding_scale": return pricing.minimum === pricing.maximum ? {
2045
- kind: "exact",
2046
- amount: pricing.maximum,
2047
- currency: pricing.currency
2048
- } : {
2049
- kind: "range",
2050
- minAmount: pricing.minimum,
2051
- maxAmount: pricing.maximum,
2052
- currency: pricing.currency
2053
- };
2054
- case "tiered": return resolvePricingOptionsPriceDisplay({
2055
- options: pricing.options,
2056
- currency: pricing.currency,
2057
- purpose: "configured"
2058
- });
2059
- default: return assertNever$2(pricing);
2060
- }
2061
- }
2062
- //#endregion
2063
- //#region ../core/src/event-page-surface-subject.ts
2064
- function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
2065
- const mode = eventDestinationMode ? { eventDestinationMode } : {};
2066
- return bookingScope === "whole_series" ? {
2067
- kind: "eventSeries",
2068
- eventSeriesId,
2069
- bookingScope,
2070
- isWholeSeriesBooking: true,
2071
- ...mode
2072
- } : {
2073
- kind: "eventSeries",
2074
- eventSeriesId,
2075
- bookingScope,
2076
- ...mode
2077
- };
2078
- }
2079
- function createEventOfferingPageSurfaceSubject(eventOfferingId, input) {
2080
- const mode = input.eventDestinationMode ? { eventDestinationMode: input.eventDestinationMode } : {};
2081
- const identity = {
2082
- kind: "eventOffering",
2083
- eventOfferingId,
2084
- catalogueEventSeriesIds: input.catalogueEventSeriesIds
2085
- };
2086
- return input.bookingScope === "whole_series" ? {
2087
- ...identity,
2088
- bookingScope: input.bookingScope,
2089
- isWholeSeriesBooking: true,
2090
- ...mode
2091
- } : {
2092
- ...identity,
2093
- bookingScope: input.bookingScope,
2094
- ...mode
2095
- };
2096
- }
2097
- /**
2098
- * Read the destination mode off a wire value.
2099
- *
2100
- * Absent or unrecognised reads as absent, which leaves the routed Event record
2101
- * as the only authority — the behaviour every caller had before the field
2102
- * existed.
2103
- */
2104
- function parseEventPageSurfaceDestinationMode(value) {
2105
- return value === "cms-managed" || value === "system-managed" ? value : void 0;
2106
- }
2107
- function parseEventPageSurfaceSubject(value) {
2108
- if (!isRecord$7(value)) return err({ kind: "notObject" });
2109
- switch (value.kind) {
2110
- case "eventSeries": {
2111
- const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
2112
- if (!eventSeriesId.ok) return err({
2113
- kind: "invalidEventSeriesId",
2114
- error: eventSeriesId.error
2115
- });
2116
- if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") return err({
2117
- kind: "invalidEventSeriesBookingScope",
2118
- bookingScope: value.bookingScope
2119
- });
2120
- return ok(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope, parseEventPageSurfaceDestinationMode(value.eventDestinationMode)));
2121
- }
2122
- case "eventOffering": {
2123
- const eventOfferingId = parseEventOfferingId(value.eventOfferingId);
2124
- if (!eventOfferingId.ok) return err({
2125
- kind: "invalidEventOfferingId",
2126
- error: eventOfferingId.error
2127
- });
2128
- if (!Array.isArray(value.catalogueEventSeriesIds)) return err({
2129
- kind: "invalidEventOfferingCatalogue",
2130
- catalogue: value.catalogueEventSeriesIds
2131
- });
2132
- if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") return err({
2133
- kind: "invalidEventOfferingBookingScope",
2134
- bookingScope: value.bookingScope
2135
- });
2136
- const parsedCatalogueIds = parseEventSeriesIdList(value.catalogueEventSeriesIds);
2137
- if (!parsedCatalogueIds.ok) return parsedCatalogueIds;
2138
- const eventDestinationMode = parseEventPageSurfaceDestinationMode(value.eventDestinationMode);
2139
- return ok(createEventOfferingPageSurfaceSubject(eventOfferingId.value, {
2140
- catalogueEventSeriesIds: parsedCatalogueIds.value,
2141
- bookingScope: value.bookingScope,
2142
- ...eventDestinationMode ? { eventDestinationMode } : {}
2143
- }));
2144
- }
2145
- default: return err({
2146
- kind: "unsupportedSubject",
2147
- subjectKind: value.kind
2148
- });
2149
- }
2150
- }
2151
- function parseEventSeriesIdList(values) {
2152
- const parsed = [];
2153
- for (const value of values) {
2154
- const eventSeriesId = parseEventSeriesId(value);
2155
- if (!eventSeriesId.ok) return err({
2156
- kind: "invalidEventSeriesId",
2157
- error: eventSeriesId.error
2158
- });
2159
- parsed.push(eventSeriesId.value);
2160
- }
2161
- return ok(parsed);
2162
- }
2163
- function isRecord$7(value) {
2164
- return typeof value === "object" && value !== null && !Array.isArray(value);
2165
- }
1596
+ }
1597
+ function parseEventSeriesIdList(values) {
1598
+ const parsed = [];
1599
+ for (const value of values) {
1600
+ const eventSeriesId = parseEventSeriesId(value);
1601
+ if (!eventSeriesId.ok) return err({
1602
+ kind: "invalidEventSeriesId",
1603
+ error: eventSeriesId.error
1604
+ });
1605
+ parsed.push(eventSeriesId.value);
1606
+ }
1607
+ return ok(parsed);
1608
+ }
1609
+ function isRecord$10(value) {
1610
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1611
+ }
2166
1612
  //#endregion
2167
1613
  //#region ../core/src/booking-time-zone.ts
2168
1614
  const TIME_ZONE_VALIDITY_CACHE_LIMIT = 100;
@@ -2330,9 +1776,379 @@ function toLocationCoordinates(value) {
2330
1776
  };
2331
1777
  }
2332
1778
  //#endregion
2333
- //#region ../core/src/newsletter-signup-destination.ts
2334
- function parseNewsletterSignupDestinationProvider(value) {
2335
- return value === "riverbank" || value === "mailchimp" ? value : null;
1779
+ //#region ../core/src/mailing-list-identifiers.ts
1780
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
1781
+ const OPAQUE_ID_PATTERN = /^[A-Za-z0-9_-]{1,100}$/u;
1782
+ function parseTrimmedMatchingString(value, pattern) {
1783
+ if (typeof value !== "string") return null;
1784
+ const trimmed = value.trim();
1785
+ return pattern.test(trimmed) ? trimmed : null;
1786
+ }
1787
+ function parsePositiveInteger(value) {
1788
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1789
+ }
1790
+ function parseNewsletterListId(value) {
1791
+ return parseTrimmedMatchingString(value, UUID_PATTERN);
1792
+ }
1793
+ function parseMailchimpAudienceId(value) {
1794
+ return parseTrimmedMatchingString(value, OPAQUE_ID_PATTERN);
1795
+ }
1796
+ function parseMailchimpInterestId(value) {
1797
+ return parseTrimmedMatchingString(value, OPAQUE_ID_PATTERN);
1798
+ }
1799
+ function parseBrevoListId(value) {
1800
+ return parsePositiveInteger(value);
1801
+ }
1802
+ //#endregion
1803
+ //#region ../core/src/mailing-list-setup-parser.ts
1804
+ function parseMailingListGlobalForm(value) {
1805
+ if (!isRecord$9(value)) return formError("shape");
1806
+ const fields = parseGlobalFields(value.fields);
1807
+ if (fields === null) return formError("fields");
1808
+ if (typeof value.includeOptionalPhone !== "boolean") return formError("includeOptionalPhone");
1809
+ const buttonLabel = parseRequiredString(value.buttonLabel);
1810
+ if (buttonLabel === null) return formError("buttonLabel");
1811
+ const privacyNote = parseOptionalString(value.privacyNote);
1812
+ if (privacyNote === void 0) return formError("privacyNote");
1813
+ return ok({
1814
+ fields,
1815
+ includeOptionalPhone: value.includeOptionalPhone,
1816
+ buttonLabel,
1817
+ privacyNote
1818
+ });
1819
+ }
1820
+ function parseGlobalFields(value) {
1821
+ if (!isRecord$9(value) || typeof value.preset !== "string") return null;
1822
+ switch (value.preset) {
1823
+ case "emailOnly": return { preset: "emailOnly" };
1824
+ case "emailName": return isRequirement(value.name) ? {
1825
+ preset: "emailName",
1826
+ name: value.name
1827
+ } : null;
1828
+ case "emailFirstLast": return isRequirement(value.firstName) && isRequirement(value.lastName) ? {
1829
+ preset: "emailFirstLast",
1830
+ firstName: value.firstName,
1831
+ lastName: value.lastName
1832
+ } : null;
1833
+ case "emailNameCompany": return isRequirement(value.name) && isRequirement(value.company) ? {
1834
+ preset: "emailNameCompany",
1835
+ name: value.name,
1836
+ company: value.company
1837
+ } : null;
1838
+ default: return null;
1839
+ }
1840
+ }
1841
+ function parseMailingListSetupRevision(value) {
1842
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1843
+ }
1844
+ function parseRequiredString(value) {
1845
+ if (typeof value !== "string") return null;
1846
+ const trimmed = value.trim();
1847
+ return trimmed.length > 0 ? trimmed : null;
1848
+ }
1849
+ function parseOptionalString(value) {
1850
+ if (value === null || value === void 0) return null;
1851
+ if (typeof value !== "string") return void 0;
1852
+ const trimmed = value.trim();
1853
+ return trimmed.length > 0 ? trimmed : null;
1854
+ }
1855
+ function isRequirement(value) {
1856
+ return value === "required" || value === "optional";
1857
+ }
1858
+ function isRecord$9(value) {
1859
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1860
+ }
1861
+ function formError(field) {
1862
+ return err({
1863
+ tag: "invalid_mailing_list_global_form",
1864
+ field
1865
+ });
1866
+ }
1867
+ //#endregion
1868
+ //#region ../core/src/mailing-list-targeting.ts
1869
+ function planMailingListChoicePresentation(targeting) {
1870
+ if (targeting.mode === "default" || targeting.selection.tag === "fixed") return { tag: "hidden" };
1871
+ switch (targeting.provider) {
1872
+ case "native": return targeting.selection.offeredTopicIds.length === 1 ? {
1873
+ tag: "automatic",
1874
+ selection: {
1875
+ provider: "native",
1876
+ topicIds: targeting.selection.offeredTopicIds
1877
+ }
1878
+ } : {
1879
+ tag: "visitor_choice",
1880
+ provider: "native",
1881
+ optionCount: targeting.selection.offeredTopicIds.length
1882
+ };
1883
+ case "brevo": return targeting.selection.offeredListIds.length === 1 ? {
1884
+ tag: "automatic",
1885
+ selection: {
1886
+ provider: "brevo",
1887
+ listIds: targeting.selection.offeredListIds
1888
+ }
1889
+ } : {
1890
+ tag: "visitor_choice",
1891
+ provider: "brevo",
1892
+ optionCount: targeting.selection.offeredListIds.length
1893
+ };
1894
+ case "mailchimp": return targeting.selection.offeredInterestIds.length === 1 ? {
1895
+ tag: "automatic",
1896
+ selection: {
1897
+ provider: "mailchimp",
1898
+ interestIds: targeting.selection.offeredInterestIds
1899
+ }
1900
+ } : {
1901
+ tag: "visitor_choice",
1902
+ provider: "mailchimp",
1903
+ optionCount: targeting.selection.offeredInterestIds.length
1904
+ };
1905
+ }
1906
+ }
1907
+ //#endregion
1908
+ //#region ../core/src/mailing-list-runtime.ts
1909
+ function parseMailingListRuntimeContext(value) {
1910
+ if (!isRecord$8(value)) return invalidRuntimeContext();
1911
+ if (value.tag === "not_configured") return ok({ tag: "not_configured" });
1912
+ if (value.tag !== "configured") return invalidRuntimeContext();
1913
+ const revision = parseMailingListSetupRevision(value.revision);
1914
+ const form = parseMailingListGlobalForm(value.form);
1915
+ const provider = parseRuntimeProvider(value.provider);
1916
+ if (revision === null || !form.ok || provider === null) return invalidRuntimeContext();
1917
+ return ok({
1918
+ tag: "configured",
1919
+ revision,
1920
+ form: form.value,
1921
+ provider
1922
+ });
1923
+ }
1924
+ function parseRuntimeProvider(value) {
1925
+ if (!isRecord$8(value)) return null;
1926
+ switch (value.provider) {
1927
+ case "native": return parseNativeRuntimeProvider(value);
1928
+ case "brevo": return parseBrevoRuntimeProvider(value);
1929
+ case "mailchimp": return parseMailchimpRuntimeProvider(value);
1930
+ default: return null;
1931
+ }
1932
+ }
1933
+ function parseNativeRuntimeProvider(value) {
1934
+ const defaultTopicId = parseNewsletterListId(value.defaultTopicId);
1935
+ const topics = parseNamedItems(value.topics, parseNewsletterListId);
1936
+ return defaultTopicId !== null && topics !== null ? {
1937
+ provider: "native",
1938
+ defaultTopicId,
1939
+ topics
1940
+ } : null;
1941
+ }
1942
+ function parseBrevoRuntimeProvider(value) {
1943
+ const defaultListId = parseBrevoListId(value.defaultListId);
1944
+ const lists = parseNamedItems(value.lists, parseBrevoListId);
1945
+ return defaultListId !== null && lists !== null ? {
1946
+ provider: "brevo",
1947
+ defaultListId,
1948
+ lists
1949
+ } : null;
1950
+ }
1951
+ function parseMailchimpRuntimeProvider(value) {
1952
+ if (!Array.isArray(value.audiences)) return null;
1953
+ const defaultAudienceId = parseMailchimpAudienceId(value.defaultAudienceId);
1954
+ if (defaultAudienceId === null) return null;
1955
+ const audiences = [];
1956
+ for (const audience of value.audiences) {
1957
+ if (!isRecord$8(audience)) return null;
1958
+ const id = parseMailchimpAudienceId(audience.id);
1959
+ const name = parseName(audience.name);
1960
+ if (id === null || name === null || !Array.isArray(audience.interests)) return null;
1961
+ const interests = parseMailchimpInterests(audience.interests);
1962
+ if (interests === null) return null;
1963
+ audiences.push({
1964
+ id,
1965
+ name,
1966
+ interests
1967
+ });
1968
+ }
1969
+ return {
1970
+ provider: "mailchimp",
1971
+ defaultAudienceId,
1972
+ audiences
1973
+ };
1974
+ }
1975
+ function parseMailchimpInterests(value) {
1976
+ const interests = [];
1977
+ for (const interest of value) {
1978
+ if (!isRecord$8(interest)) return null;
1979
+ const id = parseMailchimpInterestId(interest.id);
1980
+ const name = parseName(interest.name);
1981
+ const categoryName = parseName(interest.categoryName);
1982
+ if (id === null || name === null || categoryName === null) return null;
1983
+ interests.push({
1984
+ id,
1985
+ name,
1986
+ categoryName
1987
+ });
1988
+ }
1989
+ return interests;
1990
+ }
1991
+ function parseNamedItems(value, parseId) {
1992
+ if (!Array.isArray(value)) return null;
1993
+ const items = [];
1994
+ for (const item of value) {
1995
+ if (!isRecord$8(item)) return null;
1996
+ const id = parseId(item.id);
1997
+ const name = parseName(item.name);
1998
+ if (id === null || name === null) return null;
1999
+ items.push({
2000
+ id,
2001
+ name
2002
+ });
2003
+ }
2004
+ return items;
2005
+ }
2006
+ function parseName(value) {
2007
+ if (typeof value !== "string") return null;
2008
+ const name = value.trim();
2009
+ return name.length > 0 ? name : null;
2010
+ }
2011
+ function isRecord$8(value) {
2012
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2013
+ }
2014
+ function invalidRuntimeContext() {
2015
+ return err({ tag: "invalid_mailing_list_runtime_context" });
2016
+ }
2017
+ //#endregion
2018
+ //#region ../core/src/mailing-list-targeting-parser.ts
2019
+ function parseMailingListPlacementTargeting(value) {
2020
+ if (!isRecord$7(value)) return targetingError("shape");
2021
+ if (value.mode === "default") return ok({ mode: "default" });
2022
+ if (value.mode !== "custom") return targetingError("mode");
2023
+ if (!isRecord$7(value.selection)) return targetingError("selection");
2024
+ switch (value.provider) {
2025
+ case "native": return parseNativeTargeting(value.selection);
2026
+ case "brevo": return parseBrevoTargeting(value.selection);
2027
+ case "mailchimp": return parseMailchimpTargeting(value, value.selection);
2028
+ default: return targetingError("provider");
2029
+ }
2030
+ }
2031
+ function parseNativeTargeting(selection) {
2032
+ if (selection.tag === "fixed") {
2033
+ const topicIds = parseNonEmptyUniqueIds(selection.topicIds, parseNewsletterListId);
2034
+ return topicIds.ok ? ok({
2035
+ mode: "custom",
2036
+ provider: "native",
2037
+ selection: {
2038
+ tag: "fixed",
2039
+ topicIds: topicIds.value
2040
+ }
2041
+ }) : topicIds;
2042
+ }
2043
+ if (selection.tag === "visitor_choice") {
2044
+ const offeredTopicIds = parseNonEmptyUniqueIds(selection.offeredTopicIds, parseNewsletterListId);
2045
+ return offeredTopicIds.ok ? ok({
2046
+ mode: "custom",
2047
+ provider: "native",
2048
+ selection: {
2049
+ tag: "visitor_choice",
2050
+ offeredTopicIds: offeredTopicIds.value
2051
+ }
2052
+ }) : offeredTopicIds;
2053
+ }
2054
+ return targetingError("selection");
2055
+ }
2056
+ function parseBrevoTargeting(selection) {
2057
+ if (selection.tag === "fixed") {
2058
+ const listIds = parseNonEmptyUniqueIds(selection.listIds, parseBrevoListId);
2059
+ return listIds.ok ? ok({
2060
+ mode: "custom",
2061
+ provider: "brevo",
2062
+ selection: {
2063
+ tag: "fixed",
2064
+ listIds: listIds.value
2065
+ }
2066
+ }) : listIds;
2067
+ }
2068
+ if (selection.tag === "visitor_choice") {
2069
+ const offeredListIds = parseNonEmptyUniqueIds(selection.offeredListIds, parseBrevoListId);
2070
+ return offeredListIds.ok ? ok({
2071
+ mode: "custom",
2072
+ provider: "brevo",
2073
+ selection: {
2074
+ tag: "visitor_choice",
2075
+ offeredListIds: offeredListIds.value
2076
+ }
2077
+ }) : offeredListIds;
2078
+ }
2079
+ return targetingError("selection");
2080
+ }
2081
+ function parseMailchimpTargeting(value, selection) {
2082
+ const audienceId = parseMailchimpAudienceId(value.audienceId);
2083
+ if (audienceId === null) return targetingError("audienceId");
2084
+ if (selection.tag === "fixed") {
2085
+ const interestIds = parseUniqueIds(selection.interestIds, parseMailchimpInterestId);
2086
+ return interestIds.ok ? ok({
2087
+ mode: "custom",
2088
+ provider: "mailchimp",
2089
+ audienceId,
2090
+ selection: {
2091
+ tag: "fixed",
2092
+ interestIds: interestIds.value
2093
+ }
2094
+ }) : interestIds;
2095
+ }
2096
+ if (selection.tag === "visitor_choice") {
2097
+ const offeredInterestIds = parseNonEmptyUniqueIds(selection.offeredInterestIds, parseMailchimpInterestId);
2098
+ return offeredInterestIds.ok ? ok({
2099
+ mode: "custom",
2100
+ provider: "mailchimp",
2101
+ audienceId,
2102
+ selection: {
2103
+ tag: "visitor_choice",
2104
+ offeredInterestIds: offeredInterestIds.value
2105
+ }
2106
+ }) : offeredInterestIds;
2107
+ }
2108
+ return targetingError("selection");
2109
+ }
2110
+ function parseNonEmptyUniqueIds(value, parseId) {
2111
+ const parsed = parseUniqueIds(value, parseId);
2112
+ if (!parsed.ok) return parsed;
2113
+ const [first, ...rest] = parsed.value;
2114
+ return first === void 0 ? targetingError("targets") : ok([first, ...rest]);
2115
+ }
2116
+ function parseUniqueIds(value, parseId) {
2117
+ if (!Array.isArray(value)) return targetingError("targets");
2118
+ const parsed = [];
2119
+ for (const item of value) {
2120
+ const id = parseId(item);
2121
+ if (id === null) return targetingError("targets");
2122
+ if (parsed.includes(id)) return targetingError("duplicate_target");
2123
+ parsed.push(id);
2124
+ }
2125
+ return ok(parsed);
2126
+ }
2127
+ function isRecord$7(value) {
2128
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2129
+ }
2130
+ function targetingError(field) {
2131
+ return err({
2132
+ tag: "invalid_mailing_list_targeting",
2133
+ field
2134
+ });
2135
+ }
2136
+ //#endregion
2137
+ //#region ../core/src/mailing-list-submission.ts
2138
+ function parseMailingListBlockId(value) {
2139
+ const parsed = typeof value === "string" ? value.trim() : "";
2140
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(parsed) ? parsed : null;
2141
+ }
2142
+ function parseMailingListSignedSourceToken(value) {
2143
+ const parsed = typeof value === "string" ? value.trim() : "";
2144
+ return /^[A-Za-z0-9._~-]{16,1000}$/u.test(parsed) ? parsed : null;
2145
+ }
2146
+ function buildMailingListSignupBlockSourceRef(blockId) {
2147
+ const parsed = parseMailingListBlockId(blockId);
2148
+ return parsed !== null ? {
2149
+ tag: "block_ref",
2150
+ blockId: parsed
2151
+ } : null;
2336
2152
  }
2337
2153
  //#endregion
2338
2154
  //#region ../core/src/secret.ts
@@ -2373,320 +2189,1111 @@ function createSecretInternal(kind, value) {
2373
2189
  return Object.freeze(secret);
2374
2190
  }
2375
2191
  /**
2376
- * Parse an untrusted management API key before constructing its Secret value.
2192
+ * Parse an untrusted management API key before constructing its Secret value.
2193
+ *
2194
+ * The Secret type protects an already validated value from accidental
2195
+ * disclosure; this boundary is what validates the value's wire format.
2196
+ */
2197
+ function parseManagementKey(raw) {
2198
+ if (typeof raw !== "string" || !raw.startsWith("bld_mgmt_sk_") || raw.length === 12) return err({ kind: "invalid-management-key" });
2199
+ return ok(createSecretInternal("management-key", raw));
2200
+ }
2201
+ function sessionStartMs(session) {
2202
+ return Date.parse(session.startsAt);
2203
+ }
2204
+ /**
2205
+ * The run's sessions that still count, in schedule order.
2206
+ *
2207
+ * Sessions whose start cannot be read are dropped rather than sorted to an
2208
+ * arbitrary end: an unparseable date is not a date a visitor can attend.
2209
+ */
2210
+ function orderedLiveRunSessions(sessions) {
2211
+ return sessions.filter((session) => session.status !== "cancelled" && Number.isFinite(sessionStartMs(session))).sort((left, right) => sessionStartMs(left) - sessionStartMs(right));
2212
+ }
2213
+ const PUBLIC_CONTENT_FRESHNESS_SECONDS = 3600;
2214
+ function availability(transport) {
2215
+ return {
2216
+ kind: "live",
2217
+ reason: "booking_correctness",
2218
+ freshness: "availability",
2219
+ transport
2220
+ };
2221
+ }
2222
+ function content(tags, transport = "direct") {
2223
+ return {
2224
+ kind: "tagged",
2225
+ tags,
2226
+ freshness: "content",
2227
+ transport
2228
+ };
2229
+ }
2230
+ /** The single classification authority for cacheable public reads. */
2231
+ const PUBLIC_READ_CACHE_POLICIES = {
2232
+ listPublishedEntries: content(["published_content"]),
2233
+ getPublishedEntryPreview: content(["published_content"]),
2234
+ listPublicEvents: availability("direct"),
2235
+ listPublicEventCalendarWindow: availability("direct"),
2236
+ listPublicRecurringSchedule: availability("batch_only"),
2237
+ getPublicEventDetails: availability("batch_only"),
2238
+ resolveEventSubroute: availability("direct"),
2239
+ listPublicRouteCandidates: content(["published_content"], "batch_only"),
2240
+ listPublicTeamMembers: content(["public_team_members"]),
2241
+ listPublicProducts: availability("direct"),
2242
+ listPublicPasses: availability("direct"),
2243
+ listPublicMemberships: availability("direct"),
2244
+ getPublicFormById: content(["site_forms"]),
2245
+ getPublicBookingServices: availability("direct"),
2246
+ getPublicBookingService: availability("direct"),
2247
+ getPublicBookingResourcesReference: availability("direct"),
2248
+ getPublicBookingAvailableDates: availability("direct"),
2249
+ getPublicBookingAvailableSlots: availability("direct"),
2250
+ getPublicWeeklySlotAvailability: availability("direct"),
2251
+ listPublicForms: content(["site_forms"]),
2252
+ checkRedirect: content([]),
2253
+ getPublicRoutableContent: content(["published_content"]),
2254
+ getPublicSitemapRoutes: content(["published_content"])
2255
+ };
2256
+ new Set(Object.keys(PUBLIC_READ_CACHE_POLICIES));
2257
+ function getPublicReadCachePolicy(endpoint) {
2258
+ return PUBLIC_READ_CACHE_POLICIES[endpoint];
2259
+ }
2260
+ function publicReadFreshnessSeconds(endpoint) {
2261
+ return getPublicReadCachePolicy(endpoint).freshness === "availability" ? 60 : PUBLIC_CONTENT_FRESHNESS_SECONDS;
2262
+ }
2263
+ //#endregion
2264
+ //#region ../core/src/event-series-behaviour.ts
2265
+ const WHOLE_SERIES_EVENT_PRESET_VALUES = ["course", "term"];
2266
+ const SURFACE_EVENT_PRESET_VALUES = [
2267
+ "class",
2268
+ "course",
2269
+ "term",
2270
+ "workshop",
2271
+ "retreat",
2272
+ "performance"
2273
+ ];
2274
+ //#endregion
2275
+ //#region ../core/src/booking-vertical-preset.ts
2276
+ const BOOKING_ENGINE_BY_BEHAVIOUR = {
2277
+ class: "event_series",
2278
+ workshop: "event_series",
2279
+ course: "event_series",
2280
+ retreat: "event_series",
2281
+ performance: "event_series",
2282
+ appointment: "appointment_service"
2283
+ };
2284
+ function verticalBookingPreset(id, verticalId, canonicalBehaviour, label, pluralLabel, description, availability = "onboarding") {
2285
+ return {
2286
+ id,
2287
+ verticalId,
2288
+ canonicalBehaviour,
2289
+ engine: BOOKING_ENGINE_BY_BEHAVIOUR[canonicalBehaviour],
2290
+ availability,
2291
+ label,
2292
+ pluralLabel,
2293
+ description
2294
+ };
2295
+ }
2296
+ //#endregion
2297
+ //#region ../core/src/booking-vertical-definitions.ts
2298
+ const VERTICAL_DEFINITIONS = [
2299
+ {
2300
+ id: "yoga_wellness",
2301
+ label: "Yoga, meditation or mindfulness",
2302
+ description: "Yoga studios, wellness teachers, and movement practitioners."
2303
+ },
2304
+ {
2305
+ id: "fitness_pt",
2306
+ label: "Fitness and personal training",
2307
+ description: "Personal trainers, group training, and fitness programmes."
2308
+ },
2309
+ {
2310
+ id: "dance_school",
2311
+ label: "Dance school",
2312
+ description: "Dance classes, courses or terms, workshops, private lessons, and shows."
2313
+ },
2314
+ {
2315
+ id: "physiotherapy_physical_health",
2316
+ label: "Physiotherapy & physical health",
2317
+ description: "Physiotherapists, chiropractors, osteopaths, sports therapists, and rehabilitation practitioners."
2318
+ },
2319
+ {
2320
+ id: "massage_bodywork",
2321
+ label: "Massage & bodywork",
2322
+ description: "Massage therapists, bodyworkers, and somatic practitioners."
2323
+ },
2324
+ {
2325
+ id: "therapist_practitioner",
2326
+ label: "Therapist or practitioner",
2327
+ description: "Appointments, sessions, consultations, and occasional group work."
2328
+ },
2329
+ {
2330
+ id: "beauty_spa",
2331
+ label: "Beauty or spa",
2332
+ description: "Treatment-led businesses with optional workshops."
2333
+ },
2334
+ {
2335
+ id: "consultant_coach",
2336
+ label: "Consultant or coach",
2337
+ description: "One-to-one calls, workshops, courses, and retreats."
2338
+ },
2339
+ {
2340
+ id: "training_provider",
2341
+ label: "Training provider",
2342
+ description: "Courses, workshops, classes, appointments, and residential training."
2343
+ },
2344
+ {
2345
+ id: "retreat_centre",
2346
+ label: "Retreat centre",
2347
+ description: "Residential retreats, workshops, classes, courses, and private sessions."
2348
+ },
2349
+ {
2350
+ id: "music_school",
2351
+ label: "Music teacher or school",
2352
+ description: "Lessons, classes, terms, workshops, and performances."
2353
+ },
2354
+ {
2355
+ id: "voice_singing",
2356
+ label: "Voice & Singing",
2357
+ description: "Singing lessons, choirs, voice circles, workshops, and performances."
2358
+ },
2359
+ {
2360
+ id: "performing_arts_school",
2361
+ label: "Theatre or performing arts school",
2362
+ description: "Classes, terms, workshops, performances, and lessons."
2363
+ },
2364
+ {
2365
+ id: "community_wellbeing",
2366
+ label: "Community or wellbeing centre",
2367
+ description: "Groups, workshops, courses, appointments, and retreats."
2368
+ },
2369
+ {
2370
+ id: "venue_event_space",
2371
+ label: "Venue or event space",
2372
+ description: "Room, resource, or access bookings. Depends on the future capacity-booking domain."
2373
+ }
2374
+ ];
2375
+ VERTICAL_DEFINITIONS.map(({ id }) => id);
2376
+ //#endregion
2377
+ //#region ../core/src/booking-vertical-catalog.ts
2378
+ const VERTICAL_BOOKING_PRESETS = [
2379
+ verticalBookingPreset("yoga_wellness.classes", "yoga_wellness", "class", "Class", "Classes", "Regular scheduled classes and group sessions."),
2380
+ verticalBookingPreset("yoga_wellness.workshops", "yoga_wellness", "workshop", "Workshop", "Workshops", "One-off workshops, day retreats, or special sessions."),
2381
+ verticalBookingPreset("yoga_wellness.courses", "yoga_wellness", "course", "Course", "Courses", "Fixed-schedule courses or multi-session groups."),
2382
+ verticalBookingPreset("yoga_wellness.retreats", "yoga_wellness", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats and immersive experiences."),
2383
+ verticalBookingPreset("yoga_wellness.appointments", "yoga_wellness", "appointment", "Appointment", "Appointments", "Private sessions, consultations, or one-to-one support."),
2384
+ verticalBookingPreset("fitness_pt.group_training", "fitness_pt", "class", "Group training", "Group training", "Regular scheduled classes and group sessions."),
2385
+ verticalBookingPreset("fitness_pt.personal_training", "fitness_pt", "appointment", "Personal training", "Personal training", "One-to-one sessions booked from your availability."),
2386
+ verticalBookingPreset("fitness_pt.programmes", "fitness_pt", "course", "Programme", "Programmes", "Multi-session programmes like bootcamps or courses."),
2387
+ verticalBookingPreset("fitness_pt.workshops", "fitness_pt", "workshop", "Workshop", "Workshops", "One-off workshops, events, or special sessions."),
2388
+ verticalBookingPreset("fitness_pt.retreats", "fitness_pt", "retreat", "Retreat", "Retreats", "Multi-day or residential fitness experiences."),
2389
+ verticalBookingPreset("dance_school.classes", "dance_school", "class", "Class", "Classes", "Regular group classes and weekly sessions."),
2390
+ {
2391
+ ...verticalBookingPreset("dance_school.terms", "dance_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date course groups."),
2392
+ eventPresetOverride: "term"
2393
+ },
2394
+ verticalBookingPreset("dance_school.courses", "dance_school", "course", "Course", "Courses", "Fixed-schedule courses or multi-session groups.", "settings"),
2395
+ verticalBookingPreset("dance_school.workshops", "dance_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2396
+ verticalBookingPreset("dance_school.private_lessons", "dance_school", "appointment", "Private lesson", "Private lessons", "One-to-one lessons or coaching sessions."),
2397
+ verticalBookingPreset("dance_school.shows", "dance_school", "performance", "Show", "Shows", "Ticketed performances, showcases, and recitals."),
2398
+ verticalBookingPreset("physiotherapy_physical_health.appointments", "physiotherapy_physical_health", "appointment", "Appointment", "Appointments", "Assessments, treatments, follow-ups, and rehabilitation sessions."),
2399
+ verticalBookingPreset("physiotherapy_physical_health.rehab_classes", "physiotherapy_physical_health", "class", "Rehab class", "Rehab classes", "Regular rehabilitation, mobility, strength, or recovery classes."),
2400
+ verticalBookingPreset("physiotherapy_physical_health.workshops", "physiotherapy_physical_health", "workshop", "Workshop", "Workshops", "One-off workshops, talks, or practical group sessions."),
2401
+ verticalBookingPreset("massage_bodywork.treatments", "massage_bodywork", "appointment", "Treatment", "Treatments", "Massage treatments, bodywork sessions, and one-to-one appointments."),
2402
+ verticalBookingPreset("massage_bodywork.group_sessions", "massage_bodywork", "class", "Group session", "Group sessions", "Regular bodywork, somatic movement, or self-massage sessions."),
2403
+ verticalBookingPreset("massage_bodywork.workshops", "massage_bodywork", "workshop", "Workshop", "Workshops", "One-off bodywork, self-massage, or somatic practice workshops."),
2404
+ verticalBookingPreset("therapist_practitioner.appointments", "therapist_practitioner", "appointment", "Appointment", "Appointments", "Sessions, consultations, treatments, or assessments."),
2405
+ verticalBookingPreset("therapist_practitioner.workshops", "therapist_practitioner", "workshop", "Workshop", "Workshops", "One-off group workshops, talks, or wellbeing sessions."),
2406
+ verticalBookingPreset("therapist_practitioner.retreats", "therapist_practitioner", "retreat", "Retreat", "Retreats", "Multi-day retreats or immersive group experiences."),
2407
+ verticalBookingPreset("beauty_spa.appointments", "beauty_spa", "appointment", "Appointment", "Appointments", "Treatments, consultations, services, and sessions."),
2408
+ verticalBookingPreset("beauty_spa.workshops", "beauty_spa", "workshop", "Workshop", "Workshops", "Skincare workshops, makeup lessons, demos, or group events."),
2409
+ verticalBookingPreset("consultant_coach.appointments", "consultant_coach", "appointment", "Appointment", "Appointments", "One-to-one calls, consultations, reviews, or coaching sessions."),
2410
+ verticalBookingPreset("consultant_coach.workshops", "consultant_coach", "workshop", "Workshop", "Workshops", "One-off group sessions, webinars, or strategy days."),
2411
+ verticalBookingPreset("consultant_coach.courses", "consultant_coach", "course", "Course", "Courses", "Fixed-schedule cohorts or multi-session group programmes."),
2412
+ verticalBookingPreset("consultant_coach.retreats", "consultant_coach", "retreat", "Retreat", "Retreats", "Multi-day coaching retreats or immersive experiences."),
2413
+ verticalBookingPreset("training_provider.courses", "training_provider", "course", "Course", "Courses", "Fixed-schedule courses, cohorts, and multi-session training."),
2414
+ verticalBookingPreset("training_provider.workshops", "training_provider", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, and short training sessions."),
2415
+ verticalBookingPreset("training_provider.appointments", "training_provider", "appointment", "Appointment", "Appointments", "Consultations, assessments, mentoring, or office hours."),
2416
+ verticalBookingPreset("training_provider.classes", "training_provider", "class", "Class", "Classes", "Regular classes, sessions, or groups.", "settings"),
2417
+ verticalBookingPreset("training_provider.residentials", "training_provider", "retreat", "Residential", "Residentials", "Residential courses or multi-day training experiences.", "settings"),
2418
+ verticalBookingPreset("retreat_centre.retreats", "retreat_centre", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats with richer booking needs."),
2419
+ verticalBookingPreset("retreat_centre.workshops", "retreat_centre", "workshop", "Workshop", "Workshops", "One-day retreats, ceremonies, workshops, or special events."),
2420
+ verticalBookingPreset("retreat_centre.classes", "retreat_centre", "class", "Class", "Classes", "Regular sessions like guided meditations or group practices."),
2421
+ verticalBookingPreset("retreat_centre.courses", "retreat_centre", "course", "Course", "Courses", "Fixed-schedule programmes or multi-session experiences."),
2422
+ verticalBookingPreset("retreat_centre.appointments", "retreat_centre", "appointment", "Appointment", "Appointments", "Private sessions, therapies, consultations, or add-ons."),
2423
+ verticalBookingPreset("music_school.lessons", "music_school", "appointment", "Lesson", "Lessons", "One-to-one lessons, consultations, or coaching sessions."),
2424
+ verticalBookingPreset("music_school.classes", "music_school", "class", "Class", "Classes", "Regular group classes, ensembles, or recurring sessions."),
2425
+ {
2426
+ ...verticalBookingPreset("music_school.terms", "music_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date group courses."),
2427
+ eventPresetOverride: "term"
2428
+ },
2429
+ verticalBookingPreset("music_school.workshops", "music_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2430
+ verticalBookingPreset("music_school.performances", "music_school", "performance", "Performance", "Performances", "Concerts, recitals, showcases, or ticketed performances."),
2431
+ verticalBookingPreset("voice_singing.lessons", "voice_singing", "appointment", "Voice session", "Voice sessions", "One-to-one singing lessons, voice coaching, or private practice sessions."),
2432
+ verticalBookingPreset("voice_singing.classes", "voice_singing", "class", "Ongoing weekly session", "Ongoing weekly sessions", "Regular choirs, voice circles, community singing, or group practice."),
2433
+ {
2434
+ ...verticalBookingPreset("voice_singing.terms", "voice_singing", "course", "Singing term", "Singing terms", "Multi-week choir terms, voice courses, or fixed-date group programmes."),
2435
+ eventPresetOverride: "term"
2436
+ },
2437
+ verticalBookingPreset("voice_singing.workshops", "voice_singing", "workshop", "Workshop", "Workshops", "One-off singing workshops, voice journeys, or devotional singing sessions."),
2438
+ verticalBookingPreset("voice_singing.performances", "voice_singing", "performance", "Performance", "Performances", "Choir concerts, sharing events, recitals, or community performances."),
2439
+ verticalBookingPreset("performing_arts_school.classes", "performing_arts_school", "class", "Class", "Classes", "Regular group classes, rehearsals, or weekly sessions."),
2440
+ {
2441
+ ...verticalBookingPreset("performing_arts_school.terms", "performing_arts_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date course groups."),
2442
+ eventPresetOverride: "term"
2443
+ },
2444
+ verticalBookingPreset("performing_arts_school.workshops", "performing_arts_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2445
+ verticalBookingPreset("performing_arts_school.performances", "performing_arts_school", "performance", "Performance", "Performances", "Shows, showcases, productions, or ticketed performances."),
2446
+ verticalBookingPreset("performing_arts_school.lessons", "performing_arts_school", "appointment", "Lesson", "Lessons", "One-to-one lessons, coaching, or audition prep."),
2447
+ verticalBookingPreset("community_wellbeing.classes", "community_wellbeing", "class", "Class", "Classes", "Regular groups, sessions, practices, or weekly activities."),
2448
+ verticalBookingPreset("community_wellbeing.workshops", "community_wellbeing", "workshop", "Workshop", "Workshops", "One-off workshops, talks, events, or gatherings."),
2449
+ verticalBookingPreset("community_wellbeing.courses", "community_wellbeing", "course", "Course", "Courses", "Fixed-schedule courses, study groups, or multi-session programmes."),
2450
+ verticalBookingPreset("community_wellbeing.appointments", "community_wellbeing", "appointment", "Appointment", "Appointments", "One-to-one appointments, consultations, or support sessions."),
2451
+ verticalBookingPreset("community_wellbeing.retreats", "community_wellbeing", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats."),
2452
+ verticalBookingPreset("venue_event_space.resource_hire", "venue_event_space", "appointment", "Hire slot", "Hire slots", "Future room, venue, and resource hire bookings.", "future")
2453
+ ];
2454
+ //#endregion
2455
+ //#region ../core/src/booking-vertical-config.ts
2456
+ const VERTICAL_ID_SET = new Set(VERTICAL_DEFINITIONS.map(({ id }) => id));
2457
+ const VERTICAL_PRESET_ID_SET = new Set(VERTICAL_BOOKING_PRESETS.map((presetDefinition) => presetDefinition.id));
2458
+ function parseVerticalId(value) {
2459
+ return typeof value === "string" && VERTICAL_ID_SET.has(value) ? value : null;
2460
+ }
2461
+ function parseVerticalPresetId(value) {
2462
+ return typeof value === "string" && VERTICAL_PRESET_ID_SET.has(value) ? value : null;
2463
+ }
2464
+ function getVerticalPresetDefinition(presetId) {
2465
+ const definition = VERTICAL_BOOKING_PRESETS.find((presetDefinition) => presetDefinition.id === presetId);
2466
+ if (definition === void 0) throw new Error(`Unhandled vertical preset id: ${presetId}`);
2467
+ return definition;
2468
+ }
2469
+ function createConfiguredSiteVerticalBookingConfig(verticalId, selectedPresetIds) {
2470
+ return {
2471
+ verticalId,
2472
+ selectedPresetIds
2473
+ };
2474
+ }
2475
+ function toNonEmptyVerticalPresetIds(presetIds) {
2476
+ const firstPresetId = presetIds[0];
2477
+ return firstPresetId ? [firstPresetId, ...presetIds.slice(1)] : null;
2478
+ }
2479
+ const DEFAULT_SITE_VERTICAL_BOOKING_CONFIG = {
2480
+ verticalId: null,
2481
+ selectedPresetIds: []
2482
+ };
2483
+ function appendUniqueIssue(issues, issue) {
2484
+ return issues.some((existing) => existing.code === issue.code) ? issues : [...issues, issue];
2485
+ }
2486
+ function collectRawConfigIssues(rawConfig, config) {
2487
+ const rawPresetValue = rawConfig.selectedPresetIds;
2488
+ const rawPresetIds = Array.isArray(rawPresetValue) ? rawPresetValue : [];
2489
+ let issues = [];
2490
+ if (rawConfig.verticalId != null && config.verticalId === null) issues = appendUniqueIssue(issues, {
2491
+ code: "unknown_vertical",
2492
+ rawValue: rawConfig.verticalId
2493
+ });
2494
+ if (config.verticalId === null && config.selectedPresetIds.length > 0) issues = appendUniqueIssue(issues, { code: "missing_vertical" });
2495
+ if (rawPresetValue !== void 0 && !Array.isArray(rawPresetValue) || rawPresetIds.some((presetId) => parseVerticalPresetId(presetId) === null)) issues = appendUniqueIssue(issues, {
2496
+ code: "unknown_preset",
2497
+ rawValue: rawPresetValue
2498
+ });
2499
+ if (rawConfig.verticalId != null && config.selectedPresetIds.length === 0) issues = appendUniqueIssue(issues, { code: "empty_selected_presets" });
2500
+ return issues;
2501
+ }
2502
+ function collectPresetConfigIssues(config) {
2503
+ const seenPresetIds = /* @__PURE__ */ new Set();
2504
+ const seenBehaviours = /* @__PURE__ */ new Set();
2505
+ let issues = [];
2506
+ for (const presetId of config.selectedPresetIds) {
2507
+ const presetDefinition = getVerticalPresetDefinition(presetId);
2508
+ if (seenPresetIds.has(presetId)) issues = appendUniqueIssue(issues, { code: "duplicate_preset_id" });
2509
+ seenPresetIds.add(presetId);
2510
+ if (config.verticalId !== null && presetDefinition.verticalId !== config.verticalId) issues = appendUniqueIssue(issues, { code: "preset_vertical_mismatch" });
2511
+ if (presetDefinition.availability === "future") issues = appendUniqueIssue(issues, { code: "future_preset_selected" });
2512
+ if (seenBehaviours.has(presetDefinition.canonicalBehaviour)) issues = appendUniqueIssue(issues, { code: "duplicate_canonical_behaviour" });
2513
+ seenBehaviours.add(presetDefinition.canonicalBehaviour);
2514
+ }
2515
+ return issues;
2516
+ }
2517
+ function getSiteVerticalConfigIssues(rawConfig, config) {
2518
+ let issues = collectRawConfigIssues(rawConfig, config);
2519
+ for (const issue of collectPresetConfigIssues(config)) issues = appendUniqueIssue(issues, issue);
2520
+ return issues;
2521
+ }
2522
+ function resolveSiteVerticalBookingConfig(rawConfig) {
2523
+ if (rawConfig == null) return {
2524
+ kind: "unconfigured",
2525
+ config: DEFAULT_SITE_VERTICAL_BOOKING_CONFIG
2526
+ };
2527
+ const verticalId = parseVerticalId(rawConfig.verticalId);
2528
+ const selectedPresetIds = (Array.isArray(rawConfig.selectedPresetIds) ? rawConfig.selectedPresetIds : []).map(parseVerticalPresetId).filter((presetId) => Boolean(presetId));
2529
+ const config = {
2530
+ verticalId,
2531
+ selectedPresetIds
2532
+ };
2533
+ if (rawConfig.verticalId == null && selectedPresetIds.length === 0) return {
2534
+ kind: "unconfigured",
2535
+ config: DEFAULT_SITE_VERTICAL_BOOKING_CONFIG
2536
+ };
2537
+ const issues = getSiteVerticalConfigIssues(rawConfig, config);
2538
+ if (issues.length > 0) return {
2539
+ kind: "invalid",
2540
+ config,
2541
+ issues
2542
+ };
2543
+ const nonEmptyPresetIds = toNonEmptyVerticalPresetIds(selectedPresetIds);
2544
+ const configuredConfig = verticalId !== null && nonEmptyPresetIds !== null ? createConfiguredSiteVerticalBookingConfig(verticalId, nonEmptyPresetIds) : null;
2545
+ if (configuredConfig === null) return {
2546
+ kind: "invalid",
2547
+ config,
2548
+ issues: [{ code: "empty_selected_presets" }]
2549
+ };
2550
+ return {
2551
+ kind: "configured",
2552
+ config: configuredConfig,
2553
+ selectedPresets: configuredConfig.selectedPresetIds.map(getVerticalPresetDefinition)
2554
+ };
2555
+ }
2556
+ //#endregion
2557
+ //#region ../api/src/publicEventEndpointDefinitions.ts
2558
+ const PUBLIC_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2559
+ listPublicEventCalendarWindow: {
2560
+ path: "/public/sites/{siteId}/events/calendar-window",
2561
+ method: "GET",
2562
+ revalidate: 0,
2563
+ auth: "public",
2564
+ responseKind: "json"
2565
+ },
2566
+ resolvePublicEventCheckoutReturn: {
2567
+ path: "/public/sites/{siteId}/events/checkout-return",
2568
+ method: "POST",
2569
+ tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
2570
+ auth: "public",
2571
+ responseKind: "json"
2572
+ },
2573
+ getPublicEventRegistrationOptions: {
2574
+ path: "/public/sites/{siteId}/events/registration-options",
2575
+ method: "POST",
2576
+ tags: [TAG_TEMPLATES.publicEvents, "event-registration-options"],
2577
+ auth: "public",
2578
+ responseKind: "json",
2579
+ errors: [
2580
+ "validation:invalid_input",
2581
+ "resource:not_found",
2582
+ "resource:conflict",
2583
+ "server:internal_error"
2584
+ ]
2585
+ },
2586
+ resolveEventSubroute: {
2587
+ path: "/public/sites/{siteId}/events/subroutes/resolve",
2588
+ method: "GET",
2589
+ revalidate: publicReadFreshnessSeconds("resolveEventSubroute"),
2590
+ tags: [TAG_TEMPLATES.publicEvents, "event-subroute"],
2591
+ auth: "public",
2592
+ responseKind: "json",
2593
+ errors: []
2594
+ }
2595
+ });
2596
+ //#endregion
2597
+ //#region ../api/src/publicEventCheckoutEndpointDefinitions.ts
2598
+ /**
2599
+ * The public event registration/checkout endpoints, extracted from
2600
+ * `endpoints.ts`.
2601
+ *
2602
+ * They were moved out for the same reason the `sdkMedia*` block was: adding the
2603
+ * `errors` metadata `require-api-endpoint-errors` asks of a newly-added endpoint
2604
+ * pushed `endpoints.ts` past its `max-lines` baseline. The block is spread back
2605
+ * at the exact position it occupied, so `API_ENDPOINTS` key order is unchanged.
2606
+ * (The endpoint that originally motivated the move, `checkPublicEventEligibility`,
2607
+ * was since deleted on dev by #1326.)
2608
+ */
2609
+ const PUBLIC_EVENT_CHECKOUT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2610
+ registerForEvent: {
2611
+ path: "/public/sites/{siteId}/events/register",
2612
+ method: "POST",
2613
+ tags: [TAG_TEMPLATES.publicEvents, "event-registration"],
2614
+ auth: "public",
2615
+ responseKind: "json"
2616
+ },
2617
+ createPublicEventCheckout: {
2618
+ path: "/public/sites/{siteId}/events/checkout",
2619
+ method: "POST",
2620
+ tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
2621
+ auth: "public",
2622
+ responseKind: "json"
2623
+ }
2624
+ });
2625
+ //#endregion
2626
+ //#region ../api/src/publicBookingAvailabilityEndpointDefinitions.ts
2627
+ /**
2628
+ * Public appointment reads: the service catalogue and the bookable slot grid.
2377
2629
  *
2378
- * The Secret type protects an already validated value from accidental
2379
- * disclosure; this boundary is what validates the value's wire format.
2380
- */
2381
- function parseManagementKey(raw) {
2382
- if (typeof raw !== "string" || !raw.startsWith("bld_mgmt_sk_") || raw.length === 12) return err({ kind: "invalid-management-key" });
2383
- return ok(createSecretInternal("management-key", raw));
2384
- }
2385
- function sessionStartMs(session) {
2386
- return Date.parse(session.startsAt);
2387
- }
2388
- /**
2389
- * The run's sessions that still count, in schedule order.
2630
+ * These are the reads that appointment writes invalidate. They carry canonical
2631
+ * `riverbank:*` tags because the frontend revalidate endpoint discards anything
2632
+ * else, which is why the legacy `site-{siteId}` tag they used to declare could
2633
+ * never actually be cleared (issue 1364).
2390
2634
  *
2391
- * Sessions whose start cannot be read are dropped rather than sorted to an
2392
- * arbitrary end: an unparseable date is not a date a visitor can attend.
2635
+ * Slot availability depends on both tags: the grid moves when a booking lands,
2636
+ * and again when the service definition behind it changes.
2393
2637
  */
2394
- function orderedLiveRunSessions(sessions) {
2395
- return sessions.filter((session) => session.status !== "cancelled" && Number.isFinite(sessionStartMs(session))).sort((left, right) => sessionStartMs(left) - sessionStartMs(right));
2396
- }
2638
+ const PUBLIC_BOOKING_AVAILABILITY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2639
+ getPublicBookingServices: {
2640
+ path: "/public/bookings/services",
2641
+ method: "GET",
2642
+ revalidate: publicReadFreshnessSeconds("getPublicBookingServices"),
2643
+ tags: [TAG_TEMPLATES.publicBookingServices],
2644
+ auth: "public",
2645
+ responseKind: "json"
2646
+ },
2647
+ getPublicBookingService: {
2648
+ path: "/public/bookings/services/{serviceId}",
2649
+ method: "GET",
2650
+ revalidate: publicReadFreshnessSeconds("getPublicBookingService"),
2651
+ tags: [TAG_TEMPLATES.publicBookingServices],
2652
+ auth: "public",
2653
+ responseKind: "json"
2654
+ },
2655
+ getPublicBookingResourcesReference: {
2656
+ path: "/public/bookings/resources/reference",
2657
+ method: "GET",
2658
+ revalidate: publicReadFreshnessSeconds("getPublicBookingResourcesReference"),
2659
+ tags: [TAG_TEMPLATES.publicBookingServices],
2660
+ auth: "public",
2661
+ responseKind: "json"
2662
+ },
2663
+ getPublicBookingAvailableDates: {
2664
+ path: "/public/bookings/availability/dates",
2665
+ method: "GET",
2666
+ revalidate: publicReadFreshnessSeconds("getPublicBookingAvailableDates"),
2667
+ tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
2668
+ auth: "public",
2669
+ responseKind: "json"
2670
+ },
2671
+ getPublicBookingAvailableSlots: {
2672
+ path: "/public/bookings/availability/slots",
2673
+ method: "GET",
2674
+ revalidate: publicReadFreshnessSeconds("getPublicBookingAvailableSlots"),
2675
+ tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
2676
+ auth: "public",
2677
+ responseKind: "json"
2678
+ },
2679
+ getPublicWeeklySlotAvailability: {
2680
+ path: "/public/bookings/weekly-slot-availability",
2681
+ method: "GET",
2682
+ revalidate: publicReadFreshnessSeconds("getPublicWeeklySlotAvailability"),
2683
+ tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
2684
+ auth: "public",
2685
+ responseKind: "json",
2686
+ errors: []
2687
+ }
2688
+ });
2397
2689
  //#endregion
2398
- //#region ../core/src/event-series-behaviour.ts
2399
- const WHOLE_SERIES_EVENT_PRESET_VALUES = ["course", "term"];
2400
- const SURFACE_EVENT_PRESET_VALUES = [
2401
- "class",
2402
- "course",
2403
- "term",
2404
- "workshop",
2405
- "retreat",
2406
- "performance"
2407
- ];
2690
+ //#region ../api/src/siteApiKeyEndpointDefinitions.ts
2691
+ const SITE_API_KEY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2692
+ listSiteApiKeys: {
2693
+ path: "/sites/{siteId}/api-keys",
2694
+ method: "GET",
2695
+ auth: "user",
2696
+ tags: ["site-{siteId}", "api-keys"],
2697
+ responseKind: "json"
2698
+ },
2699
+ createSiteApiKey: {
2700
+ path: "/sites/{siteId}/api-keys",
2701
+ method: "POST",
2702
+ auth: "user",
2703
+ tags: ["site-{siteId}", "api-keys"],
2704
+ responseKind: "json"
2705
+ },
2706
+ revokeSiteApiKey: {
2707
+ path: "/sites/{siteId}/api-keys/{keyId}",
2708
+ method: "DELETE",
2709
+ auth: "user",
2710
+ tags: ["site-{siteId}", "api-keys"],
2711
+ responseKind: "json"
2712
+ },
2713
+ listSitePreviewKeys: {
2714
+ path: "/sites/{siteId}/api-keys/preview",
2715
+ method: "GET",
2716
+ auth: "user",
2717
+ tags: [
2718
+ "site-{siteId}",
2719
+ "api-keys",
2720
+ "preview-key"
2721
+ ],
2722
+ errors: [],
2723
+ responseKind: "json"
2724
+ },
2725
+ createSitePreviewKey: {
2726
+ path: "/sites/{siteId}/api-keys/preview",
2727
+ method: "POST",
2728
+ auth: "user",
2729
+ tags: [
2730
+ "site-{siteId}",
2731
+ "api-keys",
2732
+ "preview-key"
2733
+ ],
2734
+ responseKind: "json"
2735
+ },
2736
+ getSiteApiKeyAccessLogs: {
2737
+ path: "/sites/{siteId}/api-keys/access-logs",
2738
+ method: "GET",
2739
+ auth: "user",
2740
+ tags: [
2741
+ "site-{siteId}",
2742
+ "api-keys",
2743
+ "access-logs"
2744
+ ],
2745
+ responseKind: "json"
2746
+ },
2747
+ listManagementKeys: {
2748
+ path: "/sites/{siteId}/api-keys/management",
2749
+ method: "GET",
2750
+ auth: "user",
2751
+ tags: [
2752
+ "site-{siteId}",
2753
+ "api-keys",
2754
+ "management-keys"
2755
+ ],
2756
+ responseKind: "json"
2757
+ },
2758
+ createManagementKey: {
2759
+ path: "/sites/{siteId}/api-keys/management",
2760
+ method: "POST",
2761
+ auth: "user",
2762
+ tags: [
2763
+ "site-{siteId}",
2764
+ "api-keys",
2765
+ "management-keys"
2766
+ ],
2767
+ responseKind: "json"
2768
+ },
2769
+ revokeManagementKey: {
2770
+ path: "/sites/{siteId}/api-keys/management",
2771
+ method: "DELETE",
2772
+ auth: "user",
2773
+ tags: [
2774
+ "site-{siteId}",
2775
+ "api-keys",
2776
+ "management-keys"
2777
+ ],
2778
+ responseKind: "json"
2779
+ }
2780
+ });
2408
2781
  //#endregion
2409
- //#region ../core/src/booking-vertical-preset.ts
2410
- const BOOKING_ENGINE_BY_BEHAVIOUR = {
2411
- class: "event_series",
2412
- workshop: "event_series",
2413
- course: "event_series",
2414
- retreat: "event_series",
2415
- performance: "event_series",
2416
- appointment: "appointment_service"
2417
- };
2418
- function verticalBookingPreset(id, verticalId, canonicalBehaviour, label, pluralLabel, description, availability = "onboarding") {
2419
- return {
2420
- id,
2421
- verticalId,
2422
- canonicalBehaviour,
2423
- engine: BOOKING_ENGINE_BY_BEHAVIOUR[canonicalBehaviour],
2424
- availability,
2425
- label,
2426
- pluralLabel,
2427
- description
2428
- };
2429
- }
2782
+ //#region ../api/src/eventManagementEndpointDefinitions.ts
2783
+ const EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2784
+ listEvents: {
2785
+ path: "/sites/{siteId}/bookings/events",
2786
+ method: "GET",
2787
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2788
+ auth: "user",
2789
+ responseKind: "json",
2790
+ errors: []
2791
+ },
2792
+ createEvent: {
2793
+ path: "/sites/{siteId}/bookings/events",
2794
+ method: "POST",
2795
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2796
+ auth: "user",
2797
+ responseKind: "json",
2798
+ errors: [
2799
+ "validation:invalid_input",
2800
+ "resource:already_exists",
2801
+ "server:internal_error"
2802
+ ]
2803
+ },
2804
+ transitionEventLifecycle: {
2805
+ path: "/sites/{siteId}/bookings/events/{eventId}/lifecycle",
2806
+ method: "POST",
2807
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2808
+ auth: "user",
2809
+ responseKind: "json",
2810
+ errors: [
2811
+ "validation:invalid_input",
2812
+ "resource:not_found",
2813
+ "resource:conflict",
2814
+ "server:internal_error"
2815
+ ]
2816
+ },
2817
+ updateEventIdentity: {
2818
+ path: "/sites/{siteId}/bookings/events/{eventId}",
2819
+ method: "PATCH",
2820
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2821
+ auth: "user",
2822
+ responseKind: "json",
2823
+ errors: [
2824
+ "validation:invalid_input",
2825
+ "resource:not_found",
2826
+ "server:internal_error"
2827
+ ]
2828
+ },
2829
+ createWholeSeriesRun: {
2830
+ path: "/sites/{siteId}/bookings/events/{eventId}/runs",
2831
+ method: "POST",
2832
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2833
+ auth: "user",
2834
+ responseKind: "json",
2835
+ errors: [
2836
+ "validation:invalid_input",
2837
+ "resource:not_found",
2838
+ "resource:already_exists",
2839
+ "server:internal_error"
2840
+ ]
2841
+ },
2842
+ updateWholeSeriesRun: {
2843
+ path: "/sites/{siteId}/bookings/events/{eventId}/runs/{runId}",
2844
+ method: "PATCH",
2845
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2846
+ auth: "user",
2847
+ responseKind: "json",
2848
+ errors: [
2849
+ "validation:invalid_input",
2850
+ "resource:not_found",
2851
+ "resource:conflict",
2852
+ "server:internal_error"
2853
+ ]
2854
+ },
2855
+ cancelWholeSeriesRun: {
2856
+ path: "/sites/{siteId}/bookings/events/{eventId}/runs/{runId}/cancellation",
2857
+ method: "POST",
2858
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2859
+ auth: "user",
2860
+ responseKind: "json",
2861
+ errors: [
2862
+ "validation:invalid_input",
2863
+ "resource:not_found",
2864
+ "resource:conflict",
2865
+ "server:internal_error"
2866
+ ]
2867
+ }
2868
+ });
2430
2869
  //#endregion
2431
- //#region ../core/src/booking-vertical-definitions.ts
2432
- const VERTICAL_DEFINITIONS = [
2433
- {
2434
- id: "yoga_wellness",
2435
- label: "Yoga, meditation or mindfulness",
2436
- description: "Yoga studios, wellness teachers, and movement practitioners."
2870
+ //#region ../api/src/giftingEndpointDefinitions.ts
2871
+ /**
2872
+ * Gifting endpoints (gift cards & vouchers) — the dashboard surface that manages
2873
+ * offers, gifts and settings, plus the public buy/claim surface.
2874
+ */
2875
+ const GIFTING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2876
+ createGiftingOffer: {
2877
+ path: "/sites/{siteId}/gifting/offers",
2878
+ method: "POST",
2879
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2880
+ auth: "user",
2881
+ responseKind: "json",
2882
+ errors: [
2883
+ "validation:invalid_input",
2884
+ "auth:forbidden",
2885
+ "server:internal_error"
2886
+ ]
2437
2887
  },
2438
- {
2439
- id: "fitness_pt",
2440
- label: "Fitness and personal training",
2441
- description: "Personal trainers, group training, and fitness programmes."
2888
+ updateGiftingOffer: {
2889
+ path: "/sites/{siteId}/gifting/offers/{offerId}",
2890
+ method: "PATCH",
2891
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2892
+ auth: "user",
2893
+ responseKind: "json",
2894
+ errors: [
2895
+ "validation:invalid_input",
2896
+ "auth:forbidden",
2897
+ "resource:not_found",
2898
+ "resource:conflict",
2899
+ "server:internal_error"
2900
+ ]
2442
2901
  },
2443
- {
2444
- id: "dance_school",
2445
- label: "Dance school",
2446
- description: "Dance classes, courses or terms, workshops, private lessons, and shows."
2902
+ deleteGiftingOffer: {
2903
+ path: "/sites/{siteId}/gifting/offers/{offerId}",
2904
+ method: "DELETE",
2905
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2906
+ auth: "user",
2907
+ responseKind: "json",
2908
+ errors: [
2909
+ "auth:forbidden",
2910
+ "resource:not_found",
2911
+ "resource:conflict",
2912
+ "server:internal_error"
2913
+ ]
2447
2914
  },
2448
- {
2449
- id: "physiotherapy_physical_health",
2450
- label: "Physiotherapy & physical health",
2451
- description: "Physiotherapists, chiropractors, osteopaths, sports therapists, and rehabilitation practitioners."
2915
+ duplicateGiftingOffer: {
2916
+ path: "/sites/{siteId}/gifting/offers/{offerId}/duplicate",
2917
+ method: "POST",
2918
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2919
+ auth: "user",
2920
+ responseKind: "json",
2921
+ errors: [
2922
+ "auth:forbidden",
2923
+ "resource:not_found",
2924
+ "server:internal_error"
2925
+ ]
2452
2926
  },
2453
- {
2454
- id: "massage_bodywork",
2455
- label: "Massage & bodywork",
2456
- description: "Massage therapists, bodyworkers, and somatic practitioners."
2927
+ reorderGiftingOffers: {
2928
+ path: "/sites/{siteId}/gifting/offers/order",
2929
+ method: "PUT",
2930
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2931
+ auth: "user",
2932
+ responseKind: "json",
2933
+ errors: [
2934
+ "validation:invalid_input",
2935
+ "auth:forbidden",
2936
+ "resource:not_found",
2937
+ "server:internal_error"
2938
+ ]
2457
2939
  },
2458
- {
2459
- id: "therapist_practitioner",
2460
- label: "Therapist or practitioner",
2461
- description: "Appointments, sessions, consultations, and occasional group work."
2940
+ sendGift: {
2941
+ path: "/sites/{siteId}/gifting/gifts",
2942
+ method: "POST",
2943
+ tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
2944
+ auth: "user",
2945
+ demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
2946
+ responseKind: "json",
2947
+ errors: [
2948
+ "validation:invalid_input",
2949
+ "auth:forbidden",
2950
+ "resource:not_found",
2951
+ "server:internal_error"
2952
+ ]
2462
2953
  },
2463
- {
2464
- id: "beauty_spa",
2465
- label: "Beauty or spa",
2466
- description: "Treatment-led businesses with optional workshops."
2954
+ resendGift: {
2955
+ path: "/sites/{siteId}/gifting/gifts/{giftId}/resend",
2956
+ method: "POST",
2957
+ tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
2958
+ auth: "user",
2959
+ demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
2960
+ responseKind: "json",
2961
+ errors: [
2962
+ "auth:forbidden",
2963
+ "resource:not_found",
2964
+ "resource:conflict",
2965
+ "server:internal_error"
2966
+ ]
2467
2967
  },
2468
- {
2469
- id: "consultant_coach",
2470
- label: "Consultant or coach",
2471
- description: "One-to-one calls, workshops, courses, and retreats."
2968
+ correctGiftEmailAndResend: {
2969
+ path: "/sites/{siteId}/gifting/gifts/{giftId}/correct-email-and-resend",
2970
+ method: "POST",
2971
+ tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
2972
+ auth: "user",
2973
+ demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
2974
+ responseKind: "json",
2975
+ errors: [
2976
+ "validation:invalid_input",
2977
+ "auth:forbidden",
2978
+ "resource:not_found",
2979
+ "resource:conflict",
2980
+ "server:internal_error"
2981
+ ]
2472
2982
  },
2473
- {
2474
- id: "training_provider",
2475
- label: "Training provider",
2476
- description: "Courses, workshops, classes, appointments, and residential training."
2983
+ updateGiftingSettings: {
2984
+ path: "/sites/{siteId}/gifting/settings",
2985
+ method: "PATCH",
2986
+ tags: [
2987
+ "site-{siteId}",
2988
+ "gifting-offers-{siteId}",
2989
+ "gifting-gifts-{siteId}"
2990
+ ],
2991
+ auth: "user",
2992
+ responseKind: "json",
2993
+ errors: [
2994
+ "validation:invalid_input",
2995
+ "auth:forbidden",
2996
+ "server:internal_error"
2997
+ ]
2998
+ },
2999
+ listPublicGiftingOffers: {
3000
+ path: "/public/sites/{siteId}/gifting/offers",
3001
+ method: "GET",
3002
+ revalidate: 300,
3003
+ tags: [TAG_TEMPLATES.publicGifting],
3004
+ errors: [],
3005
+ auth: "public",
3006
+ responseKind: "json"
3007
+ },
3008
+ createPublicGiftingCheckout: {
3009
+ path: "/public/sites/{siteId}/gifting/checkout",
3010
+ method: "POST",
3011
+ tags: [TAG_TEMPLATES.publicGifting],
3012
+ errors: [],
3013
+ auth: "public",
3014
+ responseKind: "json"
3015
+ },
3016
+ claimGiftCardPublic: {
3017
+ path: "/public/sites/{siteId}/gifting/claim",
3018
+ method: "POST",
3019
+ tags: [TAG_TEMPLATES.publicGifting],
3020
+ errors: [],
3021
+ auth: "public",
3022
+ responseKind: "json"
3023
+ }
3024
+ });
3025
+ //#endregion
3026
+ //#region ../api/src/sdkEndpointDefinitions.ts
3027
+ /** SDK management endpoints that do not belong to a CRUD resource family. */
3028
+ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3029
+ sdkUpdateSettings: {
3030
+ path: "/sdk/{siteId}/settings",
3031
+ method: "PATCH",
3032
+ auth: "service",
3033
+ responseKind: "json",
3034
+ errors: ["validation:invalid_input", "server:internal_error"]
3035
+ },
3036
+ sdkConfigureBookingVertical: {
3037
+ path: "/sdk/{siteId}/settings/booking/vertical",
3038
+ method: "POST",
3039
+ auth: "service",
3040
+ responseKind: "json"
3041
+ },
3042
+ sdkSetupRevalidationWebhook: {
3043
+ path: "/sdk/{siteId}/webhooks/revalidation/setup",
3044
+ method: "POST",
3045
+ auth: "service",
3046
+ responseKind: "json",
3047
+ errors: ["validation:invalid_input", "server:internal_error"]
3048
+ }
3049
+ });
3050
+ //#endregion
3051
+ //#region ../api/src/sdkMediaEndpointDefinitions.ts
3052
+ /** SDK media lookup, listing, deletion and upload endpoints (management-key authenticated). */
3053
+ const SDK_MEDIA_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3054
+ sdkMediaExists: {
3055
+ path: "/sdk/{siteId}/media/exists",
3056
+ method: "POST",
3057
+ errors: ["validation:invalid_input"],
3058
+ auth: "service",
3059
+ responseKind: "json"
3060
+ },
3061
+ sdkMediaFindByChecksum: {
3062
+ path: "/sdk/{siteId}/media/find-by-checksum",
3063
+ method: "POST",
3064
+ errors: ["server:internal_error"],
3065
+ auth: "service",
3066
+ responseKind: "json"
2477
3067
  },
2478
- {
2479
- id: "retreat_centre",
2480
- label: "Retreat centre",
2481
- description: "Residential retreats, workshops, classes, courses, and private sessions."
3068
+ sdkMediaResolve: {
3069
+ path: "/sdk/{siteId}/media/resolve",
3070
+ method: "POST",
3071
+ errors: ["server:internal_error"],
3072
+ auth: "service",
3073
+ responseKind: "json"
2482
3074
  },
2483
- {
2484
- id: "music_school",
2485
- label: "Music teacher or school",
2486
- description: "Lessons, classes, terms, workshops, and performances."
3075
+ sdkMediaList: {
3076
+ path: "/sdk/{siteId}/media",
3077
+ method: "GET",
3078
+ auth: "service",
3079
+ errors: [],
3080
+ responseKind: "json"
2487
3081
  },
2488
- {
2489
- id: "voice_singing",
2490
- label: "Voice & Singing",
2491
- description: "Singing lessons, choirs, voice circles, workshops, and performances."
3082
+ sdkMediaDownload: {
3083
+ path: "/sdk/{siteId}/media/{identifier}/download",
3084
+ method: "GET",
3085
+ auth: "service",
3086
+ errors: [],
3087
+ responseKind: "json"
2492
3088
  },
2493
- {
2494
- id: "performing_arts_school",
2495
- label: "Theatre or performing arts school",
2496
- description: "Classes, terms, workshops, performances, and lessons."
3089
+ sdkMediaDeletePreview: {
3090
+ path: "/sdk/{siteId}/media/{identifier}/delete-preview",
3091
+ method: "POST",
3092
+ auth: "service",
3093
+ errors: [],
3094
+ responseKind: "json"
2497
3095
  },
2498
- {
2499
- id: "community_wellbeing",
2500
- label: "Community or wellbeing centre",
2501
- description: "Groups, workshops, courses, appointments, and retreats."
3096
+ sdkMediaDeleteApply: {
3097
+ path: "/sdk/{siteId}/media/{identifier}",
3098
+ method: "DELETE",
3099
+ auth: "service",
3100
+ errors: [],
3101
+ responseKind: "json"
2502
3102
  },
2503
- {
2504
- id: "venue_event_space",
2505
- label: "Venue or event space",
2506
- description: "Room, resource, or access bookings. Depends on the future capacity-booking domain."
3103
+ sdkMediaUploadUrl: {
3104
+ path: "/sdk/{siteId}/media/upload-url",
3105
+ method: "POST",
3106
+ errors: [
3107
+ "resource:not_found",
3108
+ "validation:invalid_input",
3109
+ "resource:conflict",
3110
+ "validation:payload_too_large",
3111
+ "billing:plan_limit_exceeded",
3112
+ "server:internal_error"
3113
+ ],
3114
+ auth: "service",
3115
+ responseKind: "json"
3116
+ },
3117
+ sdkMediaUploadFinalize: {
3118
+ path: "/sdk/{siteId}/media/upload",
3119
+ method: "POST",
3120
+ errors: [
3121
+ "resource:not_found",
3122
+ "validation:invalid_input",
3123
+ "resource:conflict",
3124
+ "validation:payload_too_large",
3125
+ "billing:plan_limit_exceeded",
3126
+ "server:internal_error"
3127
+ ],
3128
+ auth: "service",
3129
+ responseKind: "json"
2507
3130
  }
2508
- ];
2509
- VERTICAL_DEFINITIONS.map(({ id }) => id);
3131
+ });
2510
3132
  //#endregion
2511
- //#region ../core/src/booking-vertical-catalog.ts
2512
- const VERTICAL_BOOKING_PRESETS = [
2513
- verticalBookingPreset("yoga_wellness.classes", "yoga_wellness", "class", "Class", "Classes", "Regular scheduled classes and group sessions."),
2514
- verticalBookingPreset("yoga_wellness.workshops", "yoga_wellness", "workshop", "Workshop", "Workshops", "One-off workshops, day retreats, or special sessions."),
2515
- verticalBookingPreset("yoga_wellness.courses", "yoga_wellness", "course", "Course", "Courses", "Fixed-schedule courses or multi-session groups."),
2516
- verticalBookingPreset("yoga_wellness.retreats", "yoga_wellness", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats and immersive experiences."),
2517
- verticalBookingPreset("yoga_wellness.appointments", "yoga_wellness", "appointment", "Appointment", "Appointments", "Private sessions, consultations, or one-to-one support."),
2518
- verticalBookingPreset("fitness_pt.group_training", "fitness_pt", "class", "Group training", "Group training", "Regular scheduled classes and group sessions."),
2519
- verticalBookingPreset("fitness_pt.personal_training", "fitness_pt", "appointment", "Personal training", "Personal training", "One-to-one sessions booked from your availability."),
2520
- verticalBookingPreset("fitness_pt.programmes", "fitness_pt", "course", "Programme", "Programmes", "Multi-session programmes like bootcamps or courses."),
2521
- verticalBookingPreset("fitness_pt.workshops", "fitness_pt", "workshop", "Workshop", "Workshops", "One-off workshops, events, or special sessions."),
2522
- verticalBookingPreset("fitness_pt.retreats", "fitness_pt", "retreat", "Retreat", "Retreats", "Multi-day or residential fitness experiences."),
2523
- verticalBookingPreset("dance_school.classes", "dance_school", "class", "Class", "Classes", "Regular group classes and weekly sessions."),
2524
- {
2525
- ...verticalBookingPreset("dance_school.terms", "dance_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date course groups."),
2526
- eventPresetOverride: "term"
3133
+ //#region ../api/src/publicReadEndpointDefinitions.ts
3134
+ /**
3135
+ * Direct public reads governed by the canonical public-read freshness policy.
3136
+ * Keeping these definitions together makes the endpoint identity used to
3137
+ * derive freshness reviewable beside its route and tags.
3138
+ */
3139
+ const PUBLIC_READ_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3140
+ getPublishedEntryPreview: {
3141
+ path: "/public/content/{siteId}/{type}/{slug}/preview",
3142
+ method: "GET",
3143
+ revalidate: publicReadFreshnessSeconds("getPublishedEntryPreview"),
3144
+ tags: ["content-{siteId}-{type}-{slug}"],
3145
+ auth: "public",
3146
+ responseKind: "json"
2527
3147
  },
2528
- verticalBookingPreset("dance_school.courses", "dance_school", "course", "Course", "Courses", "Fixed-schedule courses or multi-session groups.", "settings"),
2529
- verticalBookingPreset("dance_school.workshops", "dance_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2530
- verticalBookingPreset("dance_school.private_lessons", "dance_school", "appointment", "Private lesson", "Private lessons", "One-to-one lessons or coaching sessions."),
2531
- verticalBookingPreset("dance_school.shows", "dance_school", "performance", "Show", "Shows", "Ticketed performances, showcases, and recitals."),
2532
- verticalBookingPreset("physiotherapy_physical_health.appointments", "physiotherapy_physical_health", "appointment", "Appointment", "Appointments", "Assessments, treatments, follow-ups, and rehabilitation sessions."),
2533
- verticalBookingPreset("physiotherapy_physical_health.rehab_classes", "physiotherapy_physical_health", "class", "Rehab class", "Rehab classes", "Regular rehabilitation, mobility, strength, or recovery classes."),
2534
- verticalBookingPreset("physiotherapy_physical_health.workshops", "physiotherapy_physical_health", "workshop", "Workshop", "Workshops", "One-off workshops, talks, or practical group sessions."),
2535
- verticalBookingPreset("massage_bodywork.treatments", "massage_bodywork", "appointment", "Treatment", "Treatments", "Massage treatments, bodywork sessions, and one-to-one appointments."),
2536
- verticalBookingPreset("massage_bodywork.group_sessions", "massage_bodywork", "class", "Group session", "Group sessions", "Regular bodywork, somatic movement, or self-massage sessions."),
2537
- verticalBookingPreset("massage_bodywork.workshops", "massage_bodywork", "workshop", "Workshop", "Workshops", "One-off bodywork, self-massage, or somatic practice workshops."),
2538
- verticalBookingPreset("therapist_practitioner.appointments", "therapist_practitioner", "appointment", "Appointment", "Appointments", "Sessions, consultations, treatments, or assessments."),
2539
- verticalBookingPreset("therapist_practitioner.workshops", "therapist_practitioner", "workshop", "Workshop", "Workshops", "One-off group workshops, talks, or wellbeing sessions."),
2540
- verticalBookingPreset("therapist_practitioner.retreats", "therapist_practitioner", "retreat", "Retreat", "Retreats", "Multi-day retreats or immersive group experiences."),
2541
- verticalBookingPreset("beauty_spa.appointments", "beauty_spa", "appointment", "Appointment", "Appointments", "Treatments, consultations, services, and sessions."),
2542
- verticalBookingPreset("beauty_spa.workshops", "beauty_spa", "workshop", "Workshop", "Workshops", "Skincare workshops, makeup lessons, demos, or group events."),
2543
- verticalBookingPreset("consultant_coach.appointments", "consultant_coach", "appointment", "Appointment", "Appointments", "One-to-one calls, consultations, reviews, or coaching sessions."),
2544
- verticalBookingPreset("consultant_coach.workshops", "consultant_coach", "workshop", "Workshop", "Workshops", "One-off group sessions, webinars, or strategy days."),
2545
- verticalBookingPreset("consultant_coach.courses", "consultant_coach", "course", "Course", "Courses", "Fixed-schedule cohorts or multi-session group programmes."),
2546
- verticalBookingPreset("consultant_coach.retreats", "consultant_coach", "retreat", "Retreat", "Retreats", "Multi-day coaching retreats or immersive experiences."),
2547
- verticalBookingPreset("training_provider.courses", "training_provider", "course", "Course", "Courses", "Fixed-schedule courses, cohorts, and multi-session training."),
2548
- verticalBookingPreset("training_provider.workshops", "training_provider", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, and short training sessions."),
2549
- verticalBookingPreset("training_provider.appointments", "training_provider", "appointment", "Appointment", "Appointments", "Consultations, assessments, mentoring, or office hours."),
2550
- verticalBookingPreset("training_provider.classes", "training_provider", "class", "Class", "Classes", "Regular classes, sessions, or groups.", "settings"),
2551
- verticalBookingPreset("training_provider.residentials", "training_provider", "retreat", "Residential", "Residentials", "Residential courses or multi-day training experiences.", "settings"),
2552
- verticalBookingPreset("retreat_centre.retreats", "retreat_centre", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats with richer booking needs."),
2553
- verticalBookingPreset("retreat_centre.workshops", "retreat_centre", "workshop", "Workshop", "Workshops", "One-day retreats, ceremonies, workshops, or special events."),
2554
- verticalBookingPreset("retreat_centre.classes", "retreat_centre", "class", "Class", "Classes", "Regular sessions like guided meditations or group practices."),
2555
- verticalBookingPreset("retreat_centre.courses", "retreat_centre", "course", "Course", "Courses", "Fixed-schedule programmes or multi-session experiences."),
2556
- verticalBookingPreset("retreat_centre.appointments", "retreat_centre", "appointment", "Appointment", "Appointments", "Private sessions, therapies, consultations, or add-ons."),
2557
- verticalBookingPreset("music_school.lessons", "music_school", "appointment", "Lesson", "Lessons", "One-to-one lessons, consultations, or coaching sessions."),
2558
- verticalBookingPreset("music_school.classes", "music_school", "class", "Class", "Classes", "Regular group classes, ensembles, or recurring sessions."),
2559
- {
2560
- ...verticalBookingPreset("music_school.terms", "music_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date group courses."),
2561
- eventPresetOverride: "term"
3148
+ listPublishedEntries: {
3149
+ path: "/public/content/{siteId}/{type}/entries",
3150
+ method: "GET",
3151
+ revalidate: publicReadFreshnessSeconds("listPublishedEntries"),
3152
+ tags: ["content-{siteId}-{type}"],
3153
+ auth: "public",
3154
+ responseKind: "json"
2562
3155
  },
2563
- verticalBookingPreset("music_school.workshops", "music_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2564
- verticalBookingPreset("music_school.performances", "music_school", "performance", "Performance", "Performances", "Concerts, recitals, showcases, or ticketed performances."),
2565
- verticalBookingPreset("voice_singing.lessons", "voice_singing", "appointment", "Voice session", "Voice sessions", "One-to-one singing lessons, voice coaching, or private practice sessions."),
2566
- verticalBookingPreset("voice_singing.classes", "voice_singing", "class", "Ongoing weekly session", "Ongoing weekly sessions", "Regular choirs, voice circles, community singing, or group practice."),
2567
- {
2568
- ...verticalBookingPreset("voice_singing.terms", "voice_singing", "course", "Singing term", "Singing terms", "Multi-week choir terms, voice courses, or fixed-date group programmes."),
2569
- eventPresetOverride: "term"
3156
+ getPublicFormById: {
3157
+ path: "/public/forms/{formId}",
3158
+ method: "GET",
3159
+ revalidate: publicReadFreshnessSeconds("getPublicFormById"),
3160
+ tags: [TAG_TEMPLATES.formById],
3161
+ auth: "public",
3162
+ responseKind: "json"
2570
3163
  },
2571
- verticalBookingPreset("voice_singing.workshops", "voice_singing", "workshop", "Workshop", "Workshops", "One-off singing workshops, voice journeys, or devotional singing sessions."),
2572
- verticalBookingPreset("voice_singing.performances", "voice_singing", "performance", "Performance", "Performances", "Choir concerts, sharing events, recitals, or community performances."),
2573
- verticalBookingPreset("performing_arts_school.classes", "performing_arts_school", "class", "Class", "Classes", "Regular group classes, rehearsals, or weekly sessions."),
2574
- {
2575
- ...verticalBookingPreset("performing_arts_school.terms", "performing_arts_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date course groups."),
2576
- eventPresetOverride: "term"
3164
+ listPublicEvents: {
3165
+ path: "/public/sites/{siteId}/events",
3166
+ method: "GET",
3167
+ revalidate: publicReadFreshnessSeconds("listPublicEvents"),
3168
+ tags: [TAG_TEMPLATES.publicEvents],
3169
+ auth: "public",
3170
+ responseKind: "json"
2577
3171
  },
2578
- verticalBookingPreset("performing_arts_school.workshops", "performing_arts_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2579
- verticalBookingPreset("performing_arts_school.performances", "performing_arts_school", "performance", "Performance", "Performances", "Shows, showcases, productions, or ticketed performances."),
2580
- verticalBookingPreset("performing_arts_school.lessons", "performing_arts_school", "appointment", "Lesson", "Lessons", "One-to-one lessons, coaching, or audition prep."),
2581
- verticalBookingPreset("community_wellbeing.classes", "community_wellbeing", "class", "Class", "Classes", "Regular groups, sessions, practices, or weekly activities."),
2582
- verticalBookingPreset("community_wellbeing.workshops", "community_wellbeing", "workshop", "Workshop", "Workshops", "One-off workshops, talks, events, or gatherings."),
2583
- verticalBookingPreset("community_wellbeing.courses", "community_wellbeing", "course", "Course", "Courses", "Fixed-schedule courses, study groups, or multi-session programmes."),
2584
- verticalBookingPreset("community_wellbeing.appointments", "community_wellbeing", "appointment", "Appointment", "Appointments", "One-to-one appointments, consultations, or support sessions."),
2585
- verticalBookingPreset("community_wellbeing.retreats", "community_wellbeing", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats."),
2586
- verticalBookingPreset("venue_event_space.resource_hire", "venue_event_space", "appointment", "Hire slot", "Hire slots", "Future room, venue, and resource hire bookings.", "future")
2587
- ];
2588
- //#endregion
2589
- //#region ../core/src/booking-vertical-config.ts
2590
- const VERTICAL_ID_SET = new Set(VERTICAL_DEFINITIONS.map(({ id }) => id));
2591
- const VERTICAL_PRESET_ID_SET = new Set(VERTICAL_BOOKING_PRESETS.map((presetDefinition) => presetDefinition.id));
2592
- function parseVerticalId(value) {
2593
- return typeof value === "string" && VERTICAL_ID_SET.has(value) ? value : null;
2594
- }
2595
- function parseVerticalPresetId(value) {
2596
- return typeof value === "string" && VERTICAL_PRESET_ID_SET.has(value) ? value : null;
2597
- }
2598
- function getVerticalPresetDefinition(presetId) {
2599
- const definition = VERTICAL_BOOKING_PRESETS.find((presetDefinition) => presetDefinition.id === presetId);
2600
- if (definition === void 0) throw new Error(`Unhandled vertical preset id: ${presetId}`);
2601
- return definition;
2602
- }
2603
- function createConfiguredSiteVerticalBookingConfig(verticalId, selectedPresetIds) {
2604
- return {
2605
- verticalId,
2606
- selectedPresetIds
2607
- };
2608
- }
2609
- function toNonEmptyVerticalPresetIds(presetIds) {
2610
- const firstPresetId = presetIds[0];
2611
- return firstPresetId ? [firstPresetId, ...presetIds.slice(1)] : null;
2612
- }
2613
- const DEFAULT_SITE_VERTICAL_BOOKING_CONFIG = {
2614
- verticalId: null,
2615
- selectedPresetIds: []
2616
- };
2617
- function appendUniqueIssue(issues, issue) {
2618
- return issues.some((existing) => existing.code === issue.code) ? issues : [...issues, issue];
2619
- }
2620
- function collectRawConfigIssues(rawConfig, config) {
2621
- const rawPresetValue = rawConfig.selectedPresetIds;
2622
- const rawPresetIds = Array.isArray(rawPresetValue) ? rawPresetValue : [];
2623
- let issues = [];
2624
- if (rawConfig.verticalId != null && config.verticalId === null) issues = appendUniqueIssue(issues, {
2625
- code: "unknown_vertical",
2626
- rawValue: rawConfig.verticalId
2627
- });
2628
- if (config.verticalId === null && config.selectedPresetIds.length > 0) issues = appendUniqueIssue(issues, { code: "missing_vertical" });
2629
- if (rawPresetValue !== void 0 && !Array.isArray(rawPresetValue) || rawPresetIds.some((presetId) => parseVerticalPresetId(presetId) === null)) issues = appendUniqueIssue(issues, {
2630
- code: "unknown_preset",
2631
- rawValue: rawPresetValue
2632
- });
2633
- if (rawConfig.verticalId != null && config.selectedPresetIds.length === 0) issues = appendUniqueIssue(issues, { code: "empty_selected_presets" });
2634
- return issues;
2635
- }
2636
- function collectPresetConfigIssues(config) {
2637
- const seenPresetIds = /* @__PURE__ */ new Set();
2638
- const seenBehaviours = /* @__PURE__ */ new Set();
2639
- let issues = [];
2640
- for (const presetId of config.selectedPresetIds) {
2641
- const presetDefinition = getVerticalPresetDefinition(presetId);
2642
- if (seenPresetIds.has(presetId)) issues = appendUniqueIssue(issues, { code: "duplicate_preset_id" });
2643
- seenPresetIds.add(presetId);
2644
- if (config.verticalId !== null && presetDefinition.verticalId !== config.verticalId) issues = appendUniqueIssue(issues, { code: "preset_vertical_mismatch" });
2645
- if (presetDefinition.availability === "future") issues = appendUniqueIssue(issues, { code: "future_preset_selected" });
2646
- if (seenBehaviours.has(presetDefinition.canonicalBehaviour)) issues = appendUniqueIssue(issues, { code: "duplicate_canonical_behaviour" });
2647
- seenBehaviours.add(presetDefinition.canonicalBehaviour);
3172
+ listPublicTeamMembers: {
3173
+ path: "/public/sites/{siteId}/team-members",
3174
+ method: "GET",
3175
+ revalidate: publicReadFreshnessSeconds("listPublicTeamMembers"),
3176
+ tags: [TAG_TEMPLATES.publicTeamMembers],
3177
+ errors: [],
3178
+ auth: "public",
3179
+ responseKind: "json"
3180
+ },
3181
+ listPublicPasses: {
3182
+ path: "/public/sites/{siteId}/bookings/products/passes",
3183
+ method: "GET",
3184
+ revalidate: publicReadFreshnessSeconds("listPublicPasses"),
3185
+ tags: [TAG_TEMPLATES.publicPasses],
3186
+ auth: "public",
3187
+ responseKind: "json"
3188
+ },
3189
+ listPublicMemberships: {
3190
+ path: "/public/sites/{siteId}/bookings/products/memberships",
3191
+ method: "GET",
3192
+ revalidate: publicReadFreshnessSeconds("listPublicMemberships"),
3193
+ tags: [TAG_TEMPLATES.publicMemberships],
3194
+ auth: "public",
3195
+ responseKind: "json"
3196
+ },
3197
+ listPublicProducts: {
3198
+ path: "/public/sites/{siteId}/products",
3199
+ method: "GET",
3200
+ revalidate: publicReadFreshnessSeconds("listPublicProducts"),
3201
+ tags: [TAG_TEMPLATES.publicProducts],
3202
+ errors: [],
3203
+ auth: "public",
3204
+ responseKind: "json"
2648
3205
  }
2649
- return issues;
2650
- }
2651
- function getSiteVerticalConfigIssues(rawConfig, config) {
2652
- let issues = collectRawConfigIssues(rawConfig, config);
2653
- for (const issue of collectPresetConfigIssues(config)) issues = appendUniqueIssue(issues, issue);
2654
- return issues;
2655
- }
2656
- function resolveSiteVerticalBookingConfig(rawConfig) {
2657
- if (rawConfig == null) return {
2658
- kind: "unconfigured",
2659
- config: DEFAULT_SITE_VERTICAL_BOOKING_CONFIG
2660
- };
2661
- const verticalId = parseVerticalId(rawConfig.verticalId);
2662
- const selectedPresetIds = (Array.isArray(rawConfig.selectedPresetIds) ? rawConfig.selectedPresetIds : []).map(parseVerticalPresetId).filter((presetId) => Boolean(presetId));
2663
- const config = {
2664
- verticalId,
2665
- selectedPresetIds
2666
- };
2667
- if (rawConfig.verticalId == null && selectedPresetIds.length === 0) return {
2668
- kind: "unconfigured",
2669
- config: DEFAULT_SITE_VERTICAL_BOOKING_CONFIG
2670
- };
2671
- const issues = getSiteVerticalConfigIssues(rawConfig, config);
2672
- if (issues.length > 0) return {
2673
- kind: "invalid",
2674
- config,
2675
- issues
2676
- };
2677
- const nonEmptyPresetIds = toNonEmptyVerticalPresetIds(selectedPresetIds);
2678
- const configuredConfig = verticalId !== null && nonEmptyPresetIds !== null ? createConfiguredSiteVerticalBookingConfig(verticalId, nonEmptyPresetIds) : null;
2679
- if (configuredConfig === null) return {
2680
- kind: "invalid",
2681
- config,
2682
- issues: [{ code: "empty_selected_presets" }]
2683
- };
2684
- return {
2685
- kind: "configured",
2686
- config: configuredConfig,
2687
- selectedPresets: configuredConfig.selectedPresetIds.map(getVerticalPresetDefinition)
2688
- };
3206
+ });
3207
+ ({
3208
+ ...ACCOUNT_ENDPOINT_DEFINITIONS,
3209
+ ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
3210
+ ...GIFTING_ENDPOINT_DEFINITIONS,
3211
+ ...PUBLIC_READ_ENDPOINT_DEFINITIONS
3212
+ }), TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, { ...SITE_API_KEY_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.site, TAG_TEMPLATES.siteBookingSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteBookingSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteEmailSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteEmailSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteEmailSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteEmailSettings, {
3213
+ ...NEWSLETTER_ENDPOINT_DEFINITIONS,
3214
+ ...APPOINTMENT_ENDPOINT_DEFINITIONS,
3215
+ ...EVENT_SERIES_REGISTRATION_ENDPOINT_DEFINITIONS
3216
+ }, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteById, TAG_TEMPLATES.siteBySlug, TAG_TEMPLATES.siteByDomain, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.workflowRunById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.routeById, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.site, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.site, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.navigationItemById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteNavigation, TAG_TEMPLATES.navigationMenuById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteSettings, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.siteTheme, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteCollection, TAG_TEMPLATES.pageSurfaceById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.pageById, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteRedirects, TAG_TEMPLATES.siteRouteOutcomes, TAG_TEMPLATES.siteRoutableContent, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, TAG_TEMPLATES.formSubmissionsBySlug, TAG_TEMPLATES.siteForms, { ...PUBLIC_BOOKING_AVAILABILITY_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.publicEventFilters, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicShop, TAG_TEMPLATES.publicCheckout, TAG_TEMPLATES.siteRoutableContent, { ...PUBLIC_EVENT_CHECKOUT_ENDPOINT_DEFINITIONS }, TAG_TEMPLATES.publicEvents, TAG_TEMPLATES.publicEvents, { ...EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS }, { ...SDK_MEDIA_ENDPOINT_DEFINITIONS }, { ...SDK_ENDPOINT_DEFINITIONS }, { ...SDK_ENTRY_READ_ENDPOINTS };
3217
+ z.object({ refreshToken: z.string().min(1).max(4096) }).strict();
3218
+ z.object({
3219
+ accessToken: z.string().min(1).max(4096),
3220
+ refreshToken: z.string().min(1).max(4096)
3221
+ }).strict();
3222
+ const ACCOUNT_SITE_CURSOR_MAX_LENGTH = 2048;
3223
+ const ACCOUNT_SITE_TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?(Z|[+-]\d{2}:\d{2})$/;
3224
+ /** Strict, injection-safe PostgreSQL/RFC3339 timestamp text used in cursors. */
3225
+ function isAccountSiteTimestamp(value) {
3226
+ if (typeof value !== "string") return false;
3227
+ const match = ACCOUNT_SITE_TIMESTAMP_PATTERN.exec(value);
3228
+ if (!match) return false;
3229
+ const [, year, month, day, hour, minute, second, , offset] = match;
3230
+ const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
3231
+ const offsetValue = offset ?? "";
3232
+ const offsetIsValid = offsetValue === "Z" || (() => {
3233
+ const offsetMatch = /^(?:[+-])(\d{2}):(\d{2})$/.exec(offsetValue);
3234
+ if (!offsetMatch) return false;
3235
+ return Number(offsetMatch[1]) <= 23 && Number(offsetMatch[2]) <= 59;
3236
+ })();
3237
+ return date.getUTCFullYear() === Number(year) && date.getUTCMonth() === Number(month) - 1 && date.getUTCDate() === Number(day) && Number(hour) <= 23 && Number(minute) <= 59 && Number(second) <= 59 && offsetIsValid;
2689
3238
  }
3239
+ const accountSiteTimestampSchema = z.string().refine(isAccountSiteTimestamp, { message: "Expected a strict account site timestamp" });
3240
+ z.enum([
3241
+ "managed",
3242
+ "headless_cms",
3243
+ "headless_full"
3244
+ ]);
3245
+ z.object({ title: z.string().trim().min(1).max(120) });
3246
+ const abandonedStandardKeyIdsSchema = z.object({
3247
+ live: z.string().uuid().optional(),
3248
+ management: z.string().uuid().optional()
3249
+ }).strict();
3250
+ const abandonedPreviewKeyIdsSchema = z.object({
3251
+ live: z.string().uuid().optional(),
3252
+ management: z.string().uuid().optional(),
3253
+ preview: z.string().uuid().optional()
3254
+ }).strict();
3255
+ z.discriminatedUnion("operation", [
3256
+ z.object({
3257
+ operation: z.literal("issue_standard"),
3258
+ siteId: z.string().uuid(),
3259
+ operationId: z.string().uuid(),
3260
+ projectName: z.string().trim().min(1).max(120),
3261
+ abandonedKeyIds: abandonedStandardKeyIdsSchema.default({})
3262
+ }).strict(),
3263
+ z.object({
3264
+ operation: z.literal("issue_with_preview"),
3265
+ siteId: z.string().uuid(),
3266
+ operationId: z.string().uuid(),
3267
+ projectName: z.string().trim().min(1).max(120),
3268
+ abandonedKeyIds: abandonedPreviewKeyIdsSchema.default({})
3269
+ }).strict(),
3270
+ z.object({
3271
+ operation: z.literal("revalidate_standard"),
3272
+ siteId: z.string().uuid(),
3273
+ operationId: z.string().uuid(),
3274
+ projectName: z.string().trim().min(1).max(120),
3275
+ keyIds: z.object({
3276
+ live: z.string().uuid(),
3277
+ management: z.string().uuid()
3278
+ }).strict()
3279
+ }).strict(),
3280
+ z.object({
3281
+ operation: z.literal("revalidate_with_preview"),
3282
+ siteId: z.string().uuid(),
3283
+ operationId: z.string().uuid(),
3284
+ projectName: z.string().trim().min(1).max(120),
3285
+ keyIds: z.object({
3286
+ live: z.string().uuid(),
3287
+ management: z.string().uuid(),
3288
+ preview: z.string().uuid()
3289
+ }).strict()
3290
+ }).strict()
3291
+ ]);
3292
+ z.object({
3293
+ cursor: z.string().min(1).max(ACCOUNT_SITE_CURSOR_MAX_LENGTH).optional(),
3294
+ limit: z.coerce.number().int().min(1).max(100).default(50),
3295
+ search: z.string().trim().min(1).max(120).optional()
3296
+ });
2690
3297
  z.object({
2691
3298
  formulaVersion: z.literal("managed-domain-overage-v1"),
2692
3299
  conversionRule: z.literal("usd-to-gbp-1-to-1-v1"),
@@ -20646,102 +21253,31 @@ const newsletterFormFragment = defineFragment({
20646
21253
  description: "Newsletter subscription form with preset field configurations.",
20647
21254
  fields: [
20648
21255
  {
20649
- id: "destinationProvider",
20650
- type: "select",
20651
- label: "Send signups to",
20652
- description: "Choose where new subscribers from this form should go.",
20653
- defaultValue: "riverbank",
20654
- required: false,
20655
- ui: { intent: "behavior" },
20656
- options: [{
20657
- value: "riverbank",
20658
- label: "Riverbank newsletter"
20659
- }]
20660
- },
20661
- {
20662
- id: "listId",
20663
- type: "reference",
20664
- label: "Newsletter list",
20665
- description: "Select the list new subscribers should be added to.",
20666
- required: false,
20667
- referenceKind: "newsletterList",
20668
- allowManualEntry: false,
20669
- ui: {
20670
- intent: "behavior",
20671
- visibleWhen: {
20672
- field: "destinationProvider",
20673
- equals: "riverbank"
20674
- }
20675
- }
20676
- },
20677
- {
20678
- id: "mailchimpAudienceId",
20679
- type: "reference",
20680
- label: "Mailchimp audience",
20681
- description: "Choose the Mailchimp audience for new subscribers.",
20682
- required: false,
20683
- referenceKind: "mailchimpAudience",
20684
- allowManualEntry: false,
20685
- ui: {
20686
- intent: "behavior",
20687
- visibleWhen: {
20688
- field: "destinationProvider",
20689
- equals: "mailchimp"
20690
- }
20691
- }
20692
- },
20693
- {
20694
- id: "mailchimpTags",
20695
- type: "text",
20696
- label: "Mailchimp tags",
20697
- description: "Optional tags to apply in Mailchimp, separated by commas or semicolons.",
21256
+ id: "targeting",
21257
+ type: "group",
21258
+ label: "Mailing-list targeting",
21259
+ description: "Choose what kind of emails people signing up here will receive.",
21260
+ defaultValue: { mode: "default" },
21261
+ required: true,
20698
21262
  ui: {
20699
21263
  intent: "behavior",
20700
- visibleWhen: {
20701
- field: "destinationProvider",
20702
- equals: "mailchimp"
20703
- }
20704
- }
20705
- },
20706
- {
20707
- id: "preset",
20708
- type: "select",
20709
- label: "Form fields",
20710
- description: "Choose which fields to collect.",
20711
- defaultValue: "emailOnly",
20712
- ui: { intent: "display" },
20713
- options: [
20714
- {
20715
- value: "emailOnly",
20716
- label: "Email only"
20717
- },
20718
- {
20719
- value: "emailName",
20720
- label: "Email + Name"
20721
- },
20722
- {
20723
- value: "emailFirstLast",
20724
- label: "Email + First/Last Name"
20725
- },
20726
- {
20727
- value: "emailNameCompany",
20728
- label: "Email + Name + Company (B2B)"
20729
- }
20730
- ]
20731
- },
20732
- {
20733
- id: "includePhone",
20734
- type: "boolean",
20735
- label: "Include phone field (optional)",
20736
- description: "Adds an optional phone field to the selected preset (never required).",
20737
- defaultValue: false,
20738
- ui: { intent: "display" }
20739
- },
20740
- {
20741
- id: "buttonLabel",
20742
- type: "text",
20743
- label: "Button label",
20744
- defaultValue: "Subscribe"
21264
+ widget: "mailingListTargeting"
21265
+ },
21266
+ schema: { fields: [{
21267
+ id: "mode",
21268
+ type: "select",
21269
+ label: "Mode",
21270
+ required: true,
21271
+ defaultValue: "default",
21272
+ options: [{
21273
+ value: "default",
21274
+ label: "Default"
21275
+ }, {
21276
+ value: "custom",
21277
+ label: "Custom"
21278
+ }],
21279
+ ui: { hidden: true }
21280
+ }] }
20745
21281
  },
20746
21282
  createButtonVariantField({
20747
21283
  id: "buttonVariant",
@@ -20749,46 +21285,42 @@ const newsletterFormFragment = defineFragment({
20749
21285
  defaultValue: "primary"
20750
21286
  }),
20751
21287
  {
20752
- id: "successMessage",
20753
- type: "text",
20754
- label: "Success message",
20755
- description: "Shown after successful subscription.",
20756
- defaultValue: "Thanks for subscribing!"
20757
- },
20758
- {
20759
- id: "privacyNote",
21288
+ id: "mailingListSource",
20760
21289
  type: "text",
20761
- label: "Privacy note",
20762
- description: "Optional small text below the form (e.g., privacy policy link)."
21290
+ label: "Mailing-list source",
21291
+ ui: {
21292
+ hidden: true,
21293
+ intent: "behavior"
21294
+ }
20763
21295
  },
20764
21296
  {
20765
- id: "tags",
21297
+ id: "mailingListSourceToken",
20766
21298
  type: "text",
20767
- label: "Tags (optional)",
20768
- description: "Optional tags to apply on subscribe (comma or semicolon separated).",
21299
+ label: "Mailing-list source token",
20769
21300
  ui: {
20770
- intent: "behavior",
20771
- visibleWhen: {
20772
- field: "destinationProvider",
20773
- equals: "riverbank"
20774
- }
21301
+ hidden: true,
21302
+ intent: "behavior"
20775
21303
  }
20776
21304
  }
20777
21305
  ],
20778
21306
  layout: [el("newsletter-form", withApiBindings({
20779
- source: "block",
20780
- destinationProvider: { $bind: { from: "destinationProvider" } },
20781
- listId: { $bind: { from: "listId" } },
20782
- preset: { $bind: { from: "preset" } },
20783
- includePhone: { $bind: { from: "includePhone" } },
20784
- buttonLabel: { $bind: { from: "buttonLabel" } },
21307
+ source: { $bind: {
21308
+ from: "mailingListSource",
21309
+ fallback: "block"
21310
+ } },
21311
+ sourceRef: {
21312
+ tag: "signed_source_ref",
21313
+ token: { $bind: { from: "mailingListSourceToken" } }
21314
+ },
21315
+ targeting: { $bind: {
21316
+ from: "targeting",
21317
+ fallback: { mode: "default" }
21318
+ } },
21319
+ mailingList: { $bind: { from: "$root.mailingList" } },
20785
21320
  buttonVariant: { $bind: {
20786
21321
  from: "buttonVariant",
20787
21322
  fallback: "primary"
20788
21323
  } },
20789
- successMessage: { $bind: { from: "successMessage" } },
20790
- privacyNote: { $bind: { from: "privacyNote" } },
20791
- tags: { $bind: { from: "tags" } },
20792
21324
  spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } }
20793
21325
  }), [])]
20794
21326
  });
@@ -22963,14 +23495,19 @@ const contactNewsletterLayout = styledSection({
22963
23495
  as: "h2",
22964
23496
  className: "newsletter-copy-title"
22965
23497
  }, when("newsletterTitle"), bind("newsletterTitle")), richText({ className: "newsletter-copy-subtitle rb-prose rb-max-w-none" }, when("newsletterSubtitle"), bind("newsletterSubtitle"))]), el("newsletter-form", withApiBindings({
22966
- source: "block",
22967
- listId: { $bind: { from: "listId" } },
22968
- preset: { $bind: { from: "preset" } },
22969
- includePhone: { $bind: { from: "includePhone" } },
22970
- buttonLabel: { $bind: { from: "newsletterButtonLabel" } },
22971
- successMessage: { $bind: { from: "newsletterSuccessMessage" } },
22972
- privacyNote: { $bind: { from: "newsletterPrivacyNote" } },
22973
- tags: { $bind: { from: "newsletterTags" } },
23498
+ source: { $bind: {
23499
+ from: "mailingListSource",
23500
+ fallback: "block"
23501
+ } },
23502
+ sourceRef: {
23503
+ tag: "signed_source_ref",
23504
+ token: { $bind: { from: "mailingListSourceToken" } }
23505
+ },
23506
+ targeting: { $bind: {
23507
+ from: "targeting",
23508
+ fallback: { mode: "default" }
23509
+ } },
23510
+ mailingList: { $bind: { from: "$root.mailingList" } },
22974
23511
  spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } }
22975
23512
  }), [])])])], { className: "contact-newsletter-container" }),
22976
23513
  baseClass: "",
@@ -23006,79 +23543,55 @@ const contactNewsletterBlockDefinition = {
23006
23543
  label: "Newsletter intro"
23007
23544
  },
23008
23545
  {
23009
- id: "listId",
23010
- type: "reference",
23011
- label: "Newsletter list",
23012
- description: "Select the list new subscribers should be added to.",
23546
+ id: "targeting",
23547
+ type: "group",
23548
+ label: "Mailing-list targeting",
23549
+ description: "Choose what kind of emails people signing up here will receive.",
23013
23550
  required: true,
23014
- referenceKind: "newsletterList",
23015
- allowManualEntry: false,
23016
- ui: { intent: "behavior" }
23017
- },
23018
- {
23019
- id: "preset",
23020
- type: "select",
23021
- label: "Newsletter fields",
23022
- defaultValue: "emailOnly",
23023
- ui: { intent: "display" },
23024
- options: [
23025
- {
23026
- value: "emailOnly",
23027
- label: "Email only"
23028
- },
23029
- {
23030
- value: "emailName",
23031
- label: "Email + Name"
23032
- },
23033
- {
23034
- value: "emailFirstLast",
23035
- label: "Email + First/Last Name"
23036
- },
23037
- {
23038
- value: "emailNameCompany",
23039
- label: "Email + Name + Company (B2B)"
23040
- }
23041
- ]
23042
- },
23043
- {
23044
- id: "includePhone",
23045
- type: "boolean",
23046
- label: "Include phone field",
23047
- defaultValue: false,
23048
- ui: { intent: "display" }
23049
- },
23050
- {
23051
- id: "newsletterButtonLabel",
23052
- type: "text",
23053
- label: "Newsletter button label",
23054
- defaultValue: "Subscribe",
23055
- ui: { intent: "display" }
23056
- },
23057
- {
23058
- id: "newsletterSuccessMessage",
23059
- type: "text",
23060
- label: "Newsletter success message",
23061
- defaultValue: "Thanks for subscribing!",
23062
- ui: { intent: "behavior" }
23551
+ defaultValue: { mode: "default" },
23552
+ ui: {
23553
+ intent: "behavior",
23554
+ widget: "mailingListTargeting"
23555
+ },
23556
+ schema: { fields: [{
23557
+ id: "mode",
23558
+ type: "select",
23559
+ label: "Mode",
23560
+ required: true,
23561
+ defaultValue: "default",
23562
+ options: [{
23563
+ value: "default",
23564
+ label: "Default"
23565
+ }, {
23566
+ value: "custom",
23567
+ label: "Custom"
23568
+ }],
23569
+ ui: { hidden: true }
23570
+ }] }
23063
23571
  },
23064
23572
  {
23065
- id: "newsletterPrivacyNote",
23573
+ id: "contactTitle",
23066
23574
  type: "text",
23067
- label: "Privacy note",
23068
- ui: { intent: "display" }
23575
+ label: "Contact title",
23576
+ defaultValue: "Get in touch"
23069
23577
  },
23070
23578
  {
23071
- id: "newsletterTags",
23579
+ id: "mailingListSource",
23072
23580
  type: "text",
23073
- label: "Newsletter tags",
23074
- description: "Optional tags to apply on subscribe, separated by comma or semicolon.",
23075
- ui: { intent: "behavior" }
23581
+ label: "Mailing-list source",
23582
+ ui: {
23583
+ hidden: true,
23584
+ intent: "behavior"
23585
+ }
23076
23586
  },
23077
23587
  {
23078
- id: "contactTitle",
23588
+ id: "mailingListSourceToken",
23079
23589
  type: "text",
23080
- label: "Contact title",
23081
- defaultValue: "Get in touch"
23590
+ label: "Mailing-list source token",
23591
+ ui: {
23592
+ hidden: true,
23593
+ intent: "behavior"
23594
+ }
23082
23595
  },
23083
23596
  {
23084
23597
  id: "contactIntro",
@@ -23279,6 +23792,12 @@ const arrayLengthTransform = {
23279
23792
  summary: "Get the length of an array",
23280
23793
  run: (value) => Array.isArray(value) ? value.length : 0
23281
23794
  };
23795
+ const booleanNotTransform = {
23796
+ id: "boolean.not",
23797
+ kind: "formatter",
23798
+ summary: "Invert a boolean value; fail closed to false for other inputs",
23799
+ run: (value) => typeof value === "boolean" ? !value : false
23800
+ };
23282
23801
  const jsonArrayTransform = {
23283
23802
  id: "value.jsonArray",
23284
23803
  kind: "formatter",
@@ -23329,33 +23848,51 @@ const marqueeLoopTransform = {
23329
23848
  function toUnknownArray(value) {
23330
23849
  return Array.isArray(value) ? value : [];
23331
23850
  }
23851
+ /**
23852
+ * Map a content state/enum to a theme token colour — the first-class way to turn
23853
+ * a value like a class's availability (`available`/`filling`/`waitlist`), a
23854
+ * card's featured flag, or a pricing tier level into a themed colour. Authors
23855
+ * write role names and get `rgb(var(--tb-<role>))`, so the result live-recolours
23856
+ * with the theme and drops directly into a `style` binding (e.g.
23857
+ * `borderLeftColor`). Returns `undefined` when the value matches no case and no
23858
+ * fallback is given, so the style property is simply omitted.
23859
+ */
23860
+ const mapTokenTransform = {
23861
+ id: "value.mapToken",
23862
+ kind: "formatter",
23863
+ summary: "Map a state/enum value to a theme token colour (rgb(var(--tb-<role>)))",
23864
+ schema: z.object({
23865
+ /**
23866
+ * Maps an input state/enum value (stringified) to a theme token ROLE name
23867
+ * (e.g. `secondary`, `accent`, `muted`, `border`). The role is wrapped to
23868
+ * `rgb(var(--tb-<role>))` so the result drops straight into a `style` binding.
23869
+ */
23870
+ cases: z.record(z.string(), z.string()),
23871
+ /** Role used when the value matches no case. Omit to yield `undefined`. */
23872
+ fallback: z.string().optional()
23873
+ }),
23874
+ run: (value, options) => {
23875
+ const role = options.cases[String(value)] ?? options.fallback;
23876
+ return role ? `rgb(var(--tb-${role}))` : void 0;
23877
+ }
23878
+ };
23332
23879
  const formattingTransforms = [
23880
+ {
23881
+ id: "value.equals",
23882
+ kind: "formatter",
23883
+ summary: "Compare a value to a fixed string",
23884
+ schema: z.object({ value: z.string() }),
23885
+ run: (value, options) => value === options.value
23886
+ },
23333
23887
  uppercaseTransform,
23334
23888
  fallbackTransform,
23335
23889
  dateFormatShort,
23336
23890
  arrayLengthTransform,
23891
+ booleanNotTransform,
23337
23892
  jsonArrayTransform,
23338
23893
  csvTransform,
23339
23894
  marqueeLoopTransform,
23340
- {
23341
- id: "value.mapToken",
23342
- kind: "formatter",
23343
- summary: "Map a state/enum value to a theme token colour (rgb(var(--tb-<role>)))",
23344
- schema: z.object({
23345
- /**
23346
- * Maps an input state/enum value (stringified) to a theme token ROLE name
23347
- * (e.g. `secondary`, `accent`, `muted`, `border`). The role is wrapped to
23348
- * `rgb(var(--tb-<role>))` so the result drops straight into a `style` binding.
23349
- */
23350
- cases: z.record(z.string(), z.string()),
23351
- /** Role used when the value matches no case. Omit to yield `undefined`. */
23352
- fallback: z.string().optional()
23353
- }),
23354
- run: (value, options) => {
23355
- const role = options.cases[String(value)] ?? options.fallback;
23356
- return role ? `rgb(var(--tb-${role}))` : void 0;
23357
- }
23358
- }
23895
+ mapTokenTransform
23359
23896
  ];
23360
23897
  //#endregion
23361
23898
  //#region ../blocks/src/system/transforms/registry/ui.ts
@@ -24772,13 +25309,9 @@ const footerStandardContactBindings = {
24772
25309
  };
24773
25310
  const footerMailingListBindings = {
24774
25311
  source: { $bind: { from: "module.engagement.runtime.source" } },
24775
- destinationRef: { $bind: { from: "module.engagement.runtime.destinationRef" } },
24776
- listId: { $bind: { from: "module.engagement.runtime.listId" } },
24777
- preset: { $bind: { from: "module.engagement.runtime.preset" } },
24778
- includePhone: { $bind: { from: "module.engagement.runtime.includePhone" } },
24779
- buttonLabel: { $bind: { from: "module.engagement.runtime.buttonLabel" } },
24780
- successMessage: { $bind: { from: "module.engagement.runtime.successMessage" } },
24781
- privacyNote: { $bind: { from: "module.engagement.runtime.privacyNote" } }
25312
+ sourceRef: { $bind: { from: "module.engagement.runtime.sourceRef" } },
25313
+ mailingList: { $bind: { from: "module.engagement.runtime.mailingList" } },
25314
+ targeting: { $bind: { from: "module.engagement.runtime.targeting" } }
24782
25315
  };
24783
25316
  const footerSpamProtectionBinding = { spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } } };
24784
25317
  const brandLogo = link({
@@ -26466,6 +26999,78 @@ const appointmentBookingBlockDefinition = {
26466
26999
  }
26467
27000
  }
26468
27001
  };
27002
+ const appointmentServicesBlockDefinition = {
27003
+ manifest: createBlockManifest({
27004
+ id: "block.appointment-services",
27005
+ version: "1.0.0",
27006
+ title: "Appointment Services",
27007
+ titleSource: "heading",
27008
+ description: "List public appointment services with booking links",
27009
+ skipSectionStyles: true,
27010
+ additionalFields: [
27011
+ {
27012
+ id: "heading",
27013
+ type: "text",
27014
+ label: "Heading",
27015
+ required: false,
27016
+ multiline: false,
27017
+ defaultValue: "Appointments",
27018
+ ui: { intent: "content" }
27019
+ },
27020
+ {
27021
+ id: "description",
27022
+ type: "richText",
27023
+ label: "Description",
27024
+ required: false,
27025
+ ui: { intent: "content" }
27026
+ },
27027
+ sectionStylesField({
27028
+ id: "_sectionStyles",
27029
+ label: "Section styles",
27030
+ renderInHeader: false
27031
+ })
27032
+ ],
27033
+ styleTokens: {
27034
+ background: "surface",
27035
+ typography: "body",
27036
+ spacing: "md"
27037
+ },
27038
+ behaviours: {
27039
+ supportsThemeSwitching: true,
27040
+ inlineEditing: true,
27041
+ animation: false,
27042
+ paletteHidden: false
27043
+ },
27044
+ category: "interactive",
27045
+ contentSource: { kind: "inline" },
27046
+ tags: [
27047
+ "booking",
27048
+ "appointment",
27049
+ "services",
27050
+ "scheduling"
27051
+ ],
27052
+ icon: "CalendarDays",
27053
+ layout: [styledSection({
27054
+ baseClass: "",
27055
+ spacing: "spacious",
27056
+ children: sectionContainer([
27057
+ text({ as: "h2" }, bind("content.heading"), when("content.heading")),
27058
+ richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
27059
+ el("appointment-services", {
27060
+ sectionHeading: { $bind: { from: "content.heading" } },
27061
+ services: { $bind: { from: "data.services" } },
27062
+ pathname: { $bind: { from: "$root.pathname" } }
27063
+ })
27064
+ ], { gap: "lg" })
27065
+ })]
27066
+ }),
27067
+ dataSchemas: { services: z.unknown().optional() },
27068
+ dataLoaders: { services: {
27069
+ endpoint: "getPublicBookingServices",
27070
+ params: { siteId: { $bind: { from: "$root.siteId" } } },
27071
+ mode: "server"
27072
+ } }
27073
+ };
26469
27074
  //#endregion
26470
27075
  //#region ../blocks/src/system/blocks/weekly-slot-availability.ts
26471
27076
  const weeklySlotAvailabilityManifest = createBlockManifest({
@@ -27704,8 +28309,8 @@ z.object({
27704
28309
  previewToken: z.string().nullable().optional(),
27705
28310
  stage: previewStageSchema.optional()
27706
28311
  });
27707
- function buildEventRegistrationDisplay(source) {
27708
- const selectedRun = resolveSelectedWholeSeriesRun(source);
28312
+ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents) {
28313
+ const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
27709
28314
  if (selectedRun && (!selectedRun.run.enrollmentOpen || toWholeSeriesRegistrationEvent(selectedRun) === null)) return {
27710
28315
  kind: "event-registration",
27711
28316
  state: "registration-closed",
@@ -27713,7 +28318,7 @@ function buildEventRegistrationDisplay(source) {
27713
28318
  titleText: "Registration closed",
27714
28319
  bodyText: "This run is no longer open for bookings."
27715
28320
  };
27716
- const stepLabels = buildEventRegistrationShellStepLabels(source);
28321
+ const stepLabels = buildEventRegistrationShellStepLabels(source, scopedSourceEvents);
27717
28322
  return {
27718
28323
  kind: "event-registration",
27719
28324
  state: "generic-shell",
@@ -27724,8 +28329,8 @@ function buildEventRegistrationDisplay(source) {
27724
28329
  summaryButtonText: source.buttonText?.trim() || "Register"
27725
28330
  };
27726
28331
  }
27727
- function buildEventRegistrationShellStepLabels(source) {
27728
- const selectedRun = resolveSelectedWholeSeriesRun(source);
28332
+ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
28333
+ const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
27729
28334
  if (selectedRun) {
27730
28335
  const selectedEvent = toWholeSeriesRegistrationEvent(selectedRun);
27731
28336
  if (!selectedEvent) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
@@ -27735,7 +28340,7 @@ function buildEventRegistrationShellStepLabels(source) {
27735
28340
  event: selectedEvent
27736
28341
  })).map((step) => shellLabelForRegistrationStep(step, [selectedEvent]));
27737
28342
  }
27738
- const scopedEvents = scopeEventRegistrationEvents(source);
28343
+ const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
27739
28344
  if (scopedEvents.length === 0) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
27740
28345
  const preSelectedEvent = findPreSelectedEvent({
27741
28346
  scopedEvents,
@@ -27748,31 +28353,25 @@ function buildEventRegistrationShellStepLabels(source) {
27748
28353
  event: preSelectedEvent
27749
28354
  }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
27750
28355
  }
27751
- function scopeEventRegistrationEvents(source) {
27752
- const scoped = scopeEventRegistrationSourceEvents(source);
27753
- return [...toRegistrationEvents(scoped), ...toWholeSeriesRegistrationEvents(scoped)];
28356
+ function toScopedEventRegistrationEvents(scopedSourceEvents) {
28357
+ return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
27754
28358
  }
27755
- /**
27756
- * Every event this surface may register for, from both of its loads.
27757
- *
27758
- * The date page and the run read answer different questions and can name the
27759
- * same occurrence, so the two lists are joined and any occurrence that arrives
27760
- * twice is kept once, in the order the date page gave it.
27761
- */
27762
28359
  function scopeEventRegistrationSourceEvents(source) {
27763
- const byOccurrenceId = /* @__PURE__ */ new Map();
27764
- for (const event of [...source.events ?? [], ...source.wholeSeriesRunEvents ?? []]) if (!byOccurrenceId.has(event.occurrenceId)) byOccurrenceId.set(event.occurrenceId, event);
27765
- return resolveEventRegistrationScopedEvents([...byOccurrenceId.values()], {
28360
+ return resolveEventRegistrationScopedEvents(source.wholeSeriesRunEvents ?? source.events ?? [], {
27766
28361
  occurrenceContext: source.occurrenceContext,
27767
28362
  subject: source.subject
27768
28363
  });
27769
28364
  }
28365
+ function resolveLoadedEventRegistrationSourceEvents(source, scopedSourceEvents) {
28366
+ if (source.events == null && source.wholeSeriesRunEvents == null) return null;
28367
+ return [...scopedSourceEvents];
28368
+ }
27770
28369
  /**
27771
28370
  * The one run this surface is registering for, when the scoped events describe
27772
28371
  * exactly one. More than one run means the visitor still has to choose.
27773
28372
  */
27774
- function resolveSelectedWholeSeriesRun(source) {
27775
- const runs = groupPublicEventsByRun(scopeEventRegistrationSourceEvents(source));
28373
+ function resolveSelectedWholeSeriesRun(scopedSourceEvents) {
28374
+ const runs = groupPublicEventsByRun(scopedSourceEvents);
27776
28375
  return runs.length === 1 ? runs[0] : null;
27777
28376
  }
27778
28377
  function shellLabelForRegistrationStep(step, events) {
@@ -27806,9 +28405,10 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
27806
28405
  render: createZodCodec(eventRegistrationRenderSchema)
27807
28406
  },
27808
28407
  buildRender: (source) => {
27809
- const events = source.events == null ? null : scopeEventRegistrationSourceEvents(source);
28408
+ const scopedSourceEvents = scopeEventRegistrationSourceEvents(source);
28409
+ const events = resolveLoadedEventRegistrationSourceEvents(source, scopedSourceEvents);
27810
28410
  return {
27811
- display: buildEventRegistrationDisplay(source),
28411
+ display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents),
27812
28412
  hydration: {
27813
28413
  occurrenceContext: source.occurrenceContext ?? null,
27814
28414
  subject: source.subject ?? null,
@@ -28009,6 +28609,11 @@ const eventRegistrationBlockDefinition = {
28009
28609
  dataLoaders: {
28010
28610
  events: {
28011
28611
  endpoint: "listPublicEvents",
28612
+ when: { $bind: {
28613
+ from: "$root.subject.isWholeSeriesBooking",
28614
+ fallback: true,
28615
+ transforms: [{ id: "boolean.not" }]
28616
+ } },
28012
28617
  params: {
28013
28618
  siteId: { $bind: { from: "$root.siteId" } },
28014
28619
  limit: "50",
@@ -28030,13 +28635,9 @@ const eventRegistrationBlockDefinition = {
28030
28635
  /**
28031
28636
  * The runs this page can register for, finished ones included.
28032
28637
  *
28033
- * The `events` page above is date-bounded, so a run whose first session has
28034
- * passed contributes no rows to it and the surface would have nothing to
28035
- * render as closed. This asks for runs instead, and only on a page whose
28036
- * subject books whole runs. It is scoped exactly like the date page — same
28037
- * series, same run subroute — because scope and grain are separate
28038
- * questions. The two lists are joined by occurrence, so a run that appears
28039
- * in both is still one run.
28638
+ * A run read is the sole Event source on a whole-series surface. It is
28639
+ * status-bounded rather than date-bounded, so completed runs can still
28640
+ * render their closed state and every run carries its complete schedule.
28040
28641
  */
28041
28642
  wholeSeriesRunEvents: {
28042
28643
  endpoint: "listPublicEvents",
@@ -30629,20 +31230,7 @@ function isRecord$2(value) {
30629
31230
  return typeof value === "object" && value !== null && !Array.isArray(value);
30630
31231
  }
30631
31232
  //#endregion
30632
- //#region ../blocks/src/system/blocks/offerings-preview/index.ts
30633
- /**
30634
- * The whole-series presets, as the CSV `listPublicEvents` expects. Both
30635
- * whole-series presets share one loader: the block renders them as a single
30636
- * run group.
30637
- */
30638
- const WHOLE_SERIES_EVENT_PRESETS_PARAM = WHOLE_SERIES_EVENT_PRESET_VALUES.join(",");
30639
- const publicRouteCandidatesSchema = z.object({ routes: z.array(z.object({
30640
- entityId: z.string(),
30641
- path: z.string(),
30642
- title: z.string(),
30643
- kind: z.enum(RESOLVED_INTERNAL_LINK_ENTITY_TYPES),
30644
- contentTypeKey: z.string().nullable().optional()
30645
- })) });
31233
+ //#region ../blocks/src/system/blocks/offerings-preview/fields.ts
30646
31234
  const groupOverrideFields = [
30647
31235
  {
30648
31236
  id: "sourceKey",
@@ -30848,7 +31436,65 @@ const offeringsPreviewFields = [
30848
31436
  renderInHeader: false
30849
31437
  })
30850
31438
  ];
31439
+ function labelForOfferingPreviewSourceKey(sourceKey) {
31440
+ const eventDefinition = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.find((definition) => definition.sourceKey === sourceKey);
31441
+ if (eventDefinition) return eventDefinition.label;
31442
+ return "Courses";
31443
+ }
31444
+ //#endregion
31445
+ //#region ../blocks/src/system/blocks/offerings-preview/index.ts
31446
+ /**
31447
+ * The whole-series presets, as the CSV `listPublicEvents` expects. Both
31448
+ * whole-series presets share one loader: the block renders them as a single
31449
+ * run group.
31450
+ */
31451
+ const WHOLE_SERIES_EVENT_PRESETS_PARAM = WHOLE_SERIES_EVENT_PRESET_VALUES.join(",");
31452
+ const publicRouteCandidatesSchema = z.object({ routes: z.array(z.object({
31453
+ entityId: z.string(),
31454
+ path: z.string(),
31455
+ title: z.string(),
31456
+ kind: z.enum(RESOLVED_INTERNAL_LINK_ENTITY_TYPES),
31457
+ contentTypeKey: z.string().nullable().optional()
31458
+ })) });
30851
31459
  const offeringsPreviewEventDataBindings = Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.dataKey, { $bind: { from: `data.${definition.dataKey}` } }]));
31460
+ const DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE = "preview";
31461
+ const displayModeBinding = {
31462
+ from: "content.displayMode",
31463
+ fallback: DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE
31464
+ };
31465
+ const catalogueMode = { $bind: {
31466
+ ...displayModeBinding,
31467
+ transforms: [{
31468
+ id: "value.equals",
31469
+ options: { value: "catalogue" }
31470
+ }]
31471
+ } };
31472
+ const previewMode = { $bind: {
31473
+ ...displayModeBinding,
31474
+ transforms: [{
31475
+ id: "value.equals",
31476
+ options: { value: "preview" }
31477
+ }]
31478
+ } };
31479
+ function offeringsPreviewHeaderNodes() {
31480
+ return [text({
31481
+ as: "h2",
31482
+ className: { $bind: {
31483
+ from: "content.displayMode",
31484
+ fallback: "rb-text-center",
31485
+ transforms: [{
31486
+ id: "value.equals",
31487
+ options: { value: "catalogue" }
31488
+ }, {
31489
+ id: "ui.conditionalClass",
31490
+ options: {
31491
+ whenTrue: "rb-text-left",
31492
+ whenFalse: "rb-text-center"
31493
+ }
31494
+ }]
31495
+ } }
31496
+ }, bind("content.heading"), when("content.heading")), richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description"))];
31497
+ }
30852
31498
  const offeringsPreviewManifest = createBlockManifest({
30853
31499
  id: "block.offerings-preview",
30854
31500
  version: "1.0.0",
@@ -30879,48 +31525,45 @@ const offeringsPreviewManifest = createBlockManifest({
30879
31525
  ],
30880
31526
  icon: "PanelsTopLeft",
30881
31527
  layout: [styledSection({
30882
- children: sectionContainer([
30883
- text({
30884
- as: "h2",
30885
- className: "rb-text-center"
30886
- }, bind("content.heading"), when("content.heading")),
30887
- richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
30888
- el("offerings-preview", {
30889
- bookingOfferingPresets: { $bind: {
30890
- from: "$root.bookingOfferingPresets",
30891
- fallback: []
30892
- } },
30893
- groupOverrides: { $bind: {
30894
- from: "content.groupOverrides",
30895
- fallback: []
30896
- } },
30897
- groupOrder: { $bind: { from: "content.groupOrder" } },
30898
- cardStyles: { $bind: { from: "content._cardStyles" } },
30899
- itemLimit: { $bind: {
30900
- from: "content.itemLimit",
30901
- fallback: "3"
30902
- } },
30903
- buttonVariant: { $bind: {
30904
- from: "content.buttonVariant",
30905
- fallback: "primary"
30906
- } },
30907
- buttonText: { $bind: {
30908
- from: "content.buttonText",
30909
- fallback: OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT
30910
- } },
30911
- emptyMessage: { $bind: {
30912
- from: "content.emptyMessage",
30913
- fallback: OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE
30914
- } },
30915
- previewStage: { $bind: {
30916
- from: "$root.previewStage",
30917
- fallback: "published"
30918
- } },
30919
- routeCandidates: { $bind: { from: "data.routeCandidates" } },
30920
- wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
30921
- ...offeringsPreviewEventDataBindings
30922
- })
30923
- ], { gap: "lg" }),
31528
+ children: sectionContainer([el("offerings-preview", {
31529
+ displayMode: { $bind: displayModeBinding },
31530
+ pathname: { $bind: { from: "$root.pathname" } },
31531
+ catalogueEvents: { $bind: { from: "data.catalogueEvents" } },
31532
+ quickDateEvents: { $bind: { from: "data.quickDateEvents" } },
31533
+ bookingOfferingPresets: { $bind: {
31534
+ from: "$root.bookingOfferingPresets",
31535
+ fallback: []
31536
+ } },
31537
+ groupOverrides: { $bind: {
31538
+ from: "content.groupOverrides",
31539
+ fallback: []
31540
+ } },
31541
+ groupOrder: { $bind: { from: "content.groupOrder" } },
31542
+ cardStyles: { $bind: { from: "content._cardStyles" } },
31543
+ itemLimit: { $bind: {
31544
+ from: "content.itemLimit",
31545
+ fallback: "3"
31546
+ } },
31547
+ buttonVariant: { $bind: {
31548
+ from: "content.buttonVariant",
31549
+ fallback: "primary"
31550
+ } },
31551
+ buttonText: { $bind: {
31552
+ from: "content.buttonText",
31553
+ fallback: OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT
31554
+ } },
31555
+ emptyMessage: { $bind: {
31556
+ from: "content.emptyMessage",
31557
+ fallback: OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE
31558
+ } },
31559
+ previewStage: { $bind: {
31560
+ from: "$root.previewStage",
31561
+ fallback: "published"
31562
+ } },
31563
+ routeCandidates: { $bind: { from: "data.routeCandidates" } },
31564
+ wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
31565
+ ...offeringsPreviewEventDataBindings
31566
+ }, offeringsPreviewHeaderNodes())], { gap: "lg" }),
30924
31567
  baseClass: "",
30925
31568
  spacing: "comfortable"
30926
31569
  })]
@@ -30934,16 +31577,48 @@ const offeringsPreviewBlockDefinition = {
30934
31577
  dataSchemas: {
30935
31578
  ...offeringsPreviewEventDataSchemas,
30936
31579
  routeCandidates: publicRouteCandidatesSchema.optional(),
30937
- wholeSeriesRunEvents: optionalPublicEventsArraySchema
31580
+ wholeSeriesRunEvents: optionalPublicEventsArraySchema,
31581
+ catalogueEvents: optionalPublicEventsArraySchema,
31582
+ quickDateEvents: optionalPublicEventsArraySchema
30938
31583
  },
30939
31584
  dataLoaders: {
30940
31585
  ...offeringsPreviewEventDataLoaders,
30941
31586
  routeCandidates: {
30942
31587
  endpoint: "listPublicRouteCandidates",
31588
+ when: previewMode,
30943
31589
  params: {},
30944
31590
  mode: "server"
30945
31591
  },
30946
- wholeSeriesRunEvents: wholeSeriesRunLoader()
31592
+ wholeSeriesRunEvents: wholeSeriesRunLoader(),
31593
+ catalogueEvents: {
31594
+ endpoint: "listPublicEvents",
31595
+ when: catalogueMode,
31596
+ params: {
31597
+ stage: { $bind: {
31598
+ from: "$root.previewStage",
31599
+ fallback: "published"
31600
+ } },
31601
+ detailLevel: "listing",
31602
+ readProfile: "listing-card",
31603
+ readIntent: "catalogue"
31604
+ },
31605
+ mode: "server"
31606
+ },
31607
+ quickDateEvents: {
31608
+ endpoint: "listPublicEvents",
31609
+ when: catalogueMode,
31610
+ params: {
31611
+ stage: { $bind: {
31612
+ from: "$root.previewStage",
31613
+ fallback: "published"
31614
+ } },
31615
+ detailLevel: "listing",
31616
+ readProfile: "listing-card",
31617
+ readIntent: "occurrences",
31618
+ limit: 40
31619
+ },
31620
+ mode: "server"
31621
+ }
30947
31622
  }
30948
31623
  };
30949
31624
  /**
@@ -30960,6 +31635,7 @@ function wholeSeriesRunLoader() {
30960
31635
  function eventPresetLoader(eventPresets, extraParams = {}) {
30961
31636
  return {
30962
31637
  endpoint: "listPublicEvents",
31638
+ when: previewMode,
30963
31639
  params: {
30964
31640
  siteId: { $bind: { from: "$root.siteId" } },
30965
31641
  limit: { $bind: {
@@ -30978,15 +31654,10 @@ function eventPresetLoader(eventPresets, extraParams = {}) {
30978
31654
  mode: "server"
30979
31655
  };
30980
31656
  }
30981
- function labelForOfferingPreviewSourceKey(sourceKey) {
30982
- const eventDefinition = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.find((definition) => definition.sourceKey === sourceKey);
30983
- if (eventDefinition) return eventDefinition.label;
30984
- return "Courses";
30985
- }
30986
31657
  function normalizeOfferingsPreviewBlockContent(value) {
30987
31658
  const record = normalizeOfferingsPreviewContentForWrite(value);
30988
- if (!record) return {};
30989
- const normalized = {};
31659
+ if (!record) return { displayMode: DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE };
31660
+ const normalized = { displayMode: normalizeOfferingsPreviewDisplayMode(record.displayMode) };
30990
31661
  if (typeof record.heading === "string" || record.heading === null) normalized.heading = record.heading;
30991
31662
  if ("description" in record) normalized.description = record.description;
30992
31663
  if (isOfferingsPreviewItemLimit(record.itemLimit)) normalized.itemLimit = record.itemLimit;
@@ -31003,6 +31674,9 @@ function normalizeOfferingsPreviewBlockContent(value) {
31003
31674
  if (sectionStyles !== void 0) normalized._sectionStyles = sectionStyles;
31004
31675
  return normalized;
31005
31676
  }
31677
+ function normalizeOfferingsPreviewDisplayMode(value) {
31678
+ return value === "catalogue" ? "catalogue" : DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE;
31679
+ }
31006
31680
  function isOfferingsPreviewItemLimit(value) {
31007
31681
  return value === "1" || value === "2" || value === "3" || value === "4" || value === "5" || value === "6" || value === null;
31008
31682
  }
@@ -34217,63 +34891,13 @@ const giftingBlockDefinition = {
34217
34891
  } }
34218
34892
  };
34219
34893
  //#endregion
34220
- //#region ../blocks/src/system/blocks/newsletter-signup-destination.ts
34221
- function parseNewsletterSignupBlockId(value) {
34222
- const trimmed = parseTrimmedString(value);
34223
- return trimmed && BLOCK_ID_PATTERN.test(trimmed) ? trimmed : null;
34224
- }
34225
- function parseSignedNewsletterDestinationRef(value) {
34226
- const trimmed = parseTrimmedString(value);
34227
- return trimmed && SIGNED_DESTINATION_REF_PATTERN.test(trimmed) ? trimmed : null;
34228
- }
34229
- function buildNewsletterSignupBlockRef(blockId) {
34230
- const parsed = parseNewsletterSignupBlockId(blockId);
34231
- return parsed ? {
34232
- tag: "block_ref",
34233
- blockId: parsed
34234
- } : null;
34235
- }
34236
- function normalizeNewsletterSignupEditorValuesForDestination(values) {
34237
- const destinationProvider = parseNewsletterSignupDestinationProvider(values.destinationProvider) ?? "riverbank";
34238
- if (destinationProvider === "mailchimp") return {
34239
- destinationProvider,
34240
- mailchimpAudienceId: values.mailchimpAudienceId,
34241
- mailchimpTags: values.mailchimpTags
34242
- };
34243
- return {
34244
- destinationProvider,
34245
- listId: values.listId,
34246
- tags: values.tags
34247
- };
34248
- }
34249
- function deriveNewsletterSignupRuntimeSource(values) {
34250
- const normalized = normalizeNewsletterSignupEditorValuesForDestination(values);
34251
- if (normalized.destinationProvider === "mailchimp") return {
34252
- destinationProvider: "mailchimp",
34253
- listId: null,
34254
- tags: null
34255
- };
34256
- return {
34257
- destinationProvider: "riverbank",
34258
- listId: parseTrimmedString(normalized.listId),
34259
- tags: parseTrimmedString(normalized.tags)
34260
- };
34261
- }
34262
- const BLOCK_ID_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/;
34263
- const SIGNED_DESTINATION_REF_PATTERN = /^[A-Za-z0-9._~-]{16,1000}$/;
34264
- function parseTrimmedString(value) {
34265
- if (typeof value !== "string") return null;
34266
- const trimmed = value.trim();
34267
- return trimmed.length > 0 ? trimmed : null;
34268
- }
34269
- //#endregion
34270
34894
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.types.ts
34271
34895
  /**
34272
34896
  * Get form fields based on preset configuration.
34273
34897
  */
34274
- function getPresetFields(preset, options) {
34275
- const includePhone = Boolean(options?.includePhone);
34276
- switch (preset) {
34898
+ function getGlobalFormFields(form) {
34899
+ const includePhone = form.includeOptionalPhone;
34900
+ switch (form.fields.preset) {
34277
34901
  case "emailOnly": return withOptionalPhone([{
34278
34902
  id: "email",
34279
34903
  type: "email",
@@ -34291,7 +34915,7 @@ function getPresetFields(preset, options) {
34291
34915
  id: "name",
34292
34916
  type: "text",
34293
34917
  label: "Name",
34294
- required: false,
34918
+ required: form.fields.name === "required",
34295
34919
  placeholder: "Your name"
34296
34920
  }], includePhone);
34297
34921
  case "emailFirstLast": return withOptionalPhone([
@@ -34306,14 +34930,14 @@ function getPresetFields(preset, options) {
34306
34930
  id: "firstName",
34307
34931
  type: "text",
34308
34932
  label: "First Name",
34309
- required: false,
34933
+ required: form.fields.firstName === "required",
34310
34934
  placeholder: "First name"
34311
34935
  },
34312
34936
  {
34313
34937
  id: "lastName",
34314
34938
  type: "text",
34315
34939
  label: "Last Name",
34316
- required: false,
34940
+ required: form.fields.lastName === "required",
34317
34941
  placeholder: "Last name"
34318
34942
  }
34319
34943
  ], includePhone);
@@ -34329,14 +34953,14 @@ function getPresetFields(preset, options) {
34329
34953
  id: "name",
34330
34954
  type: "text",
34331
34955
  label: "Name",
34332
- required: false,
34956
+ required: form.fields.name === "required",
34333
34957
  placeholder: "Your name"
34334
34958
  },
34335
34959
  {
34336
34960
  id: "company",
34337
34961
  type: "text",
34338
34962
  label: "Company",
34339
- required: false,
34963
+ required: form.fields.company === "required",
34340
34964
  placeholder: "Company name"
34341
34965
  }
34342
34966
  ], includePhone);
@@ -34355,14 +34979,29 @@ function withOptionalPhone(fields, includePhone) {
34355
34979
  }
34356
34980
  //#endregion
34357
34981
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.interactive.ts
34358
- const newsletterPresetSchema = z.enum([
34359
- "emailOnly",
34360
- "emailName",
34361
- "emailFirstLast",
34362
- "emailNameCompany"
34363
- ]);
34364
- const newsletterDestinationProviderSchema = z.enum(["riverbank", "mailchimp"]);
34365
34982
  const newsletterButtonVariantSchema = blockButtonRoleSchema;
34983
+ const mailingListContextSchema = z.unknown().transform((value, ctx) => {
34984
+ const parsed = parseMailingListRuntimeContext(value);
34985
+ if (!parsed.ok) {
34986
+ ctx.addIssue({
34987
+ code: z.ZodIssueCode.custom,
34988
+ message: "Invalid mailing-list runtime context"
34989
+ });
34990
+ return z.NEVER;
34991
+ }
34992
+ return parsed.value;
34993
+ });
34994
+ const mailingListTargetingSchema = z.unknown().transform((value, ctx) => {
34995
+ const parsed = parseMailingListPlacementTargeting(value);
34996
+ if (!parsed.ok) {
34997
+ ctx.addIssue({
34998
+ code: z.ZodIssueCode.custom,
34999
+ message: "Invalid mailing-list targeting"
35000
+ });
35001
+ return z.NEVER;
35002
+ }
35003
+ return parsed.value;
35004
+ });
34366
35005
  const newsletterFieldSchema = z.object({
34367
35006
  id: z.string(),
34368
35007
  type: z.enum(["email", "text"]),
@@ -34370,73 +35009,73 @@ const newsletterFieldSchema = z.object({
34370
35009
  required: z.boolean(),
34371
35010
  placeholder: z.string().optional()
34372
35011
  });
34373
- const newsletterSignupBlockIdSchema = z.string().trim().min(1).transform(parseRequiredNewsletterSignupBlockId);
34374
- const signedNewsletterDestinationRefSchema = z.string().trim().min(1).transform(parseRequiredSignedNewsletterDestinationRef);
35012
+ const mailingListSignupBlockIdSchema = z.string().trim().min(1).transform(parseRequiredMailingListSignupBlockId);
35013
+ const signedMailingListSourceTokenSchema = z.string().trim().min(1).transform(parseRequiredMailingListSignedSourceToken);
34375
35014
  const newsletterSourceCommonSchema = z.object({
34376
35015
  siteId: z.string().optional(),
34377
35016
  apiBaseUrl: z.string().optional(),
34378
- preset: newsletterPresetSchema.optional(),
34379
- includePhone: z.boolean().optional(),
34380
- buttonLabel: z.string().nullable().optional(),
35017
+ mailingList: mailingListContextSchema.optional(),
35018
+ targeting: mailingListTargetingSchema.optional(),
34381
35019
  buttonVariant: newsletterButtonVariantSchema.nullable().optional(),
34382
- successMessage: z.string().nullable().optional(),
34383
- privacyNote: z.string().nullable().optional(),
34384
- tags: z.string().nullable().optional(),
34385
35020
  spamProtectionEnabled: z.boolean().optional(),
34386
35021
  className: z.string().nullable().optional()
34387
35022
  });
34388
35023
  const newsletterSourceSchema = z.discriminatedUnion("source", [
34389
35024
  newsletterSourceCommonSchema.extend({
34390
35025
  source: z.literal("block"),
34391
- blockId: z.string().nullable().optional(),
34392
- listId: z.string().nullable().optional(),
34393
- destinationProvider: newsletterDestinationProviderSchema.nullable().optional()
35026
+ blockId: z.string().nullable().optional()
34394
35027
  }),
34395
35028
  newsletterSourceCommonSchema.extend({
34396
35029
  source: z.literal("signed-footer"),
34397
- destinationRef: z.object({
34398
- tag: z.literal("signed_destination_ref"),
34399
- token: signedNewsletterDestinationRefSchema
35030
+ sourceRef: z.object({
35031
+ tag: z.literal("signed_source_ref"),
35032
+ token: signedMailingListSourceTokenSchema
34400
35033
  })
34401
35034
  }),
34402
- newsletterSourceCommonSchema.extend({
34403
- source: z.literal("legacy-direct-list"),
34404
- listId: z.string().nullable()
34405
- })
35035
+ newsletterSourceCommonSchema.extend({ source: z.literal("preview") })
34406
35036
  ]);
34407
35037
  const newsletterDisplaySchema = z.object({
34408
35038
  kind: z.literal("newsletter-form"),
35039
+ availability: z.enum(["ready", "unavailable"]),
34409
35040
  className: z.string().nullable(),
34410
35041
  buttonLabel: z.string(),
34411
35042
  buttonVariant: newsletterButtonVariantSchema,
34412
- successMessage: z.string().nullable(),
34413
35043
  privacyNote: z.string().nullable(),
34414
- fields: z.array(newsletterFieldSchema)
35044
+ fields: z.array(newsletterFieldSchema),
35045
+ choices: z.array(z.object({
35046
+ id: z.string(),
35047
+ label: z.string()
35048
+ }))
34415
35049
  });
34416
35050
  const newsletterHydrationSchema = z.object({
34417
35051
  target: z.discriminatedUnion("tag", [
34418
35052
  z.object({
34419
35053
  tag: z.literal("block-source"),
34420
- destinationRef: z.object({
35054
+ sourceRef: z.object({
34421
35055
  tag: z.literal("block_ref"),
34422
- blockId: newsletterSignupBlockIdSchema
35056
+ blockId: mailingListSignupBlockIdSchema
34423
35057
  }).nullable()
34424
35058
  }),
34425
35059
  z.object({
34426
35060
  tag: z.literal("signed-footer-source"),
34427
- destinationRef: z.object({
34428
- tag: z.literal("signed_destination_ref"),
34429
- token: signedNewsletterDestinationRefSchema
35061
+ sourceRef: z.object({
35062
+ tag: z.literal("signed_source_ref"),
35063
+ token: signedMailingListSourceTokenSchema
34430
35064
  })
34431
35065
  }),
34432
- z.object({
34433
- tag: z.literal("legacy-direct-list"),
34434
- listId: z.string().nullable()
34435
- })
35066
+ z.object({ tag: z.literal("preview") })
35067
+ ]),
35068
+ setupRevision: z.number().int().positive().nullable(),
35069
+ choiceProvider: z.enum([
35070
+ "native",
35071
+ "brevo",
35072
+ "mailchimp"
35073
+ ]).nullable(),
35074
+ choicePresentation: z.enum([
35075
+ "hidden",
35076
+ "automatic",
35077
+ "visitor_choice"
34436
35078
  ]),
34437
- preset: newsletterPresetSchema,
34438
- includePhone: z.boolean(),
34439
- tags: z.string().nullable().optional(),
34440
35079
  spamProtectionEnabled: z.boolean().optional()
34441
35080
  });
34442
35081
  const newsletterRenderSchema = z.object({
@@ -34448,16 +35087,24 @@ z.object({
34448
35087
  apiBaseUrl: z.string().optional()
34449
35088
  });
34450
35089
  function buildNewsletterDisplay(source) {
34451
- const preset = source.preset ?? "emailOnly";
34452
- const includePhone = source.includePhone ?? false;
35090
+ const context = source.mailingList;
35091
+ const form = context?.tag === "configured" ? context.form : {
35092
+ fields: { preset: "emailOnly" },
35093
+ includeOptionalPhone: false,
35094
+ buttonLabel: "Subscribe",
35095
+ privacyNote: null
35096
+ };
35097
+ const targeting = source.targeting ?? { mode: "default" };
35098
+ const choices = resolveRuntimeChoices(context, targeting);
34453
35099
  return {
34454
35100
  kind: "newsletter-form",
35101
+ availability: runtimeTargetingIsAvailable(context, targeting, choices) ? "ready" : "unavailable",
34455
35102
  className: source.className ?? null,
34456
- buttonLabel: source.buttonLabel?.trim() || "Subscribe",
35103
+ buttonLabel: form.buttonLabel,
34457
35104
  buttonVariant: source.buttonVariant ?? "primary",
34458
- successMessage: source.successMessage ?? "Thanks for subscribing!",
34459
- privacyNote: source.privacyNote ?? null,
34460
- fields: getPresetFields(preset, { includePhone })
35105
+ privacyNote: form.privacyNote,
35106
+ fields: getGlobalFormFields(form),
35107
+ choices
34461
35108
  };
34462
35109
  }
34463
35110
  const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
@@ -34467,14 +35114,15 @@ const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
34467
35114
  render: createZodCodec(newsletterRenderSchema)
34468
35115
  },
34469
35116
  buildRender: (source) => {
34470
- const runtimeSource = source.source === "block" ? deriveNewsletterSignupRuntimeSource(source) : null;
35117
+ const targeting = source.targeting ?? { mode: "default" };
35118
+ const presentation = planMailingListChoicePresentation(targeting);
34471
35119
  return {
34472
35120
  display: buildNewsletterDisplay(source),
34473
35121
  hydration: {
34474
35122
  target: projectNewsletterFormHydrationTarget(source),
34475
- preset: source.preset ?? "emailOnly",
34476
- includePhone: source.includePhone ?? false,
34477
- tags: runtimeSource ? runtimeSource.tags : source.tags,
35123
+ setupRevision: source.mailingList?.tag === "configured" ? source.mailingList.revision : null,
35124
+ choiceProvider: targeting.mode === "custom" ? targeting.provider : null,
35125
+ choicePresentation: presentation.tag,
34478
35126
  spamProtectionEnabled: source.spamProtectionEnabled
34479
35127
  }
34480
35128
  };
@@ -34483,35 +35131,87 @@ const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
34483
35131
  function projectNewsletterFormHydrationTarget(source) {
34484
35132
  switch (source.source) {
34485
35133
  case "block": {
34486
- const destinationRef = buildNewsletterSignupBlockRef(source.blockId);
34487
- if (destinationRef?.tag === "block_ref") return {
35134
+ const sourceRef = buildMailingListSignupBlockSourceRef(source.blockId);
35135
+ if (sourceRef?.tag === "block_ref") return {
34488
35136
  tag: "block-source",
34489
- destinationRef
35137
+ sourceRef
34490
35138
  };
34491
35139
  return {
34492
- tag: "legacy-direct-list",
34493
- listId: source.listId ?? null
35140
+ tag: "block-source",
35141
+ sourceRef: null
34494
35142
  };
34495
35143
  }
34496
35144
  case "signed-footer": return {
34497
35145
  tag: "signed-footer-source",
34498
- destinationRef: source.destinationRef
34499
- };
34500
- case "legacy-direct-list": return {
34501
- tag: "legacy-direct-list",
34502
- listId: source.listId
35146
+ sourceRef: source.sourceRef
34503
35147
  };
35148
+ case "preview": return { tag: "preview" };
34504
35149
  default: return assertNever$2(source);
34505
35150
  }
34506
35151
  }
34507
- function parseRequiredNewsletterSignupBlockId(value) {
34508
- const parsed = parseNewsletterSignupBlockId(value);
34509
- if (!parsed) throw new Error("Newsletter signup block destination ref requires blockId");
35152
+ function resolveRuntimeChoices(context, targeting) {
35153
+ if (context?.tag !== "configured" || targeting.mode !== "custom" || targeting.selection.tag !== "visitor_choice" || context.provider.provider !== targeting.provider) return [];
35154
+ switch (targeting.provider) {
35155
+ case "native": {
35156
+ const offered = new Set(targeting.selection.offeredTopicIds);
35157
+ return context.provider.provider === "native" ? context.provider.topics.filter((item) => offered.has(item.id)).map((item) => ({
35158
+ id: item.id,
35159
+ label: item.name
35160
+ })) : [];
35161
+ }
35162
+ case "brevo": {
35163
+ const offered = new Set(targeting.selection.offeredListIds);
35164
+ return context.provider.provider === "brevo" ? context.provider.lists.filter((item) => offered.has(item.id)).map((item) => ({
35165
+ id: String(item.id),
35166
+ label: item.name
35167
+ })) : [];
35168
+ }
35169
+ case "mailchimp": {
35170
+ const offered = new Set(targeting.selection.offeredInterestIds);
35171
+ return (context.provider.provider === "mailchimp" ? context.provider.audiences.find((item) => item.id === targeting.audienceId) : void 0)?.interests.filter((item) => offered.has(item.id)).map((item) => ({
35172
+ id: item.id,
35173
+ label: item.name
35174
+ })) ?? [];
35175
+ }
35176
+ }
35177
+ }
35178
+ function runtimeTargetingIsAvailable(context, targeting, choices) {
35179
+ if (context?.tag !== "configured") return false;
35180
+ const provider = context.provider;
35181
+ if (targeting.mode === "default") switch (provider.provider) {
35182
+ case "native": return provider.topics.some((item) => item.id === provider.defaultTopicId);
35183
+ case "brevo": return provider.lists.some((item) => item.id === provider.defaultListId);
35184
+ case "mailchimp": return provider.audiences.some((item) => item.id === provider.defaultAudienceId);
35185
+ }
35186
+ if (targeting.provider !== provider.provider) return false;
35187
+ switch (targeting.provider) {
35188
+ case "native": return provider.provider === "native" && nativeTargetIsAvailable(provider, targeting, choices);
35189
+ case "brevo": return provider.provider === "brevo" && brevoTargetIsAvailable(provider, targeting, choices);
35190
+ case "mailchimp": return provider.provider === "mailchimp" && mailchimpTargetIsAvailable(provider, targeting, choices);
35191
+ }
35192
+ }
35193
+ function nativeTargetIsAvailable(provider, targeting, choices) {
35194
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
35195
+ return ids.every((id) => provider.topics.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
35196
+ }
35197
+ function brevoTargetIsAvailable(provider, targeting, choices) {
35198
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
35199
+ return ids.every((id) => provider.lists.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
35200
+ }
35201
+ function mailchimpTargetIsAvailable(provider, targeting, choices) {
35202
+ const audience = provider.audiences.find((item) => item.id === targeting.audienceId);
35203
+ if (!audience) return false;
35204
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
35205
+ return ids.every((id) => audience.interests.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
35206
+ }
35207
+ function parseRequiredMailingListSignupBlockId(value) {
35208
+ const parsed = parseMailingListBlockId(value);
35209
+ if (!parsed) throw new Error("Mailing-list signup block source ref requires blockId");
34510
35210
  return parsed;
34511
35211
  }
34512
- function parseRequiredSignedNewsletterDestinationRef(value) {
34513
- const parsed = parseSignedNewsletterDestinationRef(value);
34514
- if (!parsed) throw new Error("Newsletter signup signed destination ref requires token");
35212
+ function parseRequiredMailingListSignedSourceToken(value) {
35213
+ const parsed = parseMailingListSignedSourceToken(value);
35214
+ if (!parsed) throw new Error("Mailing-list signup signed source ref requires token");
34515
35215
  return parsed;
34516
35216
  }
34517
35217
  //#endregion
@@ -35884,6 +36584,14 @@ const systemBlockDefinitions = [
35884
36584
  weight: "standard"
35885
36585
  })]
35886
36586
  })),
36587
+ withDesignCapability("block.appointment-services", appointmentServicesBlockDefinition, sectionOnly(), defineBlockDesignMetadata({
36588
+ flow: conversionFlow,
36589
+ layoutProfiles: [layoutProfile({
36590
+ variantKey: "default",
36591
+ family: "stack",
36592
+ weight: "standard"
36593
+ })]
36594
+ })),
35887
36595
  withDesignCapability("block.weekly-slot-availability", weeklySlotAvailabilityBlockDefinition, sectionOnly(), defineBlockDesignMetadata({
35888
36596
  flow: offeringsFlow,
35889
36597
  layoutProfiles: [layoutProfile({
@@ -37782,6 +38490,7 @@ const SYSTEM_BLOCK_KINDS = [
37782
38490
  "block.form",
37783
38491
  "block.contact-newsletter",
37784
38492
  "block.appointment-booking",
38493
+ "block.appointment-services",
37785
38494
  "block.weekly-slot-availability",
37786
38495
  "block.event-registration",
37787
38496
  "block.event-details",
@@ -38060,18 +38769,13 @@ const navigationItemConfigSchema = z.object({
38060
38769
  isCta: z.boolean().optional()
38061
38770
  }).extend({ children: z.lazy(() => z.array(navigationItemConfigSchema)).optional() });
38062
38771
  const navigationMenuConfigSchema = z.object({
38063
- identifier: z.string().min(1, "Identifier is required"),
38064
- name: z.string().min(1, "Name is required"),
38065
- isPrimary: z.boolean().optional(),
38066
- isFooter: z.boolean().optional(),
38067
- footerRole: z.enum(["main", "legal-policy"]).nullable().optional(),
38772
+ location: z.enum([
38773
+ "main",
38774
+ "footer",
38775
+ "legal"
38776
+ ]),
38068
38777
  items: z.array(navigationItemConfigSchema)
38069
- }).superRefine((menu, ctx) => {
38070
- if (menu.footerRole !== void 0 && menu.isFooter !== void 0 && menu.isFooter !== (menu.footerRole === "main")) ctx.addIssue({
38071
- code: "custom",
38072
- message: "footerRole and isFooter disagree",
38073
- path: ["footerRole"]
38074
- });
38778
+ }).strict().superRefine((menu, ctx) => {
38075
38779
  const portalStats = collectPortalLinkStats(menu.items);
38076
38780
  if (portalStats.count > 1) ctx.addIssue({
38077
38781
  code: z.ZodIssueCode.custom,
@@ -38158,11 +38862,11 @@ const contentConfigSchema = z.object({
38158
38862
  });
38159
38863
  }
38160
38864
  if (data.navigationMenus && data.navigationMenus.length > 1) {
38161
- const identifiers = data.navigationMenus.map((m) => m.identifier);
38162
- const duplicateIds = identifiers.filter((id, i) => identifiers.indexOf(id) !== i);
38163
- if (duplicateIds.length > 0) ctx.addIssue({
38865
+ const locations = data.navigationMenus.map((m) => m.location);
38866
+ const duplicateLocations = locations.filter((location, i) => locations.indexOf(location) !== i);
38867
+ if (duplicateLocations.length > 0) ctx.addIssue({
38164
38868
  code: z.ZodIssueCode.custom,
38165
- message: `Navigation menu identifiers must be unique. Duplicates: ${duplicateIds.join(", ")}`,
38869
+ message: `Navigation locations must be unique. Duplicates: ${duplicateLocations.join(", ")}`,
38166
38870
  path: ["navigationMenus"]
38167
38871
  });
38168
38872
  }