@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
@@ -45,6 +45,14 @@ function fallbackBinding(...sources) {
45
45
  sources: [...sources]
46
46
  };
47
47
  }
48
+ //#endregion
49
+ //#region ../api/src/apiEndpointTypes.ts
50
+ function allowedDemoMutationPolicy() {
51
+ return {
52
+ classification: "allowed",
53
+ publicMessage: ""
54
+ };
55
+ }
48
56
  function blockedDemoMutationPolicy(classification, publicMessage) {
49
57
  if (publicMessage.trim() === "") throw new Error(`Demo mutation policy ${classification} requires publicMessage.`);
50
58
  return {
@@ -57,6 +65,7 @@ function blockedDemoMutationPolicy(classification, publicMessage) {
57
65
  const DEMO_MUTATION_BLOCKED_HIDDEN = blockedDemoMutationPolicy("blocked-hidden", "This action is not available in demo sites.");
58
66
  const DEMO_MUTATION_BLOCKED_DISABLED = blockedDemoMutationPolicy("blocked-disabled", "This setting is disabled in demo sites.");
59
67
  blockedDemoMutationPolicy("internal-only", "This internal operation is not available in demo sites.");
68
+ const DEMO_MUTATION_ALLOWED = allowedDemoMutationPolicy();
60
69
  //#endregion
61
70
  //#region ../api/src/cache-tags.ts
62
71
  /**
@@ -988,14 +997,117 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
988
997
  responseKind: "json",
989
998
  errors: ["auth:forbidden", "server:internal_error"]
990
999
  },
991
- getMailchimpSignupSyncHealth: {
992
- path: "/sites/{siteId}/integrations/mailchimp/signup-sync-health",
1000
+ getMailingListSetup: {
1001
+ path: "/sites/{siteId}/mailing-list/setup",
1002
+ method: "GET",
1003
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1004
+ auth: "user",
1005
+ responseKind: "json",
1006
+ errors: ["auth:forbidden", "server:internal_error"]
1007
+ },
1008
+ getMailingListAuthoringContext: {
1009
+ path: "/sites/{siteId}/mailing-list/authoring-context",
1010
+ method: "GET",
1011
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1012
+ auth: "user",
1013
+ responseKind: "json",
1014
+ errors: ["auth:forbidden", "server:internal_error"]
1015
+ },
1016
+ previewMailingListSetupChange: {
1017
+ path: "/sites/{siteId}/mailing-list/setup/preview",
1018
+ method: "POST",
1019
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1020
+ auth: "user",
1021
+ responseKind: "json",
1022
+ demoMutation: DEMO_MUTATION_ALLOWED,
1023
+ errors: [
1024
+ "auth:forbidden",
1025
+ "validation:invalid_input",
1026
+ "server:internal_error"
1027
+ ]
1028
+ },
1029
+ applyMailingListSetupChange: {
1030
+ path: "/sites/{siteId}/mailing-list/setup",
1031
+ method: "PUT",
1032
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1033
+ auth: "user",
1034
+ responseKind: "json",
1035
+ demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
1036
+ errors: [
1037
+ "auth:forbidden",
1038
+ "validation:invalid_input",
1039
+ "resource:conflict",
1040
+ "server:internal_error"
1041
+ ]
1042
+ },
1043
+ getMailingListProviderCatalog: {
1044
+ path: "/sites/{siteId}/mailing-list/providers/{provider}/catalog",
1045
+ method: "GET",
1046
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1047
+ auth: "user",
1048
+ responseKind: "json",
1049
+ errors: [
1050
+ "auth:forbidden",
1051
+ "validation:invalid_input",
1052
+ "server:internal_error"
1053
+ ]
1054
+ },
1055
+ refreshMailingListProviderCatalog: {
1056
+ path: "/sites/{siteId}/mailing-list/providers/{provider}/catalog/refresh",
1057
+ method: "POST",
1058
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1059
+ auth: "user",
1060
+ responseKind: "json",
1061
+ demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
1062
+ errors: [
1063
+ "auth:forbidden",
1064
+ "validation:invalid_input",
1065
+ "server:internal_error"
1066
+ ]
1067
+ },
1068
+ getMailingListProviderHealth: {
1069
+ path: "/sites/{siteId}/mailing-list/health",
1070
+ method: "GET",
1071
+ tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
1072
+ auth: "user",
1073
+ responseKind: "json",
1074
+ errors: ["auth:forbidden", "server:internal_error"]
1075
+ },
1076
+ getBrevoConnection: {
1077
+ path: "/sites/{siteId}/integrations/brevo/connection",
993
1078
  method: "GET",
994
1079
  tags: [TAG_TEMPLATES.site],
995
1080
  auth: "user",
996
1081
  responseKind: "json",
997
1082
  errors: ["auth:forbidden", "server:internal_error"]
998
1083
  },
1084
+ connectBrevo: {
1085
+ path: "/sites/{siteId}/integrations/brevo/connection",
1086
+ method: "PUT",
1087
+ tags: [TAG_TEMPLATES.site],
1088
+ auth: "user",
1089
+ responseKind: "json",
1090
+ demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
1091
+ errors: [
1092
+ "auth:forbidden",
1093
+ "validation:invalid_input",
1094
+ "resource:conflict",
1095
+ "server:internal_error"
1096
+ ]
1097
+ },
1098
+ disconnectBrevo: {
1099
+ path: "/sites/{siteId}/integrations/brevo/disconnect",
1100
+ method: "POST",
1101
+ tags: [TAG_TEMPLATES.site],
1102
+ auth: "user",
1103
+ responseKind: "json",
1104
+ demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
1105
+ errors: [
1106
+ "auth:forbidden",
1107
+ "resource:conflict",
1108
+ "server:internal_error"
1109
+ ]
1110
+ },
999
1111
  getMailchimpConnection: {
1000
1112
  path: "/sites/{siteId}/integrations/mailchimp/connection",
1001
1113
  method: "GET",
@@ -1219,994 +1331,329 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1219
1331
  }
1220
1332
  });
1221
1333
  //#endregion
1222
- //#region ../api/src/publicEventEndpointDefinitions.ts
1223
- const PUBLIC_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1224
- listPublicEventCalendarWindow: {
1225
- path: "/public/sites/{siteId}/events/calendar-window",
1226
- method: "GET",
1227
- revalidate: 0,
1228
- auth: "public",
1229
- responseKind: "json"
1230
- },
1231
- resolvePublicEventCheckoutReturn: {
1232
- path: "/public/sites/{siteId}/events/checkout-return",
1233
- method: "POST",
1234
- tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
1235
- auth: "public",
1236
- responseKind: "json"
1237
- },
1238
- getPublicEventRegistrationOptions: {
1239
- path: "/public/sites/{siteId}/events/registration-options",
1240
- method: "POST",
1241
- tags: [TAG_TEMPLATES.publicEvents, "event-registration-options"],
1242
- auth: "public",
1243
- responseKind: "json",
1244
- errors: [
1245
- "validation:invalid_input",
1246
- "resource:not_found",
1247
- "resource:conflict",
1248
- "server:internal_error"
1249
- ]
1250
- },
1251
- resolveEventSubroute: {
1252
- path: "/public/sites/{siteId}/events/subroutes/resolve",
1253
- method: "GET",
1254
- revalidate: 60,
1255
- tags: [TAG_TEMPLATES.publicEvents, "event-subroute"],
1256
- auth: "public",
1257
- responseKind: "json",
1258
- errors: []
1259
- }
1260
- });
1334
+ //#region ../core/src/result.ts
1335
+ function ok(value) {
1336
+ return {
1337
+ ok: true,
1338
+ value
1339
+ };
1340
+ }
1341
+ function err(error) {
1342
+ return {
1343
+ ok: false,
1344
+ error
1345
+ };
1346
+ }
1261
1347
  //#endregion
1262
- //#region ../api/src/publicEventCheckoutEndpointDefinitions.ts
1348
+ //#region ../core/src/branded.ts
1349
+ 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;
1350
+ function parseUuidBrand(value, unsafeConstructor) {
1351
+ if (typeof value !== "string") return err({ kind: "not_string" });
1352
+ const normalized = value.trim().toLowerCase();
1353
+ if (normalized.length === 0) return err({ kind: "empty_string" });
1354
+ if (!UUID_PATTERN$1.test(normalized)) return err({ kind: "invalid_uuid" });
1355
+ return ok(unsafeConstructor(normalized));
1356
+ }
1357
+ const unsafeAsSiteId = (id) => id;
1358
+ const unsafeAsApiKeyId = (id) => id;
1359
+ const unsafeAsEventOfferingId = (id) => id;
1360
+ const unsafeAsEventSeriesId = (id) => id;
1361
+ const unsafeAsUserId = (id) => id;
1362
+ const parseSiteId = (value) => parseUuidBrand(value, unsafeAsSiteId);
1363
+ const parseApiKeyId = (value) => parseUuidBrand(value, unsafeAsApiKeyId);
1364
+ const parseEventOfferingId = (value) => parseUuidBrand(value, unsafeAsEventOfferingId);
1365
+ const parseUserId = (value) => parseUuidBrand(value, unsafeAsUserId);
1366
+ const parseEventSeriesId = (value) => parseUuidBrand(value, unsafeAsEventSeriesId);
1367
+ //#endregion
1368
+ //#region ../core/src/assert-never.ts
1369
+ function assertNever$2(value, message = "Unexpected variant") {
1370
+ throw new Error(`${message}: ${String(value)}`);
1371
+ }
1372
+ //#endregion
1373
+ //#region ../core/src/currency-format.ts
1374
+ const CURRENCY_LOCALE_BY_CODE = {
1375
+ USD: "en-US",
1376
+ GBP: "en-GB",
1377
+ EUR: "de-DE",
1378
+ AUD: "en-AU",
1379
+ CAD: "en-CA",
1380
+ NZD: "en-NZ",
1381
+ JPY: "ja-JP",
1382
+ CNY: "zh-CN",
1383
+ CHF: "de-CH"
1384
+ };
1385
+ function resolveCurrencyLocale(currencyCode, override) {
1386
+ return override ?? CURRENCY_LOCALE_BY_CODE[currencyCode] ?? "en";
1387
+ }
1263
1388
  /**
1264
- * The public event registration/checkout endpoints, extracted from
1265
- * `endpoints.ts`.
1389
+ * The single currency-amount formatter for the product: minor units in,
1390
+ * localized string out.
1266
1391
  *
1267
- * They were moved out for the same reason the `sdkMedia*` block was: adding the
1268
- * `errors` metadata `require-api-endpoint-errors` asks of a newly-added endpoint
1269
- * pushed `endpoints.ts` past its `max-lines` baseline. The block is spread back
1270
- * at the exact position it occupied, so `API_ENDPOINTS` key order is unchanged.
1271
- * (The endpoint that originally motivated the move, `checkPublicEventEligibility`,
1272
- * was since deleted on dev by #1326.)
1392
+ * The locale is derived from the currency by default, so output is deterministic
1393
+ * across server and client (no SSR/hydration mismatch). All other money
1394
+ * formatters in the codebase should delegate here rather than constructing their
1395
+ * own `Intl.NumberFormat`.
1273
1396
  */
1274
- const PUBLIC_EVENT_CHECKOUT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1275
- registerForEvent: {
1276
- path: "/public/sites/{siteId}/events/register",
1277
- method: "POST",
1278
- tags: [TAG_TEMPLATES.publicEvents, "event-registration"],
1279
- auth: "public",
1280
- responseKind: "json"
1281
- },
1282
- createPublicEventCheckout: {
1283
- path: "/public/sites/{siteId}/events/checkout",
1284
- method: "POST",
1285
- tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
1286
- auth: "public",
1287
- responseKind: "json"
1397
+ function formatCurrencyAmount(amountCents, currency, options) {
1398
+ const currencyCode = currency.toUpperCase();
1399
+ const locale = resolveCurrencyLocale(currencyCode, options?.locale);
1400
+ try {
1401
+ const exactFormatter = new Intl.NumberFormat(locale, {
1402
+ style: "currency",
1403
+ currency: currencyCode
1404
+ });
1405
+ const fractionDigits = options?.decimals === "auto" ? {
1406
+ minimumFractionDigits: 0,
1407
+ maximumFractionDigits: exactFormatter.resolvedOptions().maximumFractionDigits
1408
+ } : {};
1409
+ return new Intl.NumberFormat(locale, {
1410
+ style: "currency",
1411
+ currency: currencyCode,
1412
+ ...fractionDigits
1413
+ }).format(amountCents / 100);
1414
+ } catch {
1415
+ return `${(amountCents / 100).toFixed(2)} ${currencyCode}`;
1288
1416
  }
1289
- });
1417
+ }
1290
1418
  //#endregion
1291
- //#region ../api/src/publicBookingAvailabilityEndpointDefinitions.ts
1419
+ //#region ../core/src/planner.ts
1420
+ /** Default clock for production callers that do not inject time explicitly. */
1421
+ const systemPlanningClock = { now: () => Date.now() };
1422
+ /** Resolve current time as a Date from an explicit Date, injected clock, or system clock. */
1423
+ function resolvePlanningNowDate(input = {}) {
1424
+ if (input.now !== void 0) return input.now;
1425
+ return new Date((input.clock ?? systemPlanningClock).now());
1426
+ }
1427
+ //#endregion
1428
+ //#region ../core/src/event-pricing.ts
1429
+ /** Capacity closes booking, but does not make an otherwise eligible price unknown. */
1430
+ function isPublicEventPricingOptionDisplayable(option) {
1431
+ if (!option.isActive) return false;
1432
+ if (option.availability.kind === "available") return true;
1433
+ const reason = option.availability.reason;
1434
+ switch (reason) {
1435
+ case "sold_out":
1436
+ case "overallCapacityUnavailable": return true;
1437
+ case "inactive":
1438
+ case "not_on_sale_yet":
1439
+ case "sale_ended":
1440
+ case "attendance_mode_unavailable": return false;
1441
+ default: return assertNever$2(reason);
1442
+ }
1443
+ }
1444
+ function publicEventPricingDisplay(pricing) {
1445
+ switch (pricing.kind) {
1446
+ case "free":
1447
+ case "single":
1448
+ case "sliding_scale": return canonicalEventPricingDisplay(pricing);
1449
+ case "tiered": return resolvePricingOptionsPriceDisplay({
1450
+ options: pricing.options.filter(isPublicEventPricingOptionDisplayable),
1451
+ currency: pricing.currency,
1452
+ purpose: "configured"
1453
+ });
1454
+ case "external": return {
1455
+ kind: "external",
1456
+ label: pricing.label
1457
+ };
1458
+ }
1459
+ }
1460
+ function publicEventPricingType(pricing) {
1461
+ switch (pricing.kind) {
1462
+ case "free": return "free";
1463
+ case "single":
1464
+ case "tiered":
1465
+ case "sliding_scale": return isFreePublicEventPriceDisplay(publicEventPricingDisplay(pricing)) ? "free" : "paid";
1466
+ case "external": return "external";
1467
+ }
1468
+ }
1469
+ function isFreePublicEventPriceDisplay(display) {
1470
+ switch (display.kind) {
1471
+ case "free": return true;
1472
+ case "exact":
1473
+ case "from":
1474
+ case "range":
1475
+ case "mixed":
1476
+ case "unavailable":
1477
+ case "external": return false;
1478
+ }
1479
+ }
1480
+ function resolvePricingOptionSaleWindowState(option, now) {
1481
+ if (option.saleStartsAt !== null && option.saleStartsAt !== void 0 && new Date(option.saleStartsAt) > now) return "not_started";
1482
+ if (option.saleEndsAt !== null && option.saleEndsAt !== void 0 && new Date(option.saleEndsAt) < now) return "ended";
1483
+ return "open";
1484
+ }
1485
+ function resolvePricingOptionsPriceDisplay(input) {
1486
+ const now = input.purpose === "available_now" ? resolvePlanningNowDate({
1487
+ now: input.now,
1488
+ clock: input.clock
1489
+ }) : null;
1490
+ const amounts = input.options.filter((option) => option.isActive && (now === null || resolvePricingOptionSaleWindowState(option, now) === "open")).map((option) => option.priceAmount);
1491
+ return amounts.length === 0 ? {
1492
+ kind: "unavailable",
1493
+ currency: input.currency
1494
+ } : amountRangeDisplay(amounts, input.currency);
1495
+ }
1496
+ function amountRangeDisplay(amounts, currency) {
1497
+ const minAmount = Math.min(...amounts);
1498
+ const maxAmount = Math.max(...amounts);
1499
+ if (minAmount === maxAmount) return minAmount === 0 ? {
1500
+ kind: "free",
1501
+ currency
1502
+ } : {
1503
+ kind: "exact",
1504
+ amount: minAmount,
1505
+ currency
1506
+ };
1507
+ if (minAmount === 0) {
1508
+ const paidAmounts = amounts.filter((amount) => amount > 0);
1509
+ return {
1510
+ kind: "mixed",
1511
+ paidFromAmount: Math.min(...paidAmounts),
1512
+ paidToAmount: maxAmount,
1513
+ currency
1514
+ };
1515
+ }
1516
+ return {
1517
+ kind: "range",
1518
+ minAmount,
1519
+ maxAmount,
1520
+ currency
1521
+ };
1522
+ }
1523
+ /** Derives configured price presentation directly from canonical pricing. */
1524
+ function canonicalEventPricingDisplay(pricing) {
1525
+ switch (pricing.kind) {
1526
+ case "free": return {
1527
+ kind: "free",
1528
+ currency: pricing.currency
1529
+ };
1530
+ case "single": return {
1531
+ kind: "exact",
1532
+ amount: pricing.amount,
1533
+ currency: pricing.currency
1534
+ };
1535
+ case "sliding_scale": return pricing.minimum === pricing.maximum ? {
1536
+ kind: "exact",
1537
+ amount: pricing.maximum,
1538
+ currency: pricing.currency
1539
+ } : {
1540
+ kind: "range",
1541
+ minAmount: pricing.minimum,
1542
+ maxAmount: pricing.maximum,
1543
+ currency: pricing.currency
1544
+ };
1545
+ case "tiered": return resolvePricingOptionsPriceDisplay({
1546
+ options: pricing.options,
1547
+ currency: pricing.currency,
1548
+ purpose: "configured"
1549
+ });
1550
+ default: return assertNever$2(pricing);
1551
+ }
1552
+ }
1553
+ //#endregion
1554
+ //#region ../core/src/event-page-surface-subject.ts
1555
+ function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
1556
+ const mode = eventDestinationMode ? { eventDestinationMode } : {};
1557
+ return bookingScope === "whole_series" ? {
1558
+ kind: "eventSeries",
1559
+ eventSeriesId,
1560
+ bookingScope,
1561
+ isWholeSeriesBooking: true,
1562
+ ...mode
1563
+ } : {
1564
+ kind: "eventSeries",
1565
+ eventSeriesId,
1566
+ bookingScope,
1567
+ ...mode
1568
+ };
1569
+ }
1570
+ function createEventOfferingPageSurfaceSubject(eventOfferingId, input) {
1571
+ const mode = input.eventDestinationMode ? { eventDestinationMode: input.eventDestinationMode } : {};
1572
+ const identity = {
1573
+ kind: "eventOffering",
1574
+ eventOfferingId,
1575
+ catalogueEventSeriesIds: input.catalogueEventSeriesIds
1576
+ };
1577
+ return input.bookingScope === "whole_series" ? {
1578
+ ...identity,
1579
+ bookingScope: input.bookingScope,
1580
+ isWholeSeriesBooking: true,
1581
+ ...mode
1582
+ } : {
1583
+ ...identity,
1584
+ bookingScope: input.bookingScope,
1585
+ ...mode
1586
+ };
1587
+ }
1292
1588
  /**
1293
- * Public appointment reads: the service catalogue and the bookable slot grid.
1294
- *
1295
- * These are the reads that appointment writes invalidate. They carry canonical
1296
- * `riverbank:*` tags because the frontend revalidate endpoint discards anything
1297
- * else, which is why the legacy `site-{siteId}` tag they used to declare could
1298
- * never actually be cleared (issue 1364).
1589
+ * Read the destination mode off a wire value.
1299
1590
  *
1300
- * Slot availability depends on both tags: the grid moves when a booking lands,
1301
- * and again when the service definition behind it changes.
1591
+ * Absent or unrecognised reads as absent, which leaves the routed Event record
1592
+ * as the only authority — the behaviour every caller had before the field
1593
+ * existed.
1302
1594
  */
1303
- const PUBLIC_BOOKING_AVAILABILITY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1304
- getPublicBookingServices: {
1305
- path: "/public/bookings/services",
1306
- method: "GET",
1307
- revalidate: 60,
1308
- tags: [TAG_TEMPLATES.publicBookingServices],
1309
- auth: "public",
1310
- responseKind: "json"
1311
- },
1312
- getPublicBookingService: {
1313
- path: "/public/bookings/services/{serviceId}",
1314
- method: "GET",
1315
- revalidate: 60,
1316
- tags: [TAG_TEMPLATES.publicBookingServices],
1317
- auth: "public",
1318
- responseKind: "json"
1319
- },
1320
- getPublicBookingResourcesReference: {
1321
- path: "/public/bookings/resources/reference",
1322
- method: "GET",
1323
- revalidate: 60,
1324
- tags: [TAG_TEMPLATES.publicBookingServices],
1325
- auth: "public",
1326
- responseKind: "json"
1327
- },
1328
- getPublicBookingAvailableDates: {
1329
- path: "/public/bookings/availability/dates",
1330
- method: "GET",
1331
- revalidate: 60,
1332
- tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
1333
- auth: "public",
1334
- responseKind: "json"
1335
- },
1336
- getPublicBookingAvailableSlots: {
1337
- path: "/public/bookings/availability/slots",
1338
- method: "GET",
1339
- revalidate: 60,
1340
- tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
1341
- auth: "public",
1342
- responseKind: "json"
1343
- },
1344
- getPublicWeeklySlotAvailability: {
1345
- path: "/public/bookings/weekly-slot-availability",
1346
- method: "GET",
1347
- revalidate: 60,
1348
- tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
1349
- auth: "public",
1350
- responseKind: "json",
1351
- errors: []
1595
+ function parseEventPageSurfaceDestinationMode(value) {
1596
+ return value === "cms-managed" || value === "system-managed" ? value : void 0;
1597
+ }
1598
+ function parseEventPageSurfaceSubject(value) {
1599
+ if (!isRecord$11(value)) return err({ kind: "notObject" });
1600
+ switch (value.kind) {
1601
+ case "eventSeries": {
1602
+ const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
1603
+ if (!eventSeriesId.ok) return err({
1604
+ kind: "invalidEventSeriesId",
1605
+ error: eventSeriesId.error
1606
+ });
1607
+ if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") return err({
1608
+ kind: "invalidEventSeriesBookingScope",
1609
+ bookingScope: value.bookingScope
1610
+ });
1611
+ return ok(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope, parseEventPageSurfaceDestinationMode(value.eventDestinationMode)));
1612
+ }
1613
+ case "eventOffering": {
1614
+ const eventOfferingId = parseEventOfferingId(value.eventOfferingId);
1615
+ if (!eventOfferingId.ok) return err({
1616
+ kind: "invalidEventOfferingId",
1617
+ error: eventOfferingId.error
1618
+ });
1619
+ if (!Array.isArray(value.catalogueEventSeriesIds)) return err({
1620
+ kind: "invalidEventOfferingCatalogue",
1621
+ catalogue: value.catalogueEventSeriesIds
1622
+ });
1623
+ if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") return err({
1624
+ kind: "invalidEventOfferingBookingScope",
1625
+ bookingScope: value.bookingScope
1626
+ });
1627
+ const parsedCatalogueIds = parseEventSeriesIdList(value.catalogueEventSeriesIds);
1628
+ if (!parsedCatalogueIds.ok) return parsedCatalogueIds;
1629
+ const eventDestinationMode = parseEventPageSurfaceDestinationMode(value.eventDestinationMode);
1630
+ return ok(createEventOfferingPageSurfaceSubject(eventOfferingId.value, {
1631
+ catalogueEventSeriesIds: parsedCatalogueIds.value,
1632
+ bookingScope: value.bookingScope,
1633
+ ...eventDestinationMode ? { eventDestinationMode } : {}
1634
+ }));
1635
+ }
1636
+ default: return err({
1637
+ kind: "unsupportedSubject",
1638
+ subjectKind: value.kind
1639
+ });
1352
1640
  }
1353
- });
1354
- //#endregion
1355
- //#region ../api/src/siteApiKeyEndpointDefinitions.ts
1356
- const SITE_API_KEY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1357
- listSiteApiKeys: {
1358
- path: "/sites/{siteId}/api-keys",
1359
- method: "GET",
1360
- auth: "user",
1361
- tags: ["site-{siteId}", "api-keys"],
1362
- responseKind: "json"
1363
- },
1364
- createSiteApiKey: {
1365
- path: "/sites/{siteId}/api-keys",
1366
- method: "POST",
1367
- auth: "user",
1368
- tags: ["site-{siteId}", "api-keys"],
1369
- responseKind: "json"
1370
- },
1371
- revokeSiteApiKey: {
1372
- path: "/sites/{siteId}/api-keys/{keyId}",
1373
- method: "DELETE",
1374
- auth: "user",
1375
- tags: ["site-{siteId}", "api-keys"],
1376
- responseKind: "json"
1377
- },
1378
- listSitePreviewKeys: {
1379
- path: "/sites/{siteId}/api-keys/preview",
1380
- method: "GET",
1381
- auth: "user",
1382
- tags: [
1383
- "site-{siteId}",
1384
- "api-keys",
1385
- "preview-key"
1386
- ],
1387
- errors: [],
1388
- responseKind: "json"
1389
- },
1390
- createSitePreviewKey: {
1391
- path: "/sites/{siteId}/api-keys/preview",
1392
- method: "POST",
1393
- auth: "user",
1394
- tags: [
1395
- "site-{siteId}",
1396
- "api-keys",
1397
- "preview-key"
1398
- ],
1399
- responseKind: "json"
1400
- },
1401
- getSiteApiKeyAccessLogs: {
1402
- path: "/sites/{siteId}/api-keys/access-logs",
1403
- method: "GET",
1404
- auth: "user",
1405
- tags: [
1406
- "site-{siteId}",
1407
- "api-keys",
1408
- "access-logs"
1409
- ],
1410
- responseKind: "json"
1411
- },
1412
- listManagementKeys: {
1413
- path: "/sites/{siteId}/api-keys/management",
1414
- method: "GET",
1415
- auth: "user",
1416
- tags: [
1417
- "site-{siteId}",
1418
- "api-keys",
1419
- "management-keys"
1420
- ],
1421
- responseKind: "json"
1422
- },
1423
- createManagementKey: {
1424
- path: "/sites/{siteId}/api-keys/management",
1425
- method: "POST",
1426
- auth: "user",
1427
- tags: [
1428
- "site-{siteId}",
1429
- "api-keys",
1430
- "management-keys"
1431
- ],
1432
- responseKind: "json"
1433
- },
1434
- revokeManagementKey: {
1435
- path: "/sites/{siteId}/api-keys/management",
1436
- method: "DELETE",
1437
- auth: "user",
1438
- tags: [
1439
- "site-{siteId}",
1440
- "api-keys",
1441
- "management-keys"
1442
- ],
1443
- responseKind: "json"
1444
- }
1445
- });
1446
- //#endregion
1447
- //#region ../api/src/eventManagementEndpointDefinitions.ts
1448
- const EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1449
- listEvents: {
1450
- path: "/sites/{siteId}/bookings/events",
1451
- method: "GET",
1452
- tags: ["site-{siteId}", "event-series-{siteId}"],
1453
- auth: "user",
1454
- responseKind: "json",
1455
- errors: []
1456
- },
1457
- createEvent: {
1458
- path: "/sites/{siteId}/bookings/events",
1459
- method: "POST",
1460
- tags: ["site-{siteId}", "event-series-{siteId}"],
1461
- auth: "user",
1462
- responseKind: "json",
1463
- errors: [
1464
- "validation:invalid_input",
1465
- "resource:already_exists",
1466
- "server:internal_error"
1467
- ]
1468
- },
1469
- transitionEventLifecycle: {
1470
- path: "/sites/{siteId}/bookings/events/{eventId}/lifecycle",
1471
- method: "POST",
1472
- tags: ["site-{siteId}", "event-series-{siteId}"],
1473
- auth: "user",
1474
- responseKind: "json",
1475
- errors: [
1476
- "validation:invalid_input",
1477
- "resource:not_found",
1478
- "resource:conflict",
1479
- "server:internal_error"
1480
- ]
1481
- },
1482
- updateEventIdentity: {
1483
- path: "/sites/{siteId}/bookings/events/{eventId}",
1484
- method: "PATCH",
1485
- tags: ["site-{siteId}", "event-series-{siteId}"],
1486
- auth: "user",
1487
- responseKind: "json",
1488
- errors: [
1489
- "validation:invalid_input",
1490
- "resource:not_found",
1491
- "server:internal_error"
1492
- ]
1493
- },
1494
- createWholeSeriesRun: {
1495
- path: "/sites/{siteId}/bookings/events/{eventId}/runs",
1496
- method: "POST",
1497
- tags: ["site-{siteId}", "event-series-{siteId}"],
1498
- auth: "user",
1499
- responseKind: "json",
1500
- errors: [
1501
- "validation:invalid_input",
1502
- "resource:not_found",
1503
- "resource:already_exists",
1504
- "server:internal_error"
1505
- ]
1506
- },
1507
- updateWholeSeriesRun: {
1508
- path: "/sites/{siteId}/bookings/events/{eventId}/runs/{runId}",
1509
- method: "PATCH",
1510
- tags: ["site-{siteId}", "event-series-{siteId}"],
1511
- auth: "user",
1512
- responseKind: "json",
1513
- errors: [
1514
- "validation:invalid_input",
1515
- "resource:not_found",
1516
- "resource:conflict",
1517
- "server:internal_error"
1518
- ]
1519
- },
1520
- cancelWholeSeriesRun: {
1521
- path: "/sites/{siteId}/bookings/events/{eventId}/runs/{runId}/cancellation",
1522
- method: "POST",
1523
- tags: ["site-{siteId}", "event-series-{siteId}"],
1524
- auth: "user",
1525
- responseKind: "json",
1526
- errors: [
1527
- "validation:invalid_input",
1528
- "resource:not_found",
1529
- "resource:conflict",
1530
- "server:internal_error"
1531
- ]
1532
- }
1533
- });
1534
- //#endregion
1535
- //#region ../api/src/giftingEndpointDefinitions.ts
1536
- /**
1537
- * Gifting endpoints (gift cards & vouchers) — the dashboard surface that manages
1538
- * offers, gifts and settings, plus the public buy/claim surface.
1539
- */
1540
- const GIFTING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1541
- createGiftingOffer: {
1542
- path: "/sites/{siteId}/gifting/offers",
1543
- method: "POST",
1544
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1545
- auth: "user",
1546
- responseKind: "json",
1547
- errors: [
1548
- "validation:invalid_input",
1549
- "auth:forbidden",
1550
- "server:internal_error"
1551
- ]
1552
- },
1553
- updateGiftingOffer: {
1554
- path: "/sites/{siteId}/gifting/offers/{offerId}",
1555
- method: "PATCH",
1556
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1557
- auth: "user",
1558
- responseKind: "json",
1559
- errors: [
1560
- "validation:invalid_input",
1561
- "auth:forbidden",
1562
- "resource:not_found",
1563
- "resource:conflict",
1564
- "server:internal_error"
1565
- ]
1566
- },
1567
- deleteGiftingOffer: {
1568
- path: "/sites/{siteId}/gifting/offers/{offerId}",
1569
- method: "DELETE",
1570
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1571
- auth: "user",
1572
- responseKind: "json",
1573
- errors: [
1574
- "auth:forbidden",
1575
- "resource:not_found",
1576
- "resource:conflict",
1577
- "server:internal_error"
1578
- ]
1579
- },
1580
- duplicateGiftingOffer: {
1581
- path: "/sites/{siteId}/gifting/offers/{offerId}/duplicate",
1582
- method: "POST",
1583
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1584
- auth: "user",
1585
- responseKind: "json",
1586
- errors: [
1587
- "auth:forbidden",
1588
- "resource:not_found",
1589
- "server:internal_error"
1590
- ]
1591
- },
1592
- reorderGiftingOffers: {
1593
- path: "/sites/{siteId}/gifting/offers/order",
1594
- method: "PUT",
1595
- tags: ["site-{siteId}", "gifting-offers-{siteId}"],
1596
- auth: "user",
1597
- responseKind: "json",
1598
- errors: [
1599
- "validation:invalid_input",
1600
- "auth:forbidden",
1601
- "resource:not_found",
1602
- "server:internal_error"
1603
- ]
1604
- },
1605
- sendGift: {
1606
- path: "/sites/{siteId}/gifting/gifts",
1607
- method: "POST",
1608
- tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
1609
- auth: "user",
1610
- demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
1611
- responseKind: "json",
1612
- errors: [
1613
- "validation:invalid_input",
1614
- "auth:forbidden",
1615
- "resource:not_found",
1616
- "server:internal_error"
1617
- ]
1618
- },
1619
- resendGift: {
1620
- path: "/sites/{siteId}/gifting/gifts/{giftId}/resend",
1621
- method: "POST",
1622
- tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
1623
- auth: "user",
1624
- demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
1625
- responseKind: "json",
1626
- errors: [
1627
- "auth:forbidden",
1628
- "resource:not_found",
1629
- "resource:conflict",
1630
- "server:internal_error"
1631
- ]
1632
- },
1633
- correctGiftEmailAndResend: {
1634
- path: "/sites/{siteId}/gifting/gifts/{giftId}/correct-email-and-resend",
1635
- method: "POST",
1636
- tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
1637
- auth: "user",
1638
- demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
1639
- responseKind: "json",
1640
- errors: [
1641
- "validation:invalid_input",
1642
- "auth:forbidden",
1643
- "resource:not_found",
1644
- "resource:conflict",
1645
- "server:internal_error"
1646
- ]
1647
- },
1648
- updateGiftingSettings: {
1649
- path: "/sites/{siteId}/gifting/settings",
1650
- method: "PATCH",
1651
- tags: [
1652
- "site-{siteId}",
1653
- "gifting-offers-{siteId}",
1654
- "gifting-gifts-{siteId}"
1655
- ],
1656
- auth: "user",
1657
- responseKind: "json",
1658
- errors: [
1659
- "validation:invalid_input",
1660
- "auth:forbidden",
1661
- "server:internal_error"
1662
- ]
1663
- },
1664
- listPublicGiftingOffers: {
1665
- path: "/public/sites/{siteId}/gifting/offers",
1666
- method: "GET",
1667
- revalidate: 300,
1668
- tags: [TAG_TEMPLATES.publicGifting],
1669
- errors: [],
1670
- auth: "public",
1671
- responseKind: "json"
1672
- },
1673
- createPublicGiftingCheckout: {
1674
- path: "/public/sites/{siteId}/gifting/checkout",
1675
- method: "POST",
1676
- tags: [TAG_TEMPLATES.publicGifting],
1677
- errors: [],
1678
- auth: "public",
1679
- responseKind: "json"
1680
- },
1681
- claimGiftCardPublic: {
1682
- path: "/public/sites/{siteId}/gifting/claim",
1683
- method: "POST",
1684
- tags: [TAG_TEMPLATES.publicGifting],
1685
- errors: [],
1686
- auth: "public",
1687
- responseKind: "json"
1688
- }
1689
- });
1690
- //#endregion
1691
- //#region ../api/src/sdkEndpointDefinitions.ts
1692
- /** SDK management endpoints that do not belong to a CRUD resource family. */
1693
- const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1694
- sdkUpdateSettings: {
1695
- path: "/sdk/{siteId}/settings",
1696
- method: "PATCH",
1697
- auth: "service",
1698
- responseKind: "json",
1699
- errors: ["validation:invalid_input", "server:internal_error"]
1700
- },
1701
- sdkConfigureBookingVertical: {
1702
- path: "/sdk/{siteId}/settings/booking/vertical",
1703
- method: "POST",
1704
- auth: "service",
1705
- responseKind: "json"
1706
- },
1707
- sdkSetupRevalidationWebhook: {
1708
- path: "/sdk/{siteId}/webhooks/revalidation/setup",
1709
- method: "POST",
1710
- auth: "service",
1711
- responseKind: "json",
1712
- errors: ["validation:invalid_input", "server:internal_error"]
1713
- }
1714
- });
1715
- //#endregion
1716
- //#region ../api/src/sdkMediaEndpointDefinitions.ts
1717
- /** SDK media lookup, listing, deletion and upload endpoints (management-key authenticated). */
1718
- const SDK_MEDIA_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
1719
- sdkMediaExists: {
1720
- path: "/sdk/{siteId}/media/exists",
1721
- method: "POST",
1722
- errors: ["validation:invalid_input"],
1723
- auth: "service",
1724
- responseKind: "json"
1725
- },
1726
- sdkMediaFindByChecksum: {
1727
- path: "/sdk/{siteId}/media/find-by-checksum",
1728
- method: "POST",
1729
- errors: ["server:internal_error"],
1730
- auth: "service",
1731
- responseKind: "json"
1732
- },
1733
- sdkMediaResolve: {
1734
- path: "/sdk/{siteId}/media/resolve",
1735
- method: "POST",
1736
- errors: ["server:internal_error"],
1737
- auth: "service",
1738
- responseKind: "json"
1739
- },
1740
- sdkMediaList: {
1741
- path: "/sdk/{siteId}/media",
1742
- method: "GET",
1743
- auth: "service",
1744
- errors: [],
1745
- responseKind: "json"
1746
- },
1747
- sdkMediaDownload: {
1748
- path: "/sdk/{siteId}/media/{identifier}/download",
1749
- method: "GET",
1750
- auth: "service",
1751
- errors: [],
1752
- responseKind: "json"
1753
- },
1754
- sdkMediaDeletePreview: {
1755
- path: "/sdk/{siteId}/media/{identifier}/delete-preview",
1756
- method: "POST",
1757
- auth: "service",
1758
- errors: [],
1759
- responseKind: "json"
1760
- },
1761
- sdkMediaDeleteApply: {
1762
- path: "/sdk/{siteId}/media/{identifier}",
1763
- method: "DELETE",
1764
- auth: "service",
1765
- errors: [],
1766
- responseKind: "json"
1767
- },
1768
- sdkMediaUploadUrl: {
1769
- path: "/sdk/{siteId}/media/upload-url",
1770
- method: "POST",
1771
- errors: [
1772
- "resource:not_found",
1773
- "validation:invalid_input",
1774
- "resource:conflict",
1775
- "validation:payload_too_large",
1776
- "billing:plan_limit_exceeded",
1777
- "server:internal_error"
1778
- ],
1779
- auth: "service",
1780
- responseKind: "json"
1781
- },
1782
- sdkMediaUploadFinalize: {
1783
- path: "/sdk/{siteId}/media/upload",
1784
- method: "POST",
1785
- errors: [
1786
- "resource:not_found",
1787
- "validation:invalid_input",
1788
- "resource:conflict",
1789
- "validation:payload_too_large",
1790
- "billing:plan_limit_exceeded",
1791
- "server:internal_error"
1792
- ],
1793
- auth: "service",
1794
- responseKind: "json"
1795
- }
1796
- });
1797
- ({
1798
- ...ACCOUNT_ENDPOINT_DEFINITIONS,
1799
- ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
1800
- ...GIFTING_ENDPOINT_DEFINITIONS
1801
- }), 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, {
1802
- ...NEWSLETTER_ENDPOINT_DEFINITIONS,
1803
- ...APPOINTMENT_ENDPOINT_DEFINITIONS,
1804
- ...EVENT_SERIES_REGISTRATION_ENDPOINT_DEFINITIONS
1805
- }, 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 };
1806
- z.object({ refreshToken: z.string().min(1).max(4096) }).strict();
1807
- z.object({
1808
- accessToken: z.string().min(1).max(4096),
1809
- refreshToken: z.string().min(1).max(4096)
1810
- }).strict();
1811
- const ACCOUNT_SITE_CURSOR_MAX_LENGTH = 2048;
1812
- 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})$/;
1813
- /** Strict, injection-safe PostgreSQL/RFC3339 timestamp text used in cursors. */
1814
- function isAccountSiteTimestamp(value) {
1815
- if (typeof value !== "string") return false;
1816
- const match = ACCOUNT_SITE_TIMESTAMP_PATTERN.exec(value);
1817
- if (!match) return false;
1818
- const [, year, month, day, hour, minute, second, , offset] = match;
1819
- const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
1820
- const offsetValue = offset ?? "";
1821
- const offsetIsValid = offsetValue === "Z" || (() => {
1822
- const offsetMatch = /^(?:[+-])(\d{2}):(\d{2})$/.exec(offsetValue);
1823
- if (!offsetMatch) return false;
1824
- return Number(offsetMatch[1]) <= 23 && Number(offsetMatch[2]) <= 59;
1825
- })();
1826
- return date.getUTCFullYear() === Number(year) && date.getUTCMonth() === Number(month) - 1 && date.getUTCDate() === Number(day) && Number(hour) <= 23 && Number(minute) <= 59 && Number(second) <= 59 && offsetIsValid;
1827
- }
1828
- const accountSiteTimestampSchema = z.string().refine(isAccountSiteTimestamp, { message: "Expected a strict account site timestamp" });
1829
- z.enum([
1830
- "managed",
1831
- "headless_cms",
1832
- "headless_full"
1833
- ]);
1834
- z.object({ title: z.string().trim().min(1).max(120) });
1835
- const abandonedStandardKeyIdsSchema = z.object({
1836
- live: z.string().uuid().optional(),
1837
- management: z.string().uuid().optional()
1838
- }).strict();
1839
- const abandonedPreviewKeyIdsSchema = z.object({
1840
- live: z.string().uuid().optional(),
1841
- management: z.string().uuid().optional(),
1842
- preview: z.string().uuid().optional()
1843
- }).strict();
1844
- z.discriminatedUnion("operation", [
1845
- z.object({
1846
- operation: z.literal("issue_standard"),
1847
- siteId: z.string().uuid(),
1848
- operationId: z.string().uuid(),
1849
- projectName: z.string().trim().min(1).max(120),
1850
- abandonedKeyIds: abandonedStandardKeyIdsSchema.default({})
1851
- }).strict(),
1852
- z.object({
1853
- operation: z.literal("issue_with_preview"),
1854
- siteId: z.string().uuid(),
1855
- operationId: z.string().uuid(),
1856
- projectName: z.string().trim().min(1).max(120),
1857
- abandonedKeyIds: abandonedPreviewKeyIdsSchema.default({})
1858
- }).strict(),
1859
- z.object({
1860
- operation: z.literal("revalidate_standard"),
1861
- siteId: z.string().uuid(),
1862
- operationId: z.string().uuid(),
1863
- projectName: z.string().trim().min(1).max(120),
1864
- keyIds: z.object({
1865
- live: z.string().uuid(),
1866
- management: z.string().uuid()
1867
- }).strict()
1868
- }).strict(),
1869
- z.object({
1870
- operation: z.literal("revalidate_with_preview"),
1871
- siteId: z.string().uuid(),
1872
- operationId: z.string().uuid(),
1873
- projectName: z.string().trim().min(1).max(120),
1874
- keyIds: z.object({
1875
- live: z.string().uuid(),
1876
- management: z.string().uuid(),
1877
- preview: z.string().uuid()
1878
- }).strict()
1879
- }).strict()
1880
- ]);
1881
- z.object({
1882
- cursor: z.string().min(1).max(ACCOUNT_SITE_CURSOR_MAX_LENGTH).optional(),
1883
- limit: z.coerce.number().int().min(1).max(100).default(50),
1884
- search: z.string().trim().min(1).max(120).optional()
1885
- });
1886
- //#endregion
1887
- //#region ../core/src/result.ts
1888
- function ok(value) {
1889
- return {
1890
- ok: true,
1891
- value
1892
- };
1893
- }
1894
- function err(error) {
1895
- return {
1896
- ok: false,
1897
- error
1898
- };
1899
- }
1900
- //#endregion
1901
- //#region ../core/src/branded.ts
1902
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1903
- function parseUuidBrand(value, unsafeConstructor) {
1904
- if (typeof value !== "string") return err({ kind: "not_string" });
1905
- const normalized = value.trim().toLowerCase();
1906
- if (normalized.length === 0) return err({ kind: "empty_string" });
1907
- if (!UUID_PATTERN.test(normalized)) return err({ kind: "invalid_uuid" });
1908
- return ok(unsafeConstructor(normalized));
1909
- }
1910
- const unsafeAsSiteId = (id) => id;
1911
- const unsafeAsApiKeyId = (id) => id;
1912
- const unsafeAsEventOfferingId = (id) => id;
1913
- const unsafeAsEventSeriesId = (id) => id;
1914
- const unsafeAsUserId = (id) => id;
1915
- const parseSiteId = (value) => parseUuidBrand(value, unsafeAsSiteId);
1916
- const parseApiKeyId = (value) => parseUuidBrand(value, unsafeAsApiKeyId);
1917
- const parseEventOfferingId = (value) => parseUuidBrand(value, unsafeAsEventOfferingId);
1918
- const parseUserId = (value) => parseUuidBrand(value, unsafeAsUserId);
1919
- const parseEventSeriesId = (value) => parseUuidBrand(value, unsafeAsEventSeriesId);
1920
- //#endregion
1921
- //#region ../core/src/assert-never.ts
1922
- function assertNever$2(value, message = "Unexpected variant") {
1923
- throw new Error(`${message}: ${String(value)}`);
1924
- }
1925
- //#endregion
1926
- //#region ../core/src/currency-format.ts
1927
- const CURRENCY_LOCALE_BY_CODE = {
1928
- USD: "en-US",
1929
- GBP: "en-GB",
1930
- EUR: "de-DE",
1931
- AUD: "en-AU",
1932
- CAD: "en-CA",
1933
- NZD: "en-NZ",
1934
- JPY: "ja-JP",
1935
- CNY: "zh-CN",
1936
- CHF: "de-CH"
1937
- };
1938
- function resolveCurrencyLocale(currencyCode, override) {
1939
- return override ?? CURRENCY_LOCALE_BY_CODE[currencyCode] ?? "en";
1940
- }
1941
- /**
1942
- * The single currency-amount formatter for the product: minor units in,
1943
- * localized string out.
1944
- *
1945
- * The locale is derived from the currency by default, so output is deterministic
1946
- * across server and client (no SSR/hydration mismatch). All other money
1947
- * formatters in the codebase should delegate here rather than constructing their
1948
- * own `Intl.NumberFormat`.
1949
- */
1950
- function formatCurrencyAmount(amountCents, currency, options) {
1951
- const currencyCode = currency.toUpperCase();
1952
- const locale = resolveCurrencyLocale(currencyCode, options?.locale);
1953
- try {
1954
- const exactFormatter = new Intl.NumberFormat(locale, {
1955
- style: "currency",
1956
- currency: currencyCode
1957
- });
1958
- const fractionDigits = options?.decimals === "auto" ? {
1959
- minimumFractionDigits: 0,
1960
- maximumFractionDigits: exactFormatter.resolvedOptions().maximumFractionDigits
1961
- } : {};
1962
- return new Intl.NumberFormat(locale, {
1963
- style: "currency",
1964
- currency: currencyCode,
1965
- ...fractionDigits
1966
- }).format(amountCents / 100);
1967
- } catch {
1968
- return `${(amountCents / 100).toFixed(2)} ${currencyCode}`;
1969
- }
1970
- }
1971
- //#endregion
1972
- //#region ../core/src/planner.ts
1973
- /** Default clock for production callers that do not inject time explicitly. */
1974
- const systemPlanningClock = { now: () => Date.now() };
1975
- /** Resolve current time as a Date from an explicit Date, injected clock, or system clock. */
1976
- function resolvePlanningNowDate(input = {}) {
1977
- if (input.now !== void 0) return input.now;
1978
- return new Date((input.clock ?? systemPlanningClock).now());
1979
- }
1980
- //#endregion
1981
- //#region ../core/src/event-pricing.ts
1982
- /** Capacity closes booking, but does not make an otherwise eligible price unknown. */
1983
- function isPublicEventPricingOptionDisplayable(option) {
1984
- if (!option.isActive) return false;
1985
- if (option.availability.kind === "available") return true;
1986
- const reason = option.availability.reason;
1987
- switch (reason) {
1988
- case "sold_out":
1989
- case "overallCapacityUnavailable": return true;
1990
- case "inactive":
1991
- case "not_on_sale_yet":
1992
- case "sale_ended":
1993
- case "attendance_mode_unavailable": return false;
1994
- default: return assertNever$2(reason);
1995
- }
1996
- }
1997
- function publicEventPricingDisplay(pricing) {
1998
- switch (pricing.kind) {
1999
- case "free":
2000
- case "single":
2001
- case "sliding_scale": return canonicalEventPricingDisplay(pricing);
2002
- case "tiered": return resolvePricingOptionsPriceDisplay({
2003
- options: pricing.options.filter(isPublicEventPricingOptionDisplayable),
2004
- currency: pricing.currency,
2005
- purpose: "configured"
2006
- });
2007
- case "external": return {
2008
- kind: "external",
2009
- label: pricing.label
2010
- };
2011
- }
2012
- }
2013
- function publicEventPricingType(pricing) {
2014
- switch (pricing.kind) {
2015
- case "free": return "free";
2016
- case "single":
2017
- case "tiered":
2018
- case "sliding_scale": return isFreePublicEventPriceDisplay(publicEventPricingDisplay(pricing)) ? "free" : "paid";
2019
- case "external": return "external";
2020
- }
2021
- }
2022
- function isFreePublicEventPriceDisplay(display) {
2023
- switch (display.kind) {
2024
- case "free": return true;
2025
- case "exact":
2026
- case "from":
2027
- case "range":
2028
- case "mixed":
2029
- case "unavailable":
2030
- case "external": return false;
2031
- }
2032
- }
2033
- function resolvePricingOptionSaleWindowState(option, now) {
2034
- if (option.saleStartsAt !== null && option.saleStartsAt !== void 0 && new Date(option.saleStartsAt) > now) return "not_started";
2035
- if (option.saleEndsAt !== null && option.saleEndsAt !== void 0 && new Date(option.saleEndsAt) < now) return "ended";
2036
- return "open";
2037
- }
2038
- function resolvePricingOptionsPriceDisplay(input) {
2039
- const now = input.purpose === "available_now" ? resolvePlanningNowDate({
2040
- now: input.now,
2041
- clock: input.clock
2042
- }) : null;
2043
- const amounts = input.options.filter((option) => option.isActive && (now === null || resolvePricingOptionSaleWindowState(option, now) === "open")).map((option) => option.priceAmount);
2044
- return amounts.length === 0 ? {
2045
- kind: "unavailable",
2046
- currency: input.currency
2047
- } : amountRangeDisplay(amounts, input.currency);
2048
- }
2049
- function amountRangeDisplay(amounts, currency) {
2050
- const minAmount = Math.min(...amounts);
2051
- const maxAmount = Math.max(...amounts);
2052
- if (minAmount === maxAmount) return minAmount === 0 ? {
2053
- kind: "free",
2054
- currency
2055
- } : {
2056
- kind: "exact",
2057
- amount: minAmount,
2058
- currency
2059
- };
2060
- if (minAmount === 0) {
2061
- const paidAmounts = amounts.filter((amount) => amount > 0);
2062
- return {
2063
- kind: "mixed",
2064
- paidFromAmount: Math.min(...paidAmounts),
2065
- paidToAmount: maxAmount,
2066
- currency
2067
- };
2068
- }
2069
- return {
2070
- kind: "range",
2071
- minAmount,
2072
- maxAmount,
2073
- currency
2074
- };
2075
- }
2076
- /** Derives configured price presentation directly from canonical pricing. */
2077
- function canonicalEventPricingDisplay(pricing) {
2078
- switch (pricing.kind) {
2079
- case "free": return {
2080
- kind: "free",
2081
- currency: pricing.currency
2082
- };
2083
- case "single": return {
2084
- kind: "exact",
2085
- amount: pricing.amount,
2086
- currency: pricing.currency
2087
- };
2088
- case "sliding_scale": return pricing.minimum === pricing.maximum ? {
2089
- kind: "exact",
2090
- amount: pricing.maximum,
2091
- currency: pricing.currency
2092
- } : {
2093
- kind: "range",
2094
- minAmount: pricing.minimum,
2095
- maxAmount: pricing.maximum,
2096
- currency: pricing.currency
2097
- };
2098
- case "tiered": return resolvePricingOptionsPriceDisplay({
2099
- options: pricing.options,
2100
- currency: pricing.currency,
2101
- purpose: "configured"
2102
- });
2103
- default: return assertNever$2(pricing);
2104
- }
2105
- }
2106
- //#endregion
2107
- //#region ../core/src/event-page-surface-subject.ts
2108
- function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope, eventDestinationMode) {
2109
- const mode = eventDestinationMode ? { eventDestinationMode } : {};
2110
- return bookingScope === "whole_series" ? {
2111
- kind: "eventSeries",
2112
- eventSeriesId,
2113
- bookingScope,
2114
- isWholeSeriesBooking: true,
2115
- ...mode
2116
- } : {
2117
- kind: "eventSeries",
2118
- eventSeriesId,
2119
- bookingScope,
2120
- ...mode
2121
- };
2122
- }
2123
- function createEventOfferingPageSurfaceSubject(eventOfferingId, input) {
2124
- const mode = input.eventDestinationMode ? { eventDestinationMode: input.eventDestinationMode } : {};
2125
- const identity = {
2126
- kind: "eventOffering",
2127
- eventOfferingId,
2128
- catalogueEventSeriesIds: input.catalogueEventSeriesIds
2129
- };
2130
- return input.bookingScope === "whole_series" ? {
2131
- ...identity,
2132
- bookingScope: input.bookingScope,
2133
- isWholeSeriesBooking: true,
2134
- ...mode
2135
- } : {
2136
- ...identity,
2137
- bookingScope: input.bookingScope,
2138
- ...mode
2139
- };
2140
- }
2141
- /**
2142
- * Read the destination mode off a wire value.
2143
- *
2144
- * Absent or unrecognised reads as absent, which leaves the routed Event record
2145
- * as the only authority — the behaviour every caller had before the field
2146
- * existed.
2147
- */
2148
- function parseEventPageSurfaceDestinationMode(value) {
2149
- return value === "cms-managed" || value === "system-managed" ? value : void 0;
2150
- }
2151
- function parseEventPageSurfaceSubject(value) {
2152
- if (!isRecord$8(value)) return err({ kind: "notObject" });
2153
- switch (value.kind) {
2154
- case "eventSeries": {
2155
- const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
2156
- if (!eventSeriesId.ok) return err({
2157
- kind: "invalidEventSeriesId",
2158
- error: eventSeriesId.error
2159
- });
2160
- if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") return err({
2161
- kind: "invalidEventSeriesBookingScope",
2162
- bookingScope: value.bookingScope
2163
- });
2164
- return ok(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope, parseEventPageSurfaceDestinationMode(value.eventDestinationMode)));
2165
- }
2166
- case "eventOffering": {
2167
- const eventOfferingId = parseEventOfferingId(value.eventOfferingId);
2168
- if (!eventOfferingId.ok) return err({
2169
- kind: "invalidEventOfferingId",
2170
- error: eventOfferingId.error
2171
- });
2172
- if (!Array.isArray(value.catalogueEventSeriesIds)) return err({
2173
- kind: "invalidEventOfferingCatalogue",
2174
- catalogue: value.catalogueEventSeriesIds
2175
- });
2176
- if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") return err({
2177
- kind: "invalidEventOfferingBookingScope",
2178
- bookingScope: value.bookingScope
2179
- });
2180
- const parsedCatalogueIds = parseEventSeriesIdList(value.catalogueEventSeriesIds);
2181
- if (!parsedCatalogueIds.ok) return parsedCatalogueIds;
2182
- const eventDestinationMode = parseEventPageSurfaceDestinationMode(value.eventDestinationMode);
2183
- return ok(createEventOfferingPageSurfaceSubject(eventOfferingId.value, {
2184
- catalogueEventSeriesIds: parsedCatalogueIds.value,
2185
- bookingScope: value.bookingScope,
2186
- ...eventDestinationMode ? { eventDestinationMode } : {}
2187
- }));
2188
- }
2189
- default: return err({
2190
- kind: "unsupportedSubject",
2191
- subjectKind: value.kind
2192
- });
2193
- }
2194
- }
2195
- function parseEventSeriesIdList(values) {
2196
- const parsed = [];
2197
- for (const value of values) {
2198
- const eventSeriesId = parseEventSeriesId(value);
2199
- if (!eventSeriesId.ok) return err({
2200
- kind: "invalidEventSeriesId",
2201
- error: eventSeriesId.error
2202
- });
2203
- parsed.push(eventSeriesId.value);
2204
- }
2205
- return ok(parsed);
2206
- }
2207
- function isRecord$8(value) {
2208
- return typeof value === "object" && value !== null && !Array.isArray(value);
2209
- }
1641
+ }
1642
+ function parseEventSeriesIdList(values) {
1643
+ const parsed = [];
1644
+ for (const value of values) {
1645
+ const eventSeriesId = parseEventSeriesId(value);
1646
+ if (!eventSeriesId.ok) return err({
1647
+ kind: "invalidEventSeriesId",
1648
+ error: eventSeriesId.error
1649
+ });
1650
+ parsed.push(eventSeriesId.value);
1651
+ }
1652
+ return ok(parsed);
1653
+ }
1654
+ function isRecord$11(value) {
1655
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1656
+ }
2210
1657
  //#endregion
2211
1658
  //#region ../core/src/booking-time-zone.ts
2212
1659
  const TIME_ZONE_VALIDITY_CACHE_LIMIT = 100;
@@ -2375,7 +1822,7 @@ function toLocationCoordinates(value) {
2375
1822
  }
2376
1823
  //#endregion
2377
1824
  //#region ../core/src/rich-text.ts
2378
- function isRecord$7(value) {
1825
+ function isRecord$10(value) {
2379
1826
  return typeof value === "object" && value !== null && !Array.isArray(value);
2380
1827
  }
2381
1828
  function hasOnlyKeys(value, allowed) {
@@ -2395,20 +1842,390 @@ function isDenseArrayOf(value, predicate) {
2395
1842
  return true;
2396
1843
  }
2397
1844
  function isTipTapMark(value) {
2398
- return isRecord$7(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$7(value.attrs));
1845
+ return isRecord$10(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$10(value.attrs));
2399
1846
  }
2400
1847
  function isTipTapNode(value) {
2401
- if (!isRecord$7(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== void 0 && !isRecord$7(value.attrs) || value.marks !== void 0 && (!Array.isArray(value.marks) || !isDenseArrayOf(value.marks, isTipTapMark)) || value.content !== void 0 && (!Array.isArray(value.content) || !isDenseArrayOf(value.content, isTipTapNode))) return false;
1848
+ if (!isRecord$10(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== void 0 && !isRecord$10(value.attrs) || value.marks !== void 0 && (!Array.isArray(value.marks) || !isDenseArrayOf(value.marks, isTipTapMark)) || value.content !== void 0 && (!Array.isArray(value.content) || !isDenseArrayOf(value.content, isTipTapNode))) return false;
2402
1849
  if (value.type === "text") return typeof value.text === "string" && value.text.length > 0 && value.content === void 0;
2403
1850
  return value.text === void 0;
2404
1851
  }
2405
1852
  function isCanonicalRichTextDoc(value) {
2406
- return isRecord$7(value) && hasOnlyKeys(value, rootKeys) && value.type === "doc" && Array.isArray(value.content) && isDenseArrayOf(value.content, isTipTapNode);
1853
+ return isRecord$10(value) && hasOnlyKeys(value, rootKeys) && value.type === "doc" && Array.isArray(value.content) && isDenseArrayOf(value.content, isTipTapNode);
1854
+ }
1855
+ //#endregion
1856
+ //#region ../core/src/mailing-list-identifiers.ts
1857
+ 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;
1858
+ const OPAQUE_ID_PATTERN = /^[A-Za-z0-9_-]{1,100}$/u;
1859
+ function parseTrimmedMatchingString(value, pattern) {
1860
+ if (typeof value !== "string") return null;
1861
+ const trimmed = value.trim();
1862
+ return pattern.test(trimmed) ? trimmed : null;
1863
+ }
1864
+ function parsePositiveInteger(value) {
1865
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1866
+ }
1867
+ function parseNewsletterListId(value) {
1868
+ return parseTrimmedMatchingString(value, UUID_PATTERN);
1869
+ }
1870
+ function parseMailchimpAudienceId(value) {
1871
+ return parseTrimmedMatchingString(value, OPAQUE_ID_PATTERN);
1872
+ }
1873
+ function parseMailchimpInterestId(value) {
1874
+ return parseTrimmedMatchingString(value, OPAQUE_ID_PATTERN);
1875
+ }
1876
+ function parseBrevoListId(value) {
1877
+ return parsePositiveInteger(value);
1878
+ }
1879
+ //#endregion
1880
+ //#region ../core/src/mailing-list-setup-parser.ts
1881
+ function parseMailingListGlobalForm(value) {
1882
+ if (!isRecord$9(value)) return formError("shape");
1883
+ const fields = parseGlobalFields(value.fields);
1884
+ if (fields === null) return formError("fields");
1885
+ if (typeof value.includeOptionalPhone !== "boolean") return formError("includeOptionalPhone");
1886
+ const buttonLabel = parseRequiredString(value.buttonLabel);
1887
+ if (buttonLabel === null) return formError("buttonLabel");
1888
+ const privacyNote = parseOptionalString(value.privacyNote);
1889
+ if (privacyNote === void 0) return formError("privacyNote");
1890
+ return ok({
1891
+ fields,
1892
+ includeOptionalPhone: value.includeOptionalPhone,
1893
+ buttonLabel,
1894
+ privacyNote
1895
+ });
1896
+ }
1897
+ function parseGlobalFields(value) {
1898
+ if (!isRecord$9(value) || typeof value.preset !== "string") return null;
1899
+ switch (value.preset) {
1900
+ case "emailOnly": return { preset: "emailOnly" };
1901
+ case "emailName": return isRequirement(value.name) ? {
1902
+ preset: "emailName",
1903
+ name: value.name
1904
+ } : null;
1905
+ case "emailFirstLast": return isRequirement(value.firstName) && isRequirement(value.lastName) ? {
1906
+ preset: "emailFirstLast",
1907
+ firstName: value.firstName,
1908
+ lastName: value.lastName
1909
+ } : null;
1910
+ case "emailNameCompany": return isRequirement(value.name) && isRequirement(value.company) ? {
1911
+ preset: "emailNameCompany",
1912
+ name: value.name,
1913
+ company: value.company
1914
+ } : null;
1915
+ default: return null;
1916
+ }
1917
+ }
1918
+ function parseMailingListSetupRevision(value) {
1919
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1920
+ }
1921
+ function parseRequiredString(value) {
1922
+ if (typeof value !== "string") return null;
1923
+ const trimmed = value.trim();
1924
+ return trimmed.length > 0 ? trimmed : null;
1925
+ }
1926
+ function parseOptionalString(value) {
1927
+ if (value === null || value === void 0) return null;
1928
+ if (typeof value !== "string") return void 0;
1929
+ const trimmed = value.trim();
1930
+ return trimmed.length > 0 ? trimmed : null;
1931
+ }
1932
+ function isRequirement(value) {
1933
+ return value === "required" || value === "optional";
1934
+ }
1935
+ function isRecord$9(value) {
1936
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1937
+ }
1938
+ function formError(field) {
1939
+ return err({
1940
+ tag: "invalid_mailing_list_global_form",
1941
+ field
1942
+ });
1943
+ }
1944
+ //#endregion
1945
+ //#region ../core/src/mailing-list-targeting.ts
1946
+ function planMailingListChoicePresentation(targeting) {
1947
+ if (targeting.mode === "default" || targeting.selection.tag === "fixed") return { tag: "hidden" };
1948
+ switch (targeting.provider) {
1949
+ case "native": return targeting.selection.offeredTopicIds.length === 1 ? {
1950
+ tag: "automatic",
1951
+ selection: {
1952
+ provider: "native",
1953
+ topicIds: targeting.selection.offeredTopicIds
1954
+ }
1955
+ } : {
1956
+ tag: "visitor_choice",
1957
+ provider: "native",
1958
+ optionCount: targeting.selection.offeredTopicIds.length
1959
+ };
1960
+ case "brevo": return targeting.selection.offeredListIds.length === 1 ? {
1961
+ tag: "automatic",
1962
+ selection: {
1963
+ provider: "brevo",
1964
+ listIds: targeting.selection.offeredListIds
1965
+ }
1966
+ } : {
1967
+ tag: "visitor_choice",
1968
+ provider: "brevo",
1969
+ optionCount: targeting.selection.offeredListIds.length
1970
+ };
1971
+ case "mailchimp": return targeting.selection.offeredInterestIds.length === 1 ? {
1972
+ tag: "automatic",
1973
+ selection: {
1974
+ provider: "mailchimp",
1975
+ interestIds: targeting.selection.offeredInterestIds
1976
+ }
1977
+ } : {
1978
+ tag: "visitor_choice",
1979
+ provider: "mailchimp",
1980
+ optionCount: targeting.selection.offeredInterestIds.length
1981
+ };
1982
+ }
1983
+ }
1984
+ //#endregion
1985
+ //#region ../core/src/mailing-list-runtime.ts
1986
+ function parseMailingListRuntimeContext(value) {
1987
+ if (!isRecord$8(value)) return invalidRuntimeContext();
1988
+ if (value.tag === "not_configured") return ok({ tag: "not_configured" });
1989
+ if (value.tag !== "configured") return invalidRuntimeContext();
1990
+ const revision = parseMailingListSetupRevision(value.revision);
1991
+ const form = parseMailingListGlobalForm(value.form);
1992
+ const provider = parseRuntimeProvider(value.provider);
1993
+ if (revision === null || !form.ok || provider === null) return invalidRuntimeContext();
1994
+ return ok({
1995
+ tag: "configured",
1996
+ revision,
1997
+ form: form.value,
1998
+ provider
1999
+ });
2000
+ }
2001
+ function parseRuntimeProvider(value) {
2002
+ if (!isRecord$8(value)) return null;
2003
+ switch (value.provider) {
2004
+ case "native": return parseNativeRuntimeProvider(value);
2005
+ case "brevo": return parseBrevoRuntimeProvider(value);
2006
+ case "mailchimp": return parseMailchimpRuntimeProvider(value);
2007
+ default: return null;
2008
+ }
2009
+ }
2010
+ function parseNativeRuntimeProvider(value) {
2011
+ const defaultTopicId = parseNewsletterListId(value.defaultTopicId);
2012
+ const topics = parseNamedItems(value.topics, parseNewsletterListId);
2013
+ return defaultTopicId !== null && topics !== null ? {
2014
+ provider: "native",
2015
+ defaultTopicId,
2016
+ topics
2017
+ } : null;
2018
+ }
2019
+ function parseBrevoRuntimeProvider(value) {
2020
+ const defaultListId = parseBrevoListId(value.defaultListId);
2021
+ const lists = parseNamedItems(value.lists, parseBrevoListId);
2022
+ return defaultListId !== null && lists !== null ? {
2023
+ provider: "brevo",
2024
+ defaultListId,
2025
+ lists
2026
+ } : null;
2027
+ }
2028
+ function parseMailchimpRuntimeProvider(value) {
2029
+ if (!Array.isArray(value.audiences)) return null;
2030
+ const defaultAudienceId = parseMailchimpAudienceId(value.defaultAudienceId);
2031
+ if (defaultAudienceId === null) return null;
2032
+ const audiences = [];
2033
+ for (const audience of value.audiences) {
2034
+ if (!isRecord$8(audience)) return null;
2035
+ const id = parseMailchimpAudienceId(audience.id);
2036
+ const name = parseName(audience.name);
2037
+ if (id === null || name === null || !Array.isArray(audience.interests)) return null;
2038
+ const interests = parseMailchimpInterests(audience.interests);
2039
+ if (interests === null) return null;
2040
+ audiences.push({
2041
+ id,
2042
+ name,
2043
+ interests
2044
+ });
2045
+ }
2046
+ return {
2047
+ provider: "mailchimp",
2048
+ defaultAudienceId,
2049
+ audiences
2050
+ };
2051
+ }
2052
+ function parseMailchimpInterests(value) {
2053
+ const interests = [];
2054
+ for (const interest of value) {
2055
+ if (!isRecord$8(interest)) return null;
2056
+ const id = parseMailchimpInterestId(interest.id);
2057
+ const name = parseName(interest.name);
2058
+ const categoryName = parseName(interest.categoryName);
2059
+ if (id === null || name === null || categoryName === null) return null;
2060
+ interests.push({
2061
+ id,
2062
+ name,
2063
+ categoryName
2064
+ });
2065
+ }
2066
+ return interests;
2067
+ }
2068
+ function parseNamedItems(value, parseId) {
2069
+ if (!Array.isArray(value)) return null;
2070
+ const items = [];
2071
+ for (const item of value) {
2072
+ if (!isRecord$8(item)) return null;
2073
+ const id = parseId(item.id);
2074
+ const name = parseName(item.name);
2075
+ if (id === null || name === null) return null;
2076
+ items.push({
2077
+ id,
2078
+ name
2079
+ });
2080
+ }
2081
+ return items;
2082
+ }
2083
+ function parseName(value) {
2084
+ if (typeof value !== "string") return null;
2085
+ const name = value.trim();
2086
+ return name.length > 0 ? name : null;
2087
+ }
2088
+ function isRecord$8(value) {
2089
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2090
+ }
2091
+ function invalidRuntimeContext() {
2092
+ return err({ tag: "invalid_mailing_list_runtime_context" });
2093
+ }
2094
+ //#endregion
2095
+ //#region ../core/src/mailing-list-targeting-parser.ts
2096
+ function parseMailingListPlacementTargeting(value) {
2097
+ if (!isRecord$7(value)) return targetingError("shape");
2098
+ if (value.mode === "default") return ok({ mode: "default" });
2099
+ if (value.mode !== "custom") return targetingError("mode");
2100
+ if (!isRecord$7(value.selection)) return targetingError("selection");
2101
+ switch (value.provider) {
2102
+ case "native": return parseNativeTargeting(value.selection);
2103
+ case "brevo": return parseBrevoTargeting(value.selection);
2104
+ case "mailchimp": return parseMailchimpTargeting(value, value.selection);
2105
+ default: return targetingError("provider");
2106
+ }
2107
+ }
2108
+ function parseNativeTargeting(selection) {
2109
+ if (selection.tag === "fixed") {
2110
+ const topicIds = parseNonEmptyUniqueIds(selection.topicIds, parseNewsletterListId);
2111
+ return topicIds.ok ? ok({
2112
+ mode: "custom",
2113
+ provider: "native",
2114
+ selection: {
2115
+ tag: "fixed",
2116
+ topicIds: topicIds.value
2117
+ }
2118
+ }) : topicIds;
2119
+ }
2120
+ if (selection.tag === "visitor_choice") {
2121
+ const offeredTopicIds = parseNonEmptyUniqueIds(selection.offeredTopicIds, parseNewsletterListId);
2122
+ return offeredTopicIds.ok ? ok({
2123
+ mode: "custom",
2124
+ provider: "native",
2125
+ selection: {
2126
+ tag: "visitor_choice",
2127
+ offeredTopicIds: offeredTopicIds.value
2128
+ }
2129
+ }) : offeredTopicIds;
2130
+ }
2131
+ return targetingError("selection");
2132
+ }
2133
+ function parseBrevoTargeting(selection) {
2134
+ if (selection.tag === "fixed") {
2135
+ const listIds = parseNonEmptyUniqueIds(selection.listIds, parseBrevoListId);
2136
+ return listIds.ok ? ok({
2137
+ mode: "custom",
2138
+ provider: "brevo",
2139
+ selection: {
2140
+ tag: "fixed",
2141
+ listIds: listIds.value
2142
+ }
2143
+ }) : listIds;
2144
+ }
2145
+ if (selection.tag === "visitor_choice") {
2146
+ const offeredListIds = parseNonEmptyUniqueIds(selection.offeredListIds, parseBrevoListId);
2147
+ return offeredListIds.ok ? ok({
2148
+ mode: "custom",
2149
+ provider: "brevo",
2150
+ selection: {
2151
+ tag: "visitor_choice",
2152
+ offeredListIds: offeredListIds.value
2153
+ }
2154
+ }) : offeredListIds;
2155
+ }
2156
+ return targetingError("selection");
2157
+ }
2158
+ function parseMailchimpTargeting(value, selection) {
2159
+ const audienceId = parseMailchimpAudienceId(value.audienceId);
2160
+ if (audienceId === null) return targetingError("audienceId");
2161
+ if (selection.tag === "fixed") {
2162
+ const interestIds = parseUniqueIds(selection.interestIds, parseMailchimpInterestId);
2163
+ return interestIds.ok ? ok({
2164
+ mode: "custom",
2165
+ provider: "mailchimp",
2166
+ audienceId,
2167
+ selection: {
2168
+ tag: "fixed",
2169
+ interestIds: interestIds.value
2170
+ }
2171
+ }) : interestIds;
2172
+ }
2173
+ if (selection.tag === "visitor_choice") {
2174
+ const offeredInterestIds = parseNonEmptyUniqueIds(selection.offeredInterestIds, parseMailchimpInterestId);
2175
+ return offeredInterestIds.ok ? ok({
2176
+ mode: "custom",
2177
+ provider: "mailchimp",
2178
+ audienceId,
2179
+ selection: {
2180
+ tag: "visitor_choice",
2181
+ offeredInterestIds: offeredInterestIds.value
2182
+ }
2183
+ }) : offeredInterestIds;
2184
+ }
2185
+ return targetingError("selection");
2186
+ }
2187
+ function parseNonEmptyUniqueIds(value, parseId) {
2188
+ const parsed = parseUniqueIds(value, parseId);
2189
+ if (!parsed.ok) return parsed;
2190
+ const [first, ...rest] = parsed.value;
2191
+ return first === void 0 ? targetingError("targets") : ok([first, ...rest]);
2192
+ }
2193
+ function parseUniqueIds(value, parseId) {
2194
+ if (!Array.isArray(value)) return targetingError("targets");
2195
+ const parsed = [];
2196
+ for (const item of value) {
2197
+ const id = parseId(item);
2198
+ if (id === null) return targetingError("targets");
2199
+ if (parsed.includes(id)) return targetingError("duplicate_target");
2200
+ parsed.push(id);
2201
+ }
2202
+ return ok(parsed);
2203
+ }
2204
+ function isRecord$7(value) {
2205
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2206
+ }
2207
+ function targetingError(field) {
2208
+ return err({
2209
+ tag: "invalid_mailing_list_targeting",
2210
+ field
2211
+ });
2407
2212
  }
2408
2213
  //#endregion
2409
- //#region ../core/src/newsletter-signup-destination.ts
2410
- function parseNewsletterSignupDestinationProvider(value) {
2411
- return value === "riverbank" || value === "mailchimp" ? value : null;
2214
+ //#region ../core/src/mailing-list-submission.ts
2215
+ function parseMailingListBlockId(value) {
2216
+ const parsed = typeof value === "string" ? value.trim() : "";
2217
+ 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;
2218
+ }
2219
+ function parseMailingListSignedSourceToken(value) {
2220
+ const parsed = typeof value === "string" ? value.trim() : "";
2221
+ return /^[A-Za-z0-9._~-]{16,1000}$/u.test(parsed) ? parsed : null;
2222
+ }
2223
+ function buildMailingListSignupBlockSourceRef(blockId) {
2224
+ const parsed = parseMailingListBlockId(blockId);
2225
+ return parsed !== null ? {
2226
+ tag: "block_ref",
2227
+ blockId: parsed
2228
+ } : null;
2412
2229
  }
2413
2230
  //#endregion
2414
2231
  //#region ../core/src/secret.ts
@@ -2449,320 +2266,1111 @@ function createSecretInternal(kind, value) {
2449
2266
  return Object.freeze(secret);
2450
2267
  }
2451
2268
  /**
2452
- * Parse an untrusted management API key before constructing its Secret value.
2269
+ * Parse an untrusted management API key before constructing its Secret value.
2270
+ *
2271
+ * The Secret type protects an already validated value from accidental
2272
+ * disclosure; this boundary is what validates the value's wire format.
2273
+ */
2274
+ function parseManagementKey(raw) {
2275
+ if (typeof raw !== "string" || !raw.startsWith("bld_mgmt_sk_") || raw.length === 12) return err({ kind: "invalid-management-key" });
2276
+ return ok(createSecretInternal("management-key", raw));
2277
+ }
2278
+ function sessionStartMs(session) {
2279
+ return Date.parse(session.startsAt);
2280
+ }
2281
+ /**
2282
+ * The run's sessions that still count, in schedule order.
2283
+ *
2284
+ * Sessions whose start cannot be read are dropped rather than sorted to an
2285
+ * arbitrary end: an unparseable date is not a date a visitor can attend.
2286
+ */
2287
+ function orderedLiveRunSessions(sessions) {
2288
+ return sessions.filter((session) => session.status !== "cancelled" && Number.isFinite(sessionStartMs(session))).sort((left, right) => sessionStartMs(left) - sessionStartMs(right));
2289
+ }
2290
+ const PUBLIC_CONTENT_FRESHNESS_SECONDS = 3600;
2291
+ function availability(transport) {
2292
+ return {
2293
+ kind: "live",
2294
+ reason: "booking_correctness",
2295
+ freshness: "availability",
2296
+ transport
2297
+ };
2298
+ }
2299
+ function content(tags, transport = "direct") {
2300
+ return {
2301
+ kind: "tagged",
2302
+ tags,
2303
+ freshness: "content",
2304
+ transport
2305
+ };
2306
+ }
2307
+ /** The single classification authority for cacheable public reads. */
2308
+ const PUBLIC_READ_CACHE_POLICIES = {
2309
+ listPublishedEntries: content(["published_content"]),
2310
+ getPublishedEntryPreview: content(["published_content"]),
2311
+ listPublicEvents: availability("direct"),
2312
+ listPublicEventCalendarWindow: availability("direct"),
2313
+ listPublicRecurringSchedule: availability("batch_only"),
2314
+ getPublicEventDetails: availability("batch_only"),
2315
+ resolveEventSubroute: availability("direct"),
2316
+ listPublicRouteCandidates: content(["published_content"], "batch_only"),
2317
+ listPublicTeamMembers: content(["public_team_members"]),
2318
+ listPublicProducts: availability("direct"),
2319
+ listPublicPasses: availability("direct"),
2320
+ listPublicMemberships: availability("direct"),
2321
+ getPublicFormById: content(["site_forms"]),
2322
+ getPublicBookingServices: availability("direct"),
2323
+ getPublicBookingService: availability("direct"),
2324
+ getPublicBookingResourcesReference: availability("direct"),
2325
+ getPublicBookingAvailableDates: availability("direct"),
2326
+ getPublicBookingAvailableSlots: availability("direct"),
2327
+ getPublicWeeklySlotAvailability: availability("direct"),
2328
+ listPublicForms: content(["site_forms"]),
2329
+ checkRedirect: content([]),
2330
+ getPublicRoutableContent: content(["published_content"]),
2331
+ getPublicSitemapRoutes: content(["published_content"])
2332
+ };
2333
+ new Set(Object.keys(PUBLIC_READ_CACHE_POLICIES));
2334
+ function getPublicReadCachePolicy(endpoint) {
2335
+ return PUBLIC_READ_CACHE_POLICIES[endpoint];
2336
+ }
2337
+ function publicReadFreshnessSeconds(endpoint) {
2338
+ return getPublicReadCachePolicy(endpoint).freshness === "availability" ? 60 : PUBLIC_CONTENT_FRESHNESS_SECONDS;
2339
+ }
2340
+ //#endregion
2341
+ //#region ../core/src/event-series-behaviour.ts
2342
+ const WHOLE_SERIES_EVENT_PRESET_VALUES = ["course", "term"];
2343
+ const SURFACE_EVENT_PRESET_VALUES = [
2344
+ "class",
2345
+ "course",
2346
+ "term",
2347
+ "workshop",
2348
+ "retreat",
2349
+ "performance"
2350
+ ];
2351
+ //#endregion
2352
+ //#region ../core/src/booking-vertical-preset.ts
2353
+ const BOOKING_ENGINE_BY_BEHAVIOUR = {
2354
+ class: "event_series",
2355
+ workshop: "event_series",
2356
+ course: "event_series",
2357
+ retreat: "event_series",
2358
+ performance: "event_series",
2359
+ appointment: "appointment_service"
2360
+ };
2361
+ function verticalBookingPreset(id, verticalId, canonicalBehaviour, label, pluralLabel, description, availability = "onboarding") {
2362
+ return {
2363
+ id,
2364
+ verticalId,
2365
+ canonicalBehaviour,
2366
+ engine: BOOKING_ENGINE_BY_BEHAVIOUR[canonicalBehaviour],
2367
+ availability,
2368
+ label,
2369
+ pluralLabel,
2370
+ description
2371
+ };
2372
+ }
2373
+ //#endregion
2374
+ //#region ../core/src/booking-vertical-definitions.ts
2375
+ const VERTICAL_DEFINITIONS = [
2376
+ {
2377
+ id: "yoga_wellness",
2378
+ label: "Yoga, meditation or mindfulness",
2379
+ description: "Yoga studios, wellness teachers, and movement practitioners."
2380
+ },
2381
+ {
2382
+ id: "fitness_pt",
2383
+ label: "Fitness and personal training",
2384
+ description: "Personal trainers, group training, and fitness programmes."
2385
+ },
2386
+ {
2387
+ id: "dance_school",
2388
+ label: "Dance school",
2389
+ description: "Dance classes, courses or terms, workshops, private lessons, and shows."
2390
+ },
2391
+ {
2392
+ id: "physiotherapy_physical_health",
2393
+ label: "Physiotherapy & physical health",
2394
+ description: "Physiotherapists, chiropractors, osteopaths, sports therapists, and rehabilitation practitioners."
2395
+ },
2396
+ {
2397
+ id: "massage_bodywork",
2398
+ label: "Massage & bodywork",
2399
+ description: "Massage therapists, bodyworkers, and somatic practitioners."
2400
+ },
2401
+ {
2402
+ id: "therapist_practitioner",
2403
+ label: "Therapist or practitioner",
2404
+ description: "Appointments, sessions, consultations, and occasional group work."
2405
+ },
2406
+ {
2407
+ id: "beauty_spa",
2408
+ label: "Beauty or spa",
2409
+ description: "Treatment-led businesses with optional workshops."
2410
+ },
2411
+ {
2412
+ id: "consultant_coach",
2413
+ label: "Consultant or coach",
2414
+ description: "One-to-one calls, workshops, courses, and retreats."
2415
+ },
2416
+ {
2417
+ id: "training_provider",
2418
+ label: "Training provider",
2419
+ description: "Courses, workshops, classes, appointments, and residential training."
2420
+ },
2421
+ {
2422
+ id: "retreat_centre",
2423
+ label: "Retreat centre",
2424
+ description: "Residential retreats, workshops, classes, courses, and private sessions."
2425
+ },
2426
+ {
2427
+ id: "music_school",
2428
+ label: "Music teacher or school",
2429
+ description: "Lessons, classes, terms, workshops, and performances."
2430
+ },
2431
+ {
2432
+ id: "voice_singing",
2433
+ label: "Voice & Singing",
2434
+ description: "Singing lessons, choirs, voice circles, workshops, and performances."
2435
+ },
2436
+ {
2437
+ id: "performing_arts_school",
2438
+ label: "Theatre or performing arts school",
2439
+ description: "Classes, terms, workshops, performances, and lessons."
2440
+ },
2441
+ {
2442
+ id: "community_wellbeing",
2443
+ label: "Community or wellbeing centre",
2444
+ description: "Groups, workshops, courses, appointments, and retreats."
2445
+ },
2446
+ {
2447
+ id: "venue_event_space",
2448
+ label: "Venue or event space",
2449
+ description: "Room, resource, or access bookings. Depends on the future capacity-booking domain."
2450
+ }
2451
+ ];
2452
+ VERTICAL_DEFINITIONS.map(({ id }) => id);
2453
+ //#endregion
2454
+ //#region ../core/src/booking-vertical-catalog.ts
2455
+ const VERTICAL_BOOKING_PRESETS = [
2456
+ verticalBookingPreset("yoga_wellness.classes", "yoga_wellness", "class", "Class", "Classes", "Regular scheduled classes and group sessions."),
2457
+ verticalBookingPreset("yoga_wellness.workshops", "yoga_wellness", "workshop", "Workshop", "Workshops", "One-off workshops, day retreats, or special sessions."),
2458
+ verticalBookingPreset("yoga_wellness.courses", "yoga_wellness", "course", "Course", "Courses", "Fixed-schedule courses or multi-session groups."),
2459
+ verticalBookingPreset("yoga_wellness.retreats", "yoga_wellness", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats and immersive experiences."),
2460
+ verticalBookingPreset("yoga_wellness.appointments", "yoga_wellness", "appointment", "Appointment", "Appointments", "Private sessions, consultations, or one-to-one support."),
2461
+ verticalBookingPreset("fitness_pt.group_training", "fitness_pt", "class", "Group training", "Group training", "Regular scheduled classes and group sessions."),
2462
+ verticalBookingPreset("fitness_pt.personal_training", "fitness_pt", "appointment", "Personal training", "Personal training", "One-to-one sessions booked from your availability."),
2463
+ verticalBookingPreset("fitness_pt.programmes", "fitness_pt", "course", "Programme", "Programmes", "Multi-session programmes like bootcamps or courses."),
2464
+ verticalBookingPreset("fitness_pt.workshops", "fitness_pt", "workshop", "Workshop", "Workshops", "One-off workshops, events, or special sessions."),
2465
+ verticalBookingPreset("fitness_pt.retreats", "fitness_pt", "retreat", "Retreat", "Retreats", "Multi-day or residential fitness experiences."),
2466
+ verticalBookingPreset("dance_school.classes", "dance_school", "class", "Class", "Classes", "Regular group classes and weekly sessions."),
2467
+ {
2468
+ ...verticalBookingPreset("dance_school.terms", "dance_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date course groups."),
2469
+ eventPresetOverride: "term"
2470
+ },
2471
+ verticalBookingPreset("dance_school.courses", "dance_school", "course", "Course", "Courses", "Fixed-schedule courses or multi-session groups.", "settings"),
2472
+ verticalBookingPreset("dance_school.workshops", "dance_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2473
+ verticalBookingPreset("dance_school.private_lessons", "dance_school", "appointment", "Private lesson", "Private lessons", "One-to-one lessons or coaching sessions."),
2474
+ verticalBookingPreset("dance_school.shows", "dance_school", "performance", "Show", "Shows", "Ticketed performances, showcases, and recitals."),
2475
+ verticalBookingPreset("physiotherapy_physical_health.appointments", "physiotherapy_physical_health", "appointment", "Appointment", "Appointments", "Assessments, treatments, follow-ups, and rehabilitation sessions."),
2476
+ verticalBookingPreset("physiotherapy_physical_health.rehab_classes", "physiotherapy_physical_health", "class", "Rehab class", "Rehab classes", "Regular rehabilitation, mobility, strength, or recovery classes."),
2477
+ verticalBookingPreset("physiotherapy_physical_health.workshops", "physiotherapy_physical_health", "workshop", "Workshop", "Workshops", "One-off workshops, talks, or practical group sessions."),
2478
+ verticalBookingPreset("massage_bodywork.treatments", "massage_bodywork", "appointment", "Treatment", "Treatments", "Massage treatments, bodywork sessions, and one-to-one appointments."),
2479
+ verticalBookingPreset("massage_bodywork.group_sessions", "massage_bodywork", "class", "Group session", "Group sessions", "Regular bodywork, somatic movement, or self-massage sessions."),
2480
+ verticalBookingPreset("massage_bodywork.workshops", "massage_bodywork", "workshop", "Workshop", "Workshops", "One-off bodywork, self-massage, or somatic practice workshops."),
2481
+ verticalBookingPreset("therapist_practitioner.appointments", "therapist_practitioner", "appointment", "Appointment", "Appointments", "Sessions, consultations, treatments, or assessments."),
2482
+ verticalBookingPreset("therapist_practitioner.workshops", "therapist_practitioner", "workshop", "Workshop", "Workshops", "One-off group workshops, talks, or wellbeing sessions."),
2483
+ verticalBookingPreset("therapist_practitioner.retreats", "therapist_practitioner", "retreat", "Retreat", "Retreats", "Multi-day retreats or immersive group experiences."),
2484
+ verticalBookingPreset("beauty_spa.appointments", "beauty_spa", "appointment", "Appointment", "Appointments", "Treatments, consultations, services, and sessions."),
2485
+ verticalBookingPreset("beauty_spa.workshops", "beauty_spa", "workshop", "Workshop", "Workshops", "Skincare workshops, makeup lessons, demos, or group events."),
2486
+ verticalBookingPreset("consultant_coach.appointments", "consultant_coach", "appointment", "Appointment", "Appointments", "One-to-one calls, consultations, reviews, or coaching sessions."),
2487
+ verticalBookingPreset("consultant_coach.workshops", "consultant_coach", "workshop", "Workshop", "Workshops", "One-off group sessions, webinars, or strategy days."),
2488
+ verticalBookingPreset("consultant_coach.courses", "consultant_coach", "course", "Course", "Courses", "Fixed-schedule cohorts or multi-session group programmes."),
2489
+ verticalBookingPreset("consultant_coach.retreats", "consultant_coach", "retreat", "Retreat", "Retreats", "Multi-day coaching retreats or immersive experiences."),
2490
+ verticalBookingPreset("training_provider.courses", "training_provider", "course", "Course", "Courses", "Fixed-schedule courses, cohorts, and multi-session training."),
2491
+ verticalBookingPreset("training_provider.workshops", "training_provider", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, and short training sessions."),
2492
+ verticalBookingPreset("training_provider.appointments", "training_provider", "appointment", "Appointment", "Appointments", "Consultations, assessments, mentoring, or office hours."),
2493
+ verticalBookingPreset("training_provider.classes", "training_provider", "class", "Class", "Classes", "Regular classes, sessions, or groups.", "settings"),
2494
+ verticalBookingPreset("training_provider.residentials", "training_provider", "retreat", "Residential", "Residentials", "Residential courses or multi-day training experiences.", "settings"),
2495
+ verticalBookingPreset("retreat_centre.retreats", "retreat_centre", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats with richer booking needs."),
2496
+ verticalBookingPreset("retreat_centre.workshops", "retreat_centre", "workshop", "Workshop", "Workshops", "One-day retreats, ceremonies, workshops, or special events."),
2497
+ verticalBookingPreset("retreat_centre.classes", "retreat_centre", "class", "Class", "Classes", "Regular sessions like guided meditations or group practices."),
2498
+ verticalBookingPreset("retreat_centre.courses", "retreat_centre", "course", "Course", "Courses", "Fixed-schedule programmes or multi-session experiences."),
2499
+ verticalBookingPreset("retreat_centre.appointments", "retreat_centre", "appointment", "Appointment", "Appointments", "Private sessions, therapies, consultations, or add-ons."),
2500
+ verticalBookingPreset("music_school.lessons", "music_school", "appointment", "Lesson", "Lessons", "One-to-one lessons, consultations, or coaching sessions."),
2501
+ verticalBookingPreset("music_school.classes", "music_school", "class", "Class", "Classes", "Regular group classes, ensembles, or recurring sessions."),
2502
+ {
2503
+ ...verticalBookingPreset("music_school.terms", "music_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date group courses."),
2504
+ eventPresetOverride: "term"
2505
+ },
2506
+ verticalBookingPreset("music_school.workshops", "music_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2507
+ verticalBookingPreset("music_school.performances", "music_school", "performance", "Performance", "Performances", "Concerts, recitals, showcases, or ticketed performances."),
2508
+ verticalBookingPreset("voice_singing.lessons", "voice_singing", "appointment", "Voice session", "Voice sessions", "One-to-one singing lessons, voice coaching, or private practice sessions."),
2509
+ verticalBookingPreset("voice_singing.classes", "voice_singing", "class", "Ongoing weekly session", "Ongoing weekly sessions", "Regular choirs, voice circles, community singing, or group practice."),
2510
+ {
2511
+ ...verticalBookingPreset("voice_singing.terms", "voice_singing", "course", "Singing term", "Singing terms", "Multi-week choir terms, voice courses, or fixed-date group programmes."),
2512
+ eventPresetOverride: "term"
2513
+ },
2514
+ verticalBookingPreset("voice_singing.workshops", "voice_singing", "workshop", "Workshop", "Workshops", "One-off singing workshops, voice journeys, or devotional singing sessions."),
2515
+ verticalBookingPreset("voice_singing.performances", "voice_singing", "performance", "Performance", "Performances", "Choir concerts, sharing events, recitals, or community performances."),
2516
+ verticalBookingPreset("performing_arts_school.classes", "performing_arts_school", "class", "Class", "Classes", "Regular group classes, rehearsals, or weekly sessions."),
2517
+ {
2518
+ ...verticalBookingPreset("performing_arts_school.terms", "performing_arts_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date course groups."),
2519
+ eventPresetOverride: "term"
2520
+ },
2521
+ verticalBookingPreset("performing_arts_school.workshops", "performing_arts_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2522
+ verticalBookingPreset("performing_arts_school.performances", "performing_arts_school", "performance", "Performance", "Performances", "Shows, showcases, productions, or ticketed performances."),
2523
+ verticalBookingPreset("performing_arts_school.lessons", "performing_arts_school", "appointment", "Lesson", "Lessons", "One-to-one lessons, coaching, or audition prep."),
2524
+ verticalBookingPreset("community_wellbeing.classes", "community_wellbeing", "class", "Class", "Classes", "Regular groups, sessions, practices, or weekly activities."),
2525
+ verticalBookingPreset("community_wellbeing.workshops", "community_wellbeing", "workshop", "Workshop", "Workshops", "One-off workshops, talks, events, or gatherings."),
2526
+ verticalBookingPreset("community_wellbeing.courses", "community_wellbeing", "course", "Course", "Courses", "Fixed-schedule courses, study groups, or multi-session programmes."),
2527
+ verticalBookingPreset("community_wellbeing.appointments", "community_wellbeing", "appointment", "Appointment", "Appointments", "One-to-one appointments, consultations, or support sessions."),
2528
+ verticalBookingPreset("community_wellbeing.retreats", "community_wellbeing", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats."),
2529
+ verticalBookingPreset("venue_event_space.resource_hire", "venue_event_space", "appointment", "Hire slot", "Hire slots", "Future room, venue, and resource hire bookings.", "future")
2530
+ ];
2531
+ //#endregion
2532
+ //#region ../core/src/booking-vertical-config.ts
2533
+ const VERTICAL_ID_SET = new Set(VERTICAL_DEFINITIONS.map(({ id }) => id));
2534
+ const VERTICAL_PRESET_ID_SET = new Set(VERTICAL_BOOKING_PRESETS.map((presetDefinition) => presetDefinition.id));
2535
+ function parseVerticalId(value) {
2536
+ return typeof value === "string" && VERTICAL_ID_SET.has(value) ? value : null;
2537
+ }
2538
+ function parseVerticalPresetId(value) {
2539
+ return typeof value === "string" && VERTICAL_PRESET_ID_SET.has(value) ? value : null;
2540
+ }
2541
+ function getVerticalPresetDefinition(presetId) {
2542
+ const definition = VERTICAL_BOOKING_PRESETS.find((presetDefinition) => presetDefinition.id === presetId);
2543
+ if (definition === void 0) throw new Error(`Unhandled vertical preset id: ${presetId}`);
2544
+ return definition;
2545
+ }
2546
+ function createConfiguredSiteVerticalBookingConfig(verticalId, selectedPresetIds) {
2547
+ return {
2548
+ verticalId,
2549
+ selectedPresetIds
2550
+ };
2551
+ }
2552
+ function toNonEmptyVerticalPresetIds(presetIds) {
2553
+ const firstPresetId = presetIds[0];
2554
+ return firstPresetId ? [firstPresetId, ...presetIds.slice(1)] : null;
2555
+ }
2556
+ const DEFAULT_SITE_VERTICAL_BOOKING_CONFIG = {
2557
+ verticalId: null,
2558
+ selectedPresetIds: []
2559
+ };
2560
+ function appendUniqueIssue(issues, issue) {
2561
+ return issues.some((existing) => existing.code === issue.code) ? issues : [...issues, issue];
2562
+ }
2563
+ function collectRawConfigIssues(rawConfig, config) {
2564
+ const rawPresetValue = rawConfig.selectedPresetIds;
2565
+ const rawPresetIds = Array.isArray(rawPresetValue) ? rawPresetValue : [];
2566
+ let issues = [];
2567
+ if (rawConfig.verticalId != null && config.verticalId === null) issues = appendUniqueIssue(issues, {
2568
+ code: "unknown_vertical",
2569
+ rawValue: rawConfig.verticalId
2570
+ });
2571
+ if (config.verticalId === null && config.selectedPresetIds.length > 0) issues = appendUniqueIssue(issues, { code: "missing_vertical" });
2572
+ if (rawPresetValue !== void 0 && !Array.isArray(rawPresetValue) || rawPresetIds.some((presetId) => parseVerticalPresetId(presetId) === null)) issues = appendUniqueIssue(issues, {
2573
+ code: "unknown_preset",
2574
+ rawValue: rawPresetValue
2575
+ });
2576
+ if (rawConfig.verticalId != null && config.selectedPresetIds.length === 0) issues = appendUniqueIssue(issues, { code: "empty_selected_presets" });
2577
+ return issues;
2578
+ }
2579
+ function collectPresetConfigIssues(config) {
2580
+ const seenPresetIds = /* @__PURE__ */ new Set();
2581
+ const seenBehaviours = /* @__PURE__ */ new Set();
2582
+ let issues = [];
2583
+ for (const presetId of config.selectedPresetIds) {
2584
+ const presetDefinition = getVerticalPresetDefinition(presetId);
2585
+ if (seenPresetIds.has(presetId)) issues = appendUniqueIssue(issues, { code: "duplicate_preset_id" });
2586
+ seenPresetIds.add(presetId);
2587
+ if (config.verticalId !== null && presetDefinition.verticalId !== config.verticalId) issues = appendUniqueIssue(issues, { code: "preset_vertical_mismatch" });
2588
+ if (presetDefinition.availability === "future") issues = appendUniqueIssue(issues, { code: "future_preset_selected" });
2589
+ if (seenBehaviours.has(presetDefinition.canonicalBehaviour)) issues = appendUniqueIssue(issues, { code: "duplicate_canonical_behaviour" });
2590
+ seenBehaviours.add(presetDefinition.canonicalBehaviour);
2591
+ }
2592
+ return issues;
2593
+ }
2594
+ function getSiteVerticalConfigIssues(rawConfig, config) {
2595
+ let issues = collectRawConfigIssues(rawConfig, config);
2596
+ for (const issue of collectPresetConfigIssues(config)) issues = appendUniqueIssue(issues, issue);
2597
+ return issues;
2598
+ }
2599
+ function resolveSiteVerticalBookingConfig(rawConfig) {
2600
+ if (rawConfig == null) return {
2601
+ kind: "unconfigured",
2602
+ config: DEFAULT_SITE_VERTICAL_BOOKING_CONFIG
2603
+ };
2604
+ const verticalId = parseVerticalId(rawConfig.verticalId);
2605
+ const selectedPresetIds = (Array.isArray(rawConfig.selectedPresetIds) ? rawConfig.selectedPresetIds : []).map(parseVerticalPresetId).filter((presetId) => Boolean(presetId));
2606
+ const config = {
2607
+ verticalId,
2608
+ selectedPresetIds
2609
+ };
2610
+ if (rawConfig.verticalId == null && selectedPresetIds.length === 0) return {
2611
+ kind: "unconfigured",
2612
+ config: DEFAULT_SITE_VERTICAL_BOOKING_CONFIG
2613
+ };
2614
+ const issues = getSiteVerticalConfigIssues(rawConfig, config);
2615
+ if (issues.length > 0) return {
2616
+ kind: "invalid",
2617
+ config,
2618
+ issues
2619
+ };
2620
+ const nonEmptyPresetIds = toNonEmptyVerticalPresetIds(selectedPresetIds);
2621
+ const configuredConfig = verticalId !== null && nonEmptyPresetIds !== null ? createConfiguredSiteVerticalBookingConfig(verticalId, nonEmptyPresetIds) : null;
2622
+ if (configuredConfig === null) return {
2623
+ kind: "invalid",
2624
+ config,
2625
+ issues: [{ code: "empty_selected_presets" }]
2626
+ };
2627
+ return {
2628
+ kind: "configured",
2629
+ config: configuredConfig,
2630
+ selectedPresets: configuredConfig.selectedPresetIds.map(getVerticalPresetDefinition)
2631
+ };
2632
+ }
2633
+ //#endregion
2634
+ //#region ../api/src/publicEventEndpointDefinitions.ts
2635
+ const PUBLIC_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2636
+ listPublicEventCalendarWindow: {
2637
+ path: "/public/sites/{siteId}/events/calendar-window",
2638
+ method: "GET",
2639
+ revalidate: 0,
2640
+ auth: "public",
2641
+ responseKind: "json"
2642
+ },
2643
+ resolvePublicEventCheckoutReturn: {
2644
+ path: "/public/sites/{siteId}/events/checkout-return",
2645
+ method: "POST",
2646
+ tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
2647
+ auth: "public",
2648
+ responseKind: "json"
2649
+ },
2650
+ getPublicEventRegistrationOptions: {
2651
+ path: "/public/sites/{siteId}/events/registration-options",
2652
+ method: "POST",
2653
+ tags: [TAG_TEMPLATES.publicEvents, "event-registration-options"],
2654
+ auth: "public",
2655
+ responseKind: "json",
2656
+ errors: [
2657
+ "validation:invalid_input",
2658
+ "resource:not_found",
2659
+ "resource:conflict",
2660
+ "server:internal_error"
2661
+ ]
2662
+ },
2663
+ resolveEventSubroute: {
2664
+ path: "/public/sites/{siteId}/events/subroutes/resolve",
2665
+ method: "GET",
2666
+ revalidate: publicReadFreshnessSeconds("resolveEventSubroute"),
2667
+ tags: [TAG_TEMPLATES.publicEvents, "event-subroute"],
2668
+ auth: "public",
2669
+ responseKind: "json",
2670
+ errors: []
2671
+ }
2672
+ });
2673
+ //#endregion
2674
+ //#region ../api/src/publicEventCheckoutEndpointDefinitions.ts
2675
+ /**
2676
+ * The public event registration/checkout endpoints, extracted from
2677
+ * `endpoints.ts`.
2678
+ *
2679
+ * They were moved out for the same reason the `sdkMedia*` block was: adding the
2680
+ * `errors` metadata `require-api-endpoint-errors` asks of a newly-added endpoint
2681
+ * pushed `endpoints.ts` past its `max-lines` baseline. The block is spread back
2682
+ * at the exact position it occupied, so `API_ENDPOINTS` key order is unchanged.
2683
+ * (The endpoint that originally motivated the move, `checkPublicEventEligibility`,
2684
+ * was since deleted on dev by #1326.)
2685
+ */
2686
+ const PUBLIC_EVENT_CHECKOUT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2687
+ registerForEvent: {
2688
+ path: "/public/sites/{siteId}/events/register",
2689
+ method: "POST",
2690
+ tags: [TAG_TEMPLATES.publicEvents, "event-registration"],
2691
+ auth: "public",
2692
+ responseKind: "json"
2693
+ },
2694
+ createPublicEventCheckout: {
2695
+ path: "/public/sites/{siteId}/events/checkout",
2696
+ method: "POST",
2697
+ tags: [TAG_TEMPLATES.publicEvents, "event-checkout"],
2698
+ auth: "public",
2699
+ responseKind: "json"
2700
+ }
2701
+ });
2702
+ //#endregion
2703
+ //#region ../api/src/publicBookingAvailabilityEndpointDefinitions.ts
2704
+ /**
2705
+ * Public appointment reads: the service catalogue and the bookable slot grid.
2453
2706
  *
2454
- * The Secret type protects an already validated value from accidental
2455
- * disclosure; this boundary is what validates the value's wire format.
2456
- */
2457
- function parseManagementKey(raw) {
2458
- if (typeof raw !== "string" || !raw.startsWith("bld_mgmt_sk_") || raw.length === 12) return err({ kind: "invalid-management-key" });
2459
- return ok(createSecretInternal("management-key", raw));
2460
- }
2461
- function sessionStartMs(session) {
2462
- return Date.parse(session.startsAt);
2463
- }
2464
- /**
2465
- * The run's sessions that still count, in schedule order.
2707
+ * These are the reads that appointment writes invalidate. They carry canonical
2708
+ * `riverbank:*` tags because the frontend revalidate endpoint discards anything
2709
+ * else, which is why the legacy `site-{siteId}` tag they used to declare could
2710
+ * never actually be cleared (issue 1364).
2466
2711
  *
2467
- * Sessions whose start cannot be read are dropped rather than sorted to an
2468
- * arbitrary end: an unparseable date is not a date a visitor can attend.
2712
+ * Slot availability depends on both tags: the grid moves when a booking lands,
2713
+ * and again when the service definition behind it changes.
2469
2714
  */
2470
- function orderedLiveRunSessions(sessions) {
2471
- return sessions.filter((session) => session.status !== "cancelled" && Number.isFinite(sessionStartMs(session))).sort((left, right) => sessionStartMs(left) - sessionStartMs(right));
2472
- }
2715
+ const PUBLIC_BOOKING_AVAILABILITY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2716
+ getPublicBookingServices: {
2717
+ path: "/public/bookings/services",
2718
+ method: "GET",
2719
+ revalidate: publicReadFreshnessSeconds("getPublicBookingServices"),
2720
+ tags: [TAG_TEMPLATES.publicBookingServices],
2721
+ auth: "public",
2722
+ responseKind: "json"
2723
+ },
2724
+ getPublicBookingService: {
2725
+ path: "/public/bookings/services/{serviceId}",
2726
+ method: "GET",
2727
+ revalidate: publicReadFreshnessSeconds("getPublicBookingService"),
2728
+ tags: [TAG_TEMPLATES.publicBookingServices],
2729
+ auth: "public",
2730
+ responseKind: "json"
2731
+ },
2732
+ getPublicBookingResourcesReference: {
2733
+ path: "/public/bookings/resources/reference",
2734
+ method: "GET",
2735
+ revalidate: publicReadFreshnessSeconds("getPublicBookingResourcesReference"),
2736
+ tags: [TAG_TEMPLATES.publicBookingServices],
2737
+ auth: "public",
2738
+ responseKind: "json"
2739
+ },
2740
+ getPublicBookingAvailableDates: {
2741
+ path: "/public/bookings/availability/dates",
2742
+ method: "GET",
2743
+ revalidate: publicReadFreshnessSeconds("getPublicBookingAvailableDates"),
2744
+ tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
2745
+ auth: "public",
2746
+ responseKind: "json"
2747
+ },
2748
+ getPublicBookingAvailableSlots: {
2749
+ path: "/public/bookings/availability/slots",
2750
+ method: "GET",
2751
+ revalidate: publicReadFreshnessSeconds("getPublicBookingAvailableSlots"),
2752
+ tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
2753
+ auth: "public",
2754
+ responseKind: "json"
2755
+ },
2756
+ getPublicWeeklySlotAvailability: {
2757
+ path: "/public/bookings/weekly-slot-availability",
2758
+ method: "GET",
2759
+ revalidate: publicReadFreshnessSeconds("getPublicWeeklySlotAvailability"),
2760
+ tags: [TAG_TEMPLATES.publicBookingAvailability, TAG_TEMPLATES.publicBookingServices],
2761
+ auth: "public",
2762
+ responseKind: "json",
2763
+ errors: []
2764
+ }
2765
+ });
2473
2766
  //#endregion
2474
- //#region ../core/src/event-series-behaviour.ts
2475
- const WHOLE_SERIES_EVENT_PRESET_VALUES = ["course", "term"];
2476
- const SURFACE_EVENT_PRESET_VALUES = [
2477
- "class",
2478
- "course",
2479
- "term",
2480
- "workshop",
2481
- "retreat",
2482
- "performance"
2483
- ];
2767
+ //#region ../api/src/siteApiKeyEndpointDefinitions.ts
2768
+ const SITE_API_KEY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2769
+ listSiteApiKeys: {
2770
+ path: "/sites/{siteId}/api-keys",
2771
+ method: "GET",
2772
+ auth: "user",
2773
+ tags: ["site-{siteId}", "api-keys"],
2774
+ responseKind: "json"
2775
+ },
2776
+ createSiteApiKey: {
2777
+ path: "/sites/{siteId}/api-keys",
2778
+ method: "POST",
2779
+ auth: "user",
2780
+ tags: ["site-{siteId}", "api-keys"],
2781
+ responseKind: "json"
2782
+ },
2783
+ revokeSiteApiKey: {
2784
+ path: "/sites/{siteId}/api-keys/{keyId}",
2785
+ method: "DELETE",
2786
+ auth: "user",
2787
+ tags: ["site-{siteId}", "api-keys"],
2788
+ responseKind: "json"
2789
+ },
2790
+ listSitePreviewKeys: {
2791
+ path: "/sites/{siteId}/api-keys/preview",
2792
+ method: "GET",
2793
+ auth: "user",
2794
+ tags: [
2795
+ "site-{siteId}",
2796
+ "api-keys",
2797
+ "preview-key"
2798
+ ],
2799
+ errors: [],
2800
+ responseKind: "json"
2801
+ },
2802
+ createSitePreviewKey: {
2803
+ path: "/sites/{siteId}/api-keys/preview",
2804
+ method: "POST",
2805
+ auth: "user",
2806
+ tags: [
2807
+ "site-{siteId}",
2808
+ "api-keys",
2809
+ "preview-key"
2810
+ ],
2811
+ responseKind: "json"
2812
+ },
2813
+ getSiteApiKeyAccessLogs: {
2814
+ path: "/sites/{siteId}/api-keys/access-logs",
2815
+ method: "GET",
2816
+ auth: "user",
2817
+ tags: [
2818
+ "site-{siteId}",
2819
+ "api-keys",
2820
+ "access-logs"
2821
+ ],
2822
+ responseKind: "json"
2823
+ },
2824
+ listManagementKeys: {
2825
+ path: "/sites/{siteId}/api-keys/management",
2826
+ method: "GET",
2827
+ auth: "user",
2828
+ tags: [
2829
+ "site-{siteId}",
2830
+ "api-keys",
2831
+ "management-keys"
2832
+ ],
2833
+ responseKind: "json"
2834
+ },
2835
+ createManagementKey: {
2836
+ path: "/sites/{siteId}/api-keys/management",
2837
+ method: "POST",
2838
+ auth: "user",
2839
+ tags: [
2840
+ "site-{siteId}",
2841
+ "api-keys",
2842
+ "management-keys"
2843
+ ],
2844
+ responseKind: "json"
2845
+ },
2846
+ revokeManagementKey: {
2847
+ path: "/sites/{siteId}/api-keys/management",
2848
+ method: "DELETE",
2849
+ auth: "user",
2850
+ tags: [
2851
+ "site-{siteId}",
2852
+ "api-keys",
2853
+ "management-keys"
2854
+ ],
2855
+ responseKind: "json"
2856
+ }
2857
+ });
2484
2858
  //#endregion
2485
- //#region ../core/src/booking-vertical-preset.ts
2486
- const BOOKING_ENGINE_BY_BEHAVIOUR = {
2487
- class: "event_series",
2488
- workshop: "event_series",
2489
- course: "event_series",
2490
- retreat: "event_series",
2491
- performance: "event_series",
2492
- appointment: "appointment_service"
2493
- };
2494
- function verticalBookingPreset(id, verticalId, canonicalBehaviour, label, pluralLabel, description, availability = "onboarding") {
2495
- return {
2496
- id,
2497
- verticalId,
2498
- canonicalBehaviour,
2499
- engine: BOOKING_ENGINE_BY_BEHAVIOUR[canonicalBehaviour],
2500
- availability,
2501
- label,
2502
- pluralLabel,
2503
- description
2504
- };
2505
- }
2859
+ //#region ../api/src/eventManagementEndpointDefinitions.ts
2860
+ const EVENT_MANAGEMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2861
+ listEvents: {
2862
+ path: "/sites/{siteId}/bookings/events",
2863
+ method: "GET",
2864
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2865
+ auth: "user",
2866
+ responseKind: "json",
2867
+ errors: []
2868
+ },
2869
+ createEvent: {
2870
+ path: "/sites/{siteId}/bookings/events",
2871
+ method: "POST",
2872
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2873
+ auth: "user",
2874
+ responseKind: "json",
2875
+ errors: [
2876
+ "validation:invalid_input",
2877
+ "resource:already_exists",
2878
+ "server:internal_error"
2879
+ ]
2880
+ },
2881
+ transitionEventLifecycle: {
2882
+ path: "/sites/{siteId}/bookings/events/{eventId}/lifecycle",
2883
+ method: "POST",
2884
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2885
+ auth: "user",
2886
+ responseKind: "json",
2887
+ errors: [
2888
+ "validation:invalid_input",
2889
+ "resource:not_found",
2890
+ "resource:conflict",
2891
+ "server:internal_error"
2892
+ ]
2893
+ },
2894
+ updateEventIdentity: {
2895
+ path: "/sites/{siteId}/bookings/events/{eventId}",
2896
+ method: "PATCH",
2897
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2898
+ auth: "user",
2899
+ responseKind: "json",
2900
+ errors: [
2901
+ "validation:invalid_input",
2902
+ "resource:not_found",
2903
+ "server:internal_error"
2904
+ ]
2905
+ },
2906
+ createWholeSeriesRun: {
2907
+ path: "/sites/{siteId}/bookings/events/{eventId}/runs",
2908
+ method: "POST",
2909
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2910
+ auth: "user",
2911
+ responseKind: "json",
2912
+ errors: [
2913
+ "validation:invalid_input",
2914
+ "resource:not_found",
2915
+ "resource:already_exists",
2916
+ "server:internal_error"
2917
+ ]
2918
+ },
2919
+ updateWholeSeriesRun: {
2920
+ path: "/sites/{siteId}/bookings/events/{eventId}/runs/{runId}",
2921
+ method: "PATCH",
2922
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2923
+ auth: "user",
2924
+ responseKind: "json",
2925
+ errors: [
2926
+ "validation:invalid_input",
2927
+ "resource:not_found",
2928
+ "resource:conflict",
2929
+ "server:internal_error"
2930
+ ]
2931
+ },
2932
+ cancelWholeSeriesRun: {
2933
+ path: "/sites/{siteId}/bookings/events/{eventId}/runs/{runId}/cancellation",
2934
+ method: "POST",
2935
+ tags: ["site-{siteId}", "event-series-{siteId}"],
2936
+ auth: "user",
2937
+ responseKind: "json",
2938
+ errors: [
2939
+ "validation:invalid_input",
2940
+ "resource:not_found",
2941
+ "resource:conflict",
2942
+ "server:internal_error"
2943
+ ]
2944
+ }
2945
+ });
2506
2946
  //#endregion
2507
- //#region ../core/src/booking-vertical-definitions.ts
2508
- const VERTICAL_DEFINITIONS = [
2509
- {
2510
- id: "yoga_wellness",
2511
- label: "Yoga, meditation or mindfulness",
2512
- description: "Yoga studios, wellness teachers, and movement practitioners."
2947
+ //#region ../api/src/giftingEndpointDefinitions.ts
2948
+ /**
2949
+ * Gifting endpoints (gift cards & vouchers) — the dashboard surface that manages
2950
+ * offers, gifts and settings, plus the public buy/claim surface.
2951
+ */
2952
+ const GIFTING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
2953
+ createGiftingOffer: {
2954
+ path: "/sites/{siteId}/gifting/offers",
2955
+ method: "POST",
2956
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2957
+ auth: "user",
2958
+ responseKind: "json",
2959
+ errors: [
2960
+ "validation:invalid_input",
2961
+ "auth:forbidden",
2962
+ "server:internal_error"
2963
+ ]
2513
2964
  },
2514
- {
2515
- id: "fitness_pt",
2516
- label: "Fitness and personal training",
2517
- description: "Personal trainers, group training, and fitness programmes."
2965
+ updateGiftingOffer: {
2966
+ path: "/sites/{siteId}/gifting/offers/{offerId}",
2967
+ method: "PATCH",
2968
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2969
+ auth: "user",
2970
+ responseKind: "json",
2971
+ errors: [
2972
+ "validation:invalid_input",
2973
+ "auth:forbidden",
2974
+ "resource:not_found",
2975
+ "resource:conflict",
2976
+ "server:internal_error"
2977
+ ]
2518
2978
  },
2519
- {
2520
- id: "dance_school",
2521
- label: "Dance school",
2522
- description: "Dance classes, courses or terms, workshops, private lessons, and shows."
2979
+ deleteGiftingOffer: {
2980
+ path: "/sites/{siteId}/gifting/offers/{offerId}",
2981
+ method: "DELETE",
2982
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2983
+ auth: "user",
2984
+ responseKind: "json",
2985
+ errors: [
2986
+ "auth:forbidden",
2987
+ "resource:not_found",
2988
+ "resource:conflict",
2989
+ "server:internal_error"
2990
+ ]
2523
2991
  },
2524
- {
2525
- id: "physiotherapy_physical_health",
2526
- label: "Physiotherapy & physical health",
2527
- description: "Physiotherapists, chiropractors, osteopaths, sports therapists, and rehabilitation practitioners."
2992
+ duplicateGiftingOffer: {
2993
+ path: "/sites/{siteId}/gifting/offers/{offerId}/duplicate",
2994
+ method: "POST",
2995
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
2996
+ auth: "user",
2997
+ responseKind: "json",
2998
+ errors: [
2999
+ "auth:forbidden",
3000
+ "resource:not_found",
3001
+ "server:internal_error"
3002
+ ]
2528
3003
  },
2529
- {
2530
- id: "massage_bodywork",
2531
- label: "Massage & bodywork",
2532
- description: "Massage therapists, bodyworkers, and somatic practitioners."
3004
+ reorderGiftingOffers: {
3005
+ path: "/sites/{siteId}/gifting/offers/order",
3006
+ method: "PUT",
3007
+ tags: ["site-{siteId}", "gifting-offers-{siteId}"],
3008
+ auth: "user",
3009
+ responseKind: "json",
3010
+ errors: [
3011
+ "validation:invalid_input",
3012
+ "auth:forbidden",
3013
+ "resource:not_found",
3014
+ "server:internal_error"
3015
+ ]
2533
3016
  },
2534
- {
2535
- id: "therapist_practitioner",
2536
- label: "Therapist or practitioner",
2537
- description: "Appointments, sessions, consultations, and occasional group work."
3017
+ sendGift: {
3018
+ path: "/sites/{siteId}/gifting/gifts",
3019
+ method: "POST",
3020
+ tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
3021
+ auth: "user",
3022
+ demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
3023
+ responseKind: "json",
3024
+ errors: [
3025
+ "validation:invalid_input",
3026
+ "auth:forbidden",
3027
+ "resource:not_found",
3028
+ "server:internal_error"
3029
+ ]
2538
3030
  },
2539
- {
2540
- id: "beauty_spa",
2541
- label: "Beauty or spa",
2542
- description: "Treatment-led businesses with optional workshops."
3031
+ resendGift: {
3032
+ path: "/sites/{siteId}/gifting/gifts/{giftId}/resend",
3033
+ method: "POST",
3034
+ tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
3035
+ auth: "user",
3036
+ demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
3037
+ responseKind: "json",
3038
+ errors: [
3039
+ "auth:forbidden",
3040
+ "resource:not_found",
3041
+ "resource:conflict",
3042
+ "server:internal_error"
3043
+ ]
2543
3044
  },
2544
- {
2545
- id: "consultant_coach",
2546
- label: "Consultant or coach",
2547
- description: "One-to-one calls, workshops, courses, and retreats."
3045
+ correctGiftEmailAndResend: {
3046
+ path: "/sites/{siteId}/gifting/gifts/{giftId}/correct-email-and-resend",
3047
+ method: "POST",
3048
+ tags: ["site-{siteId}", "gifting-gifts-{siteId}"],
3049
+ auth: "user",
3050
+ demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN,
3051
+ responseKind: "json",
3052
+ errors: [
3053
+ "validation:invalid_input",
3054
+ "auth:forbidden",
3055
+ "resource:not_found",
3056
+ "resource:conflict",
3057
+ "server:internal_error"
3058
+ ]
2548
3059
  },
2549
- {
2550
- id: "training_provider",
2551
- label: "Training provider",
2552
- description: "Courses, workshops, classes, appointments, and residential training."
3060
+ updateGiftingSettings: {
3061
+ path: "/sites/{siteId}/gifting/settings",
3062
+ method: "PATCH",
3063
+ tags: [
3064
+ "site-{siteId}",
3065
+ "gifting-offers-{siteId}",
3066
+ "gifting-gifts-{siteId}"
3067
+ ],
3068
+ auth: "user",
3069
+ responseKind: "json",
3070
+ errors: [
3071
+ "validation:invalid_input",
3072
+ "auth:forbidden",
3073
+ "server:internal_error"
3074
+ ]
3075
+ },
3076
+ listPublicGiftingOffers: {
3077
+ path: "/public/sites/{siteId}/gifting/offers",
3078
+ method: "GET",
3079
+ revalidate: 300,
3080
+ tags: [TAG_TEMPLATES.publicGifting],
3081
+ errors: [],
3082
+ auth: "public",
3083
+ responseKind: "json"
3084
+ },
3085
+ createPublicGiftingCheckout: {
3086
+ path: "/public/sites/{siteId}/gifting/checkout",
3087
+ method: "POST",
3088
+ tags: [TAG_TEMPLATES.publicGifting],
3089
+ errors: [],
3090
+ auth: "public",
3091
+ responseKind: "json"
3092
+ },
3093
+ claimGiftCardPublic: {
3094
+ path: "/public/sites/{siteId}/gifting/claim",
3095
+ method: "POST",
3096
+ tags: [TAG_TEMPLATES.publicGifting],
3097
+ errors: [],
3098
+ auth: "public",
3099
+ responseKind: "json"
3100
+ }
3101
+ });
3102
+ //#endregion
3103
+ //#region ../api/src/sdkEndpointDefinitions.ts
3104
+ /** SDK management endpoints that do not belong to a CRUD resource family. */
3105
+ const SDK_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3106
+ sdkUpdateSettings: {
3107
+ path: "/sdk/{siteId}/settings",
3108
+ method: "PATCH",
3109
+ auth: "service",
3110
+ responseKind: "json",
3111
+ errors: ["validation:invalid_input", "server:internal_error"]
3112
+ },
3113
+ sdkConfigureBookingVertical: {
3114
+ path: "/sdk/{siteId}/settings/booking/vertical",
3115
+ method: "POST",
3116
+ auth: "service",
3117
+ responseKind: "json"
3118
+ },
3119
+ sdkSetupRevalidationWebhook: {
3120
+ path: "/sdk/{siteId}/webhooks/revalidation/setup",
3121
+ method: "POST",
3122
+ auth: "service",
3123
+ responseKind: "json",
3124
+ errors: ["validation:invalid_input", "server:internal_error"]
3125
+ }
3126
+ });
3127
+ //#endregion
3128
+ //#region ../api/src/sdkMediaEndpointDefinitions.ts
3129
+ /** SDK media lookup, listing, deletion and upload endpoints (management-key authenticated). */
3130
+ const SDK_MEDIA_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3131
+ sdkMediaExists: {
3132
+ path: "/sdk/{siteId}/media/exists",
3133
+ method: "POST",
3134
+ errors: ["validation:invalid_input"],
3135
+ auth: "service",
3136
+ responseKind: "json"
3137
+ },
3138
+ sdkMediaFindByChecksum: {
3139
+ path: "/sdk/{siteId}/media/find-by-checksum",
3140
+ method: "POST",
3141
+ errors: ["server:internal_error"],
3142
+ auth: "service",
3143
+ responseKind: "json"
2553
3144
  },
2554
- {
2555
- id: "retreat_centre",
2556
- label: "Retreat centre",
2557
- description: "Residential retreats, workshops, classes, courses, and private sessions."
3145
+ sdkMediaResolve: {
3146
+ path: "/sdk/{siteId}/media/resolve",
3147
+ method: "POST",
3148
+ errors: ["server:internal_error"],
3149
+ auth: "service",
3150
+ responseKind: "json"
2558
3151
  },
2559
- {
2560
- id: "music_school",
2561
- label: "Music teacher or school",
2562
- description: "Lessons, classes, terms, workshops, and performances."
3152
+ sdkMediaList: {
3153
+ path: "/sdk/{siteId}/media",
3154
+ method: "GET",
3155
+ auth: "service",
3156
+ errors: [],
3157
+ responseKind: "json"
2563
3158
  },
2564
- {
2565
- id: "voice_singing",
2566
- label: "Voice & Singing",
2567
- description: "Singing lessons, choirs, voice circles, workshops, and performances."
3159
+ sdkMediaDownload: {
3160
+ path: "/sdk/{siteId}/media/{identifier}/download",
3161
+ method: "GET",
3162
+ auth: "service",
3163
+ errors: [],
3164
+ responseKind: "json"
2568
3165
  },
2569
- {
2570
- id: "performing_arts_school",
2571
- label: "Theatre or performing arts school",
2572
- description: "Classes, terms, workshops, performances, and lessons."
3166
+ sdkMediaDeletePreview: {
3167
+ path: "/sdk/{siteId}/media/{identifier}/delete-preview",
3168
+ method: "POST",
3169
+ auth: "service",
3170
+ errors: [],
3171
+ responseKind: "json"
2573
3172
  },
2574
- {
2575
- id: "community_wellbeing",
2576
- label: "Community or wellbeing centre",
2577
- description: "Groups, workshops, courses, appointments, and retreats."
3173
+ sdkMediaDeleteApply: {
3174
+ path: "/sdk/{siteId}/media/{identifier}",
3175
+ method: "DELETE",
3176
+ auth: "service",
3177
+ errors: [],
3178
+ responseKind: "json"
2578
3179
  },
2579
- {
2580
- id: "venue_event_space",
2581
- label: "Venue or event space",
2582
- description: "Room, resource, or access bookings. Depends on the future capacity-booking domain."
3180
+ sdkMediaUploadUrl: {
3181
+ path: "/sdk/{siteId}/media/upload-url",
3182
+ method: "POST",
3183
+ errors: [
3184
+ "resource:not_found",
3185
+ "validation:invalid_input",
3186
+ "resource:conflict",
3187
+ "validation:payload_too_large",
3188
+ "billing:plan_limit_exceeded",
3189
+ "server:internal_error"
3190
+ ],
3191
+ auth: "service",
3192
+ responseKind: "json"
3193
+ },
3194
+ sdkMediaUploadFinalize: {
3195
+ path: "/sdk/{siteId}/media/upload",
3196
+ method: "POST",
3197
+ errors: [
3198
+ "resource:not_found",
3199
+ "validation:invalid_input",
3200
+ "resource:conflict",
3201
+ "validation:payload_too_large",
3202
+ "billing:plan_limit_exceeded",
3203
+ "server:internal_error"
3204
+ ],
3205
+ auth: "service",
3206
+ responseKind: "json"
2583
3207
  }
2584
- ];
2585
- VERTICAL_DEFINITIONS.map(({ id }) => id);
3208
+ });
2586
3209
  //#endregion
2587
- //#region ../core/src/booking-vertical-catalog.ts
2588
- const VERTICAL_BOOKING_PRESETS = [
2589
- verticalBookingPreset("yoga_wellness.classes", "yoga_wellness", "class", "Class", "Classes", "Regular scheduled classes and group sessions."),
2590
- verticalBookingPreset("yoga_wellness.workshops", "yoga_wellness", "workshop", "Workshop", "Workshops", "One-off workshops, day retreats, or special sessions."),
2591
- verticalBookingPreset("yoga_wellness.courses", "yoga_wellness", "course", "Course", "Courses", "Fixed-schedule courses or multi-session groups."),
2592
- verticalBookingPreset("yoga_wellness.retreats", "yoga_wellness", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats and immersive experiences."),
2593
- verticalBookingPreset("yoga_wellness.appointments", "yoga_wellness", "appointment", "Appointment", "Appointments", "Private sessions, consultations, or one-to-one support."),
2594
- verticalBookingPreset("fitness_pt.group_training", "fitness_pt", "class", "Group training", "Group training", "Regular scheduled classes and group sessions."),
2595
- verticalBookingPreset("fitness_pt.personal_training", "fitness_pt", "appointment", "Personal training", "Personal training", "One-to-one sessions booked from your availability."),
2596
- verticalBookingPreset("fitness_pt.programmes", "fitness_pt", "course", "Programme", "Programmes", "Multi-session programmes like bootcamps or courses."),
2597
- verticalBookingPreset("fitness_pt.workshops", "fitness_pt", "workshop", "Workshop", "Workshops", "One-off workshops, events, or special sessions."),
2598
- verticalBookingPreset("fitness_pt.retreats", "fitness_pt", "retreat", "Retreat", "Retreats", "Multi-day or residential fitness experiences."),
2599
- verticalBookingPreset("dance_school.classes", "dance_school", "class", "Class", "Classes", "Regular group classes and weekly sessions."),
2600
- {
2601
- ...verticalBookingPreset("dance_school.terms", "dance_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date course groups."),
2602
- eventPresetOverride: "term"
3210
+ //#region ../api/src/publicReadEndpointDefinitions.ts
3211
+ /**
3212
+ * Direct public reads governed by the canonical public-read freshness policy.
3213
+ * Keeping these definitions together makes the endpoint identity used to
3214
+ * derive freshness reviewable beside its route and tags.
3215
+ */
3216
+ const PUBLIC_READ_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
3217
+ getPublishedEntryPreview: {
3218
+ path: "/public/content/{siteId}/{type}/{slug}/preview",
3219
+ method: "GET",
3220
+ revalidate: publicReadFreshnessSeconds("getPublishedEntryPreview"),
3221
+ tags: ["content-{siteId}-{type}-{slug}"],
3222
+ auth: "public",
3223
+ responseKind: "json"
2603
3224
  },
2604
- verticalBookingPreset("dance_school.courses", "dance_school", "course", "Course", "Courses", "Fixed-schedule courses or multi-session groups.", "settings"),
2605
- verticalBookingPreset("dance_school.workshops", "dance_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2606
- verticalBookingPreset("dance_school.private_lessons", "dance_school", "appointment", "Private lesson", "Private lessons", "One-to-one lessons or coaching sessions."),
2607
- verticalBookingPreset("dance_school.shows", "dance_school", "performance", "Show", "Shows", "Ticketed performances, showcases, and recitals."),
2608
- verticalBookingPreset("physiotherapy_physical_health.appointments", "physiotherapy_physical_health", "appointment", "Appointment", "Appointments", "Assessments, treatments, follow-ups, and rehabilitation sessions."),
2609
- verticalBookingPreset("physiotherapy_physical_health.rehab_classes", "physiotherapy_physical_health", "class", "Rehab class", "Rehab classes", "Regular rehabilitation, mobility, strength, or recovery classes."),
2610
- verticalBookingPreset("physiotherapy_physical_health.workshops", "physiotherapy_physical_health", "workshop", "Workshop", "Workshops", "One-off workshops, talks, or practical group sessions."),
2611
- verticalBookingPreset("massage_bodywork.treatments", "massage_bodywork", "appointment", "Treatment", "Treatments", "Massage treatments, bodywork sessions, and one-to-one appointments."),
2612
- verticalBookingPreset("massage_bodywork.group_sessions", "massage_bodywork", "class", "Group session", "Group sessions", "Regular bodywork, somatic movement, or self-massage sessions."),
2613
- verticalBookingPreset("massage_bodywork.workshops", "massage_bodywork", "workshop", "Workshop", "Workshops", "One-off bodywork, self-massage, or somatic practice workshops."),
2614
- verticalBookingPreset("therapist_practitioner.appointments", "therapist_practitioner", "appointment", "Appointment", "Appointments", "Sessions, consultations, treatments, or assessments."),
2615
- verticalBookingPreset("therapist_practitioner.workshops", "therapist_practitioner", "workshop", "Workshop", "Workshops", "One-off group workshops, talks, or wellbeing sessions."),
2616
- verticalBookingPreset("therapist_practitioner.retreats", "therapist_practitioner", "retreat", "Retreat", "Retreats", "Multi-day retreats or immersive group experiences."),
2617
- verticalBookingPreset("beauty_spa.appointments", "beauty_spa", "appointment", "Appointment", "Appointments", "Treatments, consultations, services, and sessions."),
2618
- verticalBookingPreset("beauty_spa.workshops", "beauty_spa", "workshop", "Workshop", "Workshops", "Skincare workshops, makeup lessons, demos, or group events."),
2619
- verticalBookingPreset("consultant_coach.appointments", "consultant_coach", "appointment", "Appointment", "Appointments", "One-to-one calls, consultations, reviews, or coaching sessions."),
2620
- verticalBookingPreset("consultant_coach.workshops", "consultant_coach", "workshop", "Workshop", "Workshops", "One-off group sessions, webinars, or strategy days."),
2621
- verticalBookingPreset("consultant_coach.courses", "consultant_coach", "course", "Course", "Courses", "Fixed-schedule cohorts or multi-session group programmes."),
2622
- verticalBookingPreset("consultant_coach.retreats", "consultant_coach", "retreat", "Retreat", "Retreats", "Multi-day coaching retreats or immersive experiences."),
2623
- verticalBookingPreset("training_provider.courses", "training_provider", "course", "Course", "Courses", "Fixed-schedule courses, cohorts, and multi-session training."),
2624
- verticalBookingPreset("training_provider.workshops", "training_provider", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, and short training sessions."),
2625
- verticalBookingPreset("training_provider.appointments", "training_provider", "appointment", "Appointment", "Appointments", "Consultations, assessments, mentoring, or office hours."),
2626
- verticalBookingPreset("training_provider.classes", "training_provider", "class", "Class", "Classes", "Regular classes, sessions, or groups.", "settings"),
2627
- verticalBookingPreset("training_provider.residentials", "training_provider", "retreat", "Residential", "Residentials", "Residential courses or multi-day training experiences.", "settings"),
2628
- verticalBookingPreset("retreat_centre.retreats", "retreat_centre", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats with richer booking needs."),
2629
- verticalBookingPreset("retreat_centre.workshops", "retreat_centre", "workshop", "Workshop", "Workshops", "One-day retreats, ceremonies, workshops, or special events."),
2630
- verticalBookingPreset("retreat_centre.classes", "retreat_centre", "class", "Class", "Classes", "Regular sessions like guided meditations or group practices."),
2631
- verticalBookingPreset("retreat_centre.courses", "retreat_centre", "course", "Course", "Courses", "Fixed-schedule programmes or multi-session experiences."),
2632
- verticalBookingPreset("retreat_centre.appointments", "retreat_centre", "appointment", "Appointment", "Appointments", "Private sessions, therapies, consultations, or add-ons."),
2633
- verticalBookingPreset("music_school.lessons", "music_school", "appointment", "Lesson", "Lessons", "One-to-one lessons, consultations, or coaching sessions."),
2634
- verticalBookingPreset("music_school.classes", "music_school", "class", "Class", "Classes", "Regular group classes, ensembles, or recurring sessions."),
2635
- {
2636
- ...verticalBookingPreset("music_school.terms", "music_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date group courses."),
2637
- eventPresetOverride: "term"
3225
+ listPublishedEntries: {
3226
+ path: "/public/content/{siteId}/{type}/entries",
3227
+ method: "GET",
3228
+ revalidate: publicReadFreshnessSeconds("listPublishedEntries"),
3229
+ tags: ["content-{siteId}-{type}"],
3230
+ auth: "public",
3231
+ responseKind: "json"
2638
3232
  },
2639
- verticalBookingPreset("music_school.workshops", "music_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2640
- verticalBookingPreset("music_school.performances", "music_school", "performance", "Performance", "Performances", "Concerts, recitals, showcases, or ticketed performances."),
2641
- verticalBookingPreset("voice_singing.lessons", "voice_singing", "appointment", "Voice session", "Voice sessions", "One-to-one singing lessons, voice coaching, or private practice sessions."),
2642
- verticalBookingPreset("voice_singing.classes", "voice_singing", "class", "Ongoing weekly session", "Ongoing weekly sessions", "Regular choirs, voice circles, community singing, or group practice."),
2643
- {
2644
- ...verticalBookingPreset("voice_singing.terms", "voice_singing", "course", "Singing term", "Singing terms", "Multi-week choir terms, voice courses, or fixed-date group programmes."),
2645
- eventPresetOverride: "term"
3233
+ getPublicFormById: {
3234
+ path: "/public/forms/{formId}",
3235
+ method: "GET",
3236
+ revalidate: publicReadFreshnessSeconds("getPublicFormById"),
3237
+ tags: [TAG_TEMPLATES.formById],
3238
+ auth: "public",
3239
+ responseKind: "json"
2646
3240
  },
2647
- verticalBookingPreset("voice_singing.workshops", "voice_singing", "workshop", "Workshop", "Workshops", "One-off singing workshops, voice journeys, or devotional singing sessions."),
2648
- verticalBookingPreset("voice_singing.performances", "voice_singing", "performance", "Performance", "Performances", "Choir concerts, sharing events, recitals, or community performances."),
2649
- verticalBookingPreset("performing_arts_school.classes", "performing_arts_school", "class", "Class", "Classes", "Regular group classes, rehearsals, or weekly sessions."),
2650
- {
2651
- ...verticalBookingPreset("performing_arts_school.terms", "performing_arts_school", "course", "Term", "Terms", "Multi-week term blocks or fixed-date course groups."),
2652
- eventPresetOverride: "term"
3241
+ listPublicEvents: {
3242
+ path: "/public/sites/{siteId}/events",
3243
+ method: "GET",
3244
+ revalidate: publicReadFreshnessSeconds("listPublicEvents"),
3245
+ tags: [TAG_TEMPLATES.publicEvents],
3246
+ auth: "public",
3247
+ responseKind: "json"
2653
3248
  },
2654
- verticalBookingPreset("performing_arts_school.workshops", "performing_arts_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
2655
- verticalBookingPreset("performing_arts_school.performances", "performing_arts_school", "performance", "Performance", "Performances", "Shows, showcases, productions, or ticketed performances."),
2656
- verticalBookingPreset("performing_arts_school.lessons", "performing_arts_school", "appointment", "Lesson", "Lessons", "One-to-one lessons, coaching, or audition prep."),
2657
- verticalBookingPreset("community_wellbeing.classes", "community_wellbeing", "class", "Class", "Classes", "Regular groups, sessions, practices, or weekly activities."),
2658
- verticalBookingPreset("community_wellbeing.workshops", "community_wellbeing", "workshop", "Workshop", "Workshops", "One-off workshops, talks, events, or gatherings."),
2659
- verticalBookingPreset("community_wellbeing.courses", "community_wellbeing", "course", "Course", "Courses", "Fixed-schedule courses, study groups, or multi-session programmes."),
2660
- verticalBookingPreset("community_wellbeing.appointments", "community_wellbeing", "appointment", "Appointment", "Appointments", "One-to-one appointments, consultations, or support sessions."),
2661
- verticalBookingPreset("community_wellbeing.retreats", "community_wellbeing", "retreat", "Retreat", "Retreats", "Multi-day or residential retreats."),
2662
- verticalBookingPreset("venue_event_space.resource_hire", "venue_event_space", "appointment", "Hire slot", "Hire slots", "Future room, venue, and resource hire bookings.", "future")
2663
- ];
2664
- //#endregion
2665
- //#region ../core/src/booking-vertical-config.ts
2666
- const VERTICAL_ID_SET = new Set(VERTICAL_DEFINITIONS.map(({ id }) => id));
2667
- const VERTICAL_PRESET_ID_SET = new Set(VERTICAL_BOOKING_PRESETS.map((presetDefinition) => presetDefinition.id));
2668
- function parseVerticalId(value) {
2669
- return typeof value === "string" && VERTICAL_ID_SET.has(value) ? value : null;
2670
- }
2671
- function parseVerticalPresetId(value) {
2672
- return typeof value === "string" && VERTICAL_PRESET_ID_SET.has(value) ? value : null;
2673
- }
2674
- function getVerticalPresetDefinition(presetId) {
2675
- const definition = VERTICAL_BOOKING_PRESETS.find((presetDefinition) => presetDefinition.id === presetId);
2676
- if (definition === void 0) throw new Error(`Unhandled vertical preset id: ${presetId}`);
2677
- return definition;
2678
- }
2679
- function createConfiguredSiteVerticalBookingConfig(verticalId, selectedPresetIds) {
2680
- return {
2681
- verticalId,
2682
- selectedPresetIds
2683
- };
2684
- }
2685
- function toNonEmptyVerticalPresetIds(presetIds) {
2686
- const firstPresetId = presetIds[0];
2687
- return firstPresetId ? [firstPresetId, ...presetIds.slice(1)] : null;
2688
- }
2689
- const DEFAULT_SITE_VERTICAL_BOOKING_CONFIG = {
2690
- verticalId: null,
2691
- selectedPresetIds: []
2692
- };
2693
- function appendUniqueIssue(issues, issue) {
2694
- return issues.some((existing) => existing.code === issue.code) ? issues : [...issues, issue];
2695
- }
2696
- function collectRawConfigIssues(rawConfig, config) {
2697
- const rawPresetValue = rawConfig.selectedPresetIds;
2698
- const rawPresetIds = Array.isArray(rawPresetValue) ? rawPresetValue : [];
2699
- let issues = [];
2700
- if (rawConfig.verticalId != null && config.verticalId === null) issues = appendUniqueIssue(issues, {
2701
- code: "unknown_vertical",
2702
- rawValue: rawConfig.verticalId
2703
- });
2704
- if (config.verticalId === null && config.selectedPresetIds.length > 0) issues = appendUniqueIssue(issues, { code: "missing_vertical" });
2705
- if (rawPresetValue !== void 0 && !Array.isArray(rawPresetValue) || rawPresetIds.some((presetId) => parseVerticalPresetId(presetId) === null)) issues = appendUniqueIssue(issues, {
2706
- code: "unknown_preset",
2707
- rawValue: rawPresetValue
2708
- });
2709
- if (rawConfig.verticalId != null && config.selectedPresetIds.length === 0) issues = appendUniqueIssue(issues, { code: "empty_selected_presets" });
2710
- return issues;
2711
- }
2712
- function collectPresetConfigIssues(config) {
2713
- const seenPresetIds = /* @__PURE__ */ new Set();
2714
- const seenBehaviours = /* @__PURE__ */ new Set();
2715
- let issues = [];
2716
- for (const presetId of config.selectedPresetIds) {
2717
- const presetDefinition = getVerticalPresetDefinition(presetId);
2718
- if (seenPresetIds.has(presetId)) issues = appendUniqueIssue(issues, { code: "duplicate_preset_id" });
2719
- seenPresetIds.add(presetId);
2720
- if (config.verticalId !== null && presetDefinition.verticalId !== config.verticalId) issues = appendUniqueIssue(issues, { code: "preset_vertical_mismatch" });
2721
- if (presetDefinition.availability === "future") issues = appendUniqueIssue(issues, { code: "future_preset_selected" });
2722
- if (seenBehaviours.has(presetDefinition.canonicalBehaviour)) issues = appendUniqueIssue(issues, { code: "duplicate_canonical_behaviour" });
2723
- seenBehaviours.add(presetDefinition.canonicalBehaviour);
3249
+ listPublicTeamMembers: {
3250
+ path: "/public/sites/{siteId}/team-members",
3251
+ method: "GET",
3252
+ revalidate: publicReadFreshnessSeconds("listPublicTeamMembers"),
3253
+ tags: [TAG_TEMPLATES.publicTeamMembers],
3254
+ errors: [],
3255
+ auth: "public",
3256
+ responseKind: "json"
3257
+ },
3258
+ listPublicPasses: {
3259
+ path: "/public/sites/{siteId}/bookings/products/passes",
3260
+ method: "GET",
3261
+ revalidate: publicReadFreshnessSeconds("listPublicPasses"),
3262
+ tags: [TAG_TEMPLATES.publicPasses],
3263
+ auth: "public",
3264
+ responseKind: "json"
3265
+ },
3266
+ listPublicMemberships: {
3267
+ path: "/public/sites/{siteId}/bookings/products/memberships",
3268
+ method: "GET",
3269
+ revalidate: publicReadFreshnessSeconds("listPublicMemberships"),
3270
+ tags: [TAG_TEMPLATES.publicMemberships],
3271
+ auth: "public",
3272
+ responseKind: "json"
3273
+ },
3274
+ listPublicProducts: {
3275
+ path: "/public/sites/{siteId}/products",
3276
+ method: "GET",
3277
+ revalidate: publicReadFreshnessSeconds("listPublicProducts"),
3278
+ tags: [TAG_TEMPLATES.publicProducts],
3279
+ errors: [],
3280
+ auth: "public",
3281
+ responseKind: "json"
2724
3282
  }
2725
- return issues;
2726
- }
2727
- function getSiteVerticalConfigIssues(rawConfig, config) {
2728
- let issues = collectRawConfigIssues(rawConfig, config);
2729
- for (const issue of collectPresetConfigIssues(config)) issues = appendUniqueIssue(issues, issue);
2730
- return issues;
2731
- }
2732
- function resolveSiteVerticalBookingConfig(rawConfig) {
2733
- if (rawConfig == null) return {
2734
- kind: "unconfigured",
2735
- config: DEFAULT_SITE_VERTICAL_BOOKING_CONFIG
2736
- };
2737
- const verticalId = parseVerticalId(rawConfig.verticalId);
2738
- const selectedPresetIds = (Array.isArray(rawConfig.selectedPresetIds) ? rawConfig.selectedPresetIds : []).map(parseVerticalPresetId).filter((presetId) => Boolean(presetId));
2739
- const config = {
2740
- verticalId,
2741
- selectedPresetIds
2742
- };
2743
- if (rawConfig.verticalId == null && selectedPresetIds.length === 0) return {
2744
- kind: "unconfigured",
2745
- config: DEFAULT_SITE_VERTICAL_BOOKING_CONFIG
2746
- };
2747
- const issues = getSiteVerticalConfigIssues(rawConfig, config);
2748
- if (issues.length > 0) return {
2749
- kind: "invalid",
2750
- config,
2751
- issues
2752
- };
2753
- const nonEmptyPresetIds = toNonEmptyVerticalPresetIds(selectedPresetIds);
2754
- const configuredConfig = verticalId !== null && nonEmptyPresetIds !== null ? createConfiguredSiteVerticalBookingConfig(verticalId, nonEmptyPresetIds) : null;
2755
- if (configuredConfig === null) return {
2756
- kind: "invalid",
2757
- config,
2758
- issues: [{ code: "empty_selected_presets" }]
2759
- };
2760
- return {
2761
- kind: "configured",
2762
- config: configuredConfig,
2763
- selectedPresets: configuredConfig.selectedPresetIds.map(getVerticalPresetDefinition)
2764
- };
3283
+ });
3284
+ ({
3285
+ ...ACCOUNT_ENDPOINT_DEFINITIONS,
3286
+ ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
3287
+ ...GIFTING_ENDPOINT_DEFINITIONS,
3288
+ ...PUBLIC_READ_ENDPOINT_DEFINITIONS
3289
+ }), 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, {
3290
+ ...NEWSLETTER_ENDPOINT_DEFINITIONS,
3291
+ ...APPOINTMENT_ENDPOINT_DEFINITIONS,
3292
+ ...EVENT_SERIES_REGISTRATION_ENDPOINT_DEFINITIONS
3293
+ }, 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 };
3294
+ z.object({ refreshToken: z.string().min(1).max(4096) }).strict();
3295
+ z.object({
3296
+ accessToken: z.string().min(1).max(4096),
3297
+ refreshToken: z.string().min(1).max(4096)
3298
+ }).strict();
3299
+ const ACCOUNT_SITE_CURSOR_MAX_LENGTH = 2048;
3300
+ 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})$/;
3301
+ /** Strict, injection-safe PostgreSQL/RFC3339 timestamp text used in cursors. */
3302
+ function isAccountSiteTimestamp(value) {
3303
+ if (typeof value !== "string") return false;
3304
+ const match = ACCOUNT_SITE_TIMESTAMP_PATTERN.exec(value);
3305
+ if (!match) return false;
3306
+ const [, year, month, day, hour, minute, second, , offset] = match;
3307
+ const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
3308
+ const offsetValue = offset ?? "";
3309
+ const offsetIsValid = offsetValue === "Z" || (() => {
3310
+ const offsetMatch = /^(?:[+-])(\d{2}):(\d{2})$/.exec(offsetValue);
3311
+ if (!offsetMatch) return false;
3312
+ return Number(offsetMatch[1]) <= 23 && Number(offsetMatch[2]) <= 59;
3313
+ })();
3314
+ return date.getUTCFullYear() === Number(year) && date.getUTCMonth() === Number(month) - 1 && date.getUTCDate() === Number(day) && Number(hour) <= 23 && Number(minute) <= 59 && Number(second) <= 59 && offsetIsValid;
2765
3315
  }
3316
+ const accountSiteTimestampSchema = z.string().refine(isAccountSiteTimestamp, { message: "Expected a strict account site timestamp" });
3317
+ z.enum([
3318
+ "managed",
3319
+ "headless_cms",
3320
+ "headless_full"
3321
+ ]);
3322
+ z.object({ title: z.string().trim().min(1).max(120) });
3323
+ const abandonedStandardKeyIdsSchema = z.object({
3324
+ live: z.string().uuid().optional(),
3325
+ management: z.string().uuid().optional()
3326
+ }).strict();
3327
+ const abandonedPreviewKeyIdsSchema = z.object({
3328
+ live: z.string().uuid().optional(),
3329
+ management: z.string().uuid().optional(),
3330
+ preview: z.string().uuid().optional()
3331
+ }).strict();
3332
+ z.discriminatedUnion("operation", [
3333
+ z.object({
3334
+ operation: z.literal("issue_standard"),
3335
+ siteId: z.string().uuid(),
3336
+ operationId: z.string().uuid(),
3337
+ projectName: z.string().trim().min(1).max(120),
3338
+ abandonedKeyIds: abandonedStandardKeyIdsSchema.default({})
3339
+ }).strict(),
3340
+ z.object({
3341
+ operation: z.literal("issue_with_preview"),
3342
+ siteId: z.string().uuid(),
3343
+ operationId: z.string().uuid(),
3344
+ projectName: z.string().trim().min(1).max(120),
3345
+ abandonedKeyIds: abandonedPreviewKeyIdsSchema.default({})
3346
+ }).strict(),
3347
+ z.object({
3348
+ operation: z.literal("revalidate_standard"),
3349
+ siteId: z.string().uuid(),
3350
+ operationId: z.string().uuid(),
3351
+ projectName: z.string().trim().min(1).max(120),
3352
+ keyIds: z.object({
3353
+ live: z.string().uuid(),
3354
+ management: z.string().uuid()
3355
+ }).strict()
3356
+ }).strict(),
3357
+ z.object({
3358
+ operation: z.literal("revalidate_with_preview"),
3359
+ siteId: z.string().uuid(),
3360
+ operationId: z.string().uuid(),
3361
+ projectName: z.string().trim().min(1).max(120),
3362
+ keyIds: z.object({
3363
+ live: z.string().uuid(),
3364
+ management: z.string().uuid(),
3365
+ preview: z.string().uuid()
3366
+ }).strict()
3367
+ }).strict()
3368
+ ]);
3369
+ z.object({
3370
+ cursor: z.string().min(1).max(ACCOUNT_SITE_CURSOR_MAX_LENGTH).optional(),
3371
+ limit: z.coerce.number().int().min(1).max(100).default(50),
3372
+ search: z.string().trim().min(1).max(120).optional()
3373
+ });
2766
3374
  z.object({
2767
3375
  formulaVersion: z.literal("managed-domain-overage-v1"),
2768
3376
  conversionRule: z.literal("usd-to-gbp-1-to-1-v1"),
@@ -20722,102 +21330,31 @@ const newsletterFormFragment = defineFragment({
20722
21330
  description: "Newsletter subscription form with preset field configurations.",
20723
21331
  fields: [
20724
21332
  {
20725
- id: "destinationProvider",
20726
- type: "select",
20727
- label: "Send signups to",
20728
- description: "Choose where new subscribers from this form should go.",
20729
- defaultValue: "riverbank",
20730
- required: false,
20731
- ui: { intent: "behavior" },
20732
- options: [{
20733
- value: "riverbank",
20734
- label: "Riverbank newsletter"
20735
- }]
20736
- },
20737
- {
20738
- id: "listId",
20739
- type: "reference",
20740
- label: "Newsletter list",
20741
- description: "Select the list new subscribers should be added to.",
20742
- required: false,
20743
- referenceKind: "newsletterList",
20744
- allowManualEntry: false,
20745
- ui: {
20746
- intent: "behavior",
20747
- visibleWhen: {
20748
- field: "destinationProvider",
20749
- equals: "riverbank"
20750
- }
20751
- }
20752
- },
20753
- {
20754
- id: "mailchimpAudienceId",
20755
- type: "reference",
20756
- label: "Mailchimp audience",
20757
- description: "Choose the Mailchimp audience for new subscribers.",
20758
- required: false,
20759
- referenceKind: "mailchimpAudience",
20760
- allowManualEntry: false,
20761
- ui: {
20762
- intent: "behavior",
20763
- visibleWhen: {
20764
- field: "destinationProvider",
20765
- equals: "mailchimp"
20766
- }
20767
- }
20768
- },
20769
- {
20770
- id: "mailchimpTags",
20771
- type: "text",
20772
- label: "Mailchimp tags",
20773
- description: "Optional tags to apply in Mailchimp, separated by commas or semicolons.",
21333
+ id: "targeting",
21334
+ type: "group",
21335
+ label: "Mailing-list targeting",
21336
+ description: "Choose what kind of emails people signing up here will receive.",
21337
+ defaultValue: { mode: "default" },
21338
+ required: true,
20774
21339
  ui: {
20775
21340
  intent: "behavior",
20776
- visibleWhen: {
20777
- field: "destinationProvider",
20778
- equals: "mailchimp"
20779
- }
20780
- }
20781
- },
20782
- {
20783
- id: "preset",
20784
- type: "select",
20785
- label: "Form fields",
20786
- description: "Choose which fields to collect.",
20787
- defaultValue: "emailOnly",
20788
- ui: { intent: "display" },
20789
- options: [
20790
- {
20791
- value: "emailOnly",
20792
- label: "Email only"
20793
- },
20794
- {
20795
- value: "emailName",
20796
- label: "Email + Name"
20797
- },
20798
- {
20799
- value: "emailFirstLast",
20800
- label: "Email + First/Last Name"
20801
- },
20802
- {
20803
- value: "emailNameCompany",
20804
- label: "Email + Name + Company (B2B)"
20805
- }
20806
- ]
20807
- },
20808
- {
20809
- id: "includePhone",
20810
- type: "boolean",
20811
- label: "Include phone field (optional)",
20812
- description: "Adds an optional phone field to the selected preset (never required).",
20813
- defaultValue: false,
20814
- ui: { intent: "display" }
20815
- },
20816
- {
20817
- id: "buttonLabel",
20818
- type: "text",
20819
- label: "Button label",
20820
- defaultValue: "Subscribe"
21341
+ widget: "mailingListTargeting"
21342
+ },
21343
+ schema: { fields: [{
21344
+ id: "mode",
21345
+ type: "select",
21346
+ label: "Mode",
21347
+ required: true,
21348
+ defaultValue: "default",
21349
+ options: [{
21350
+ value: "default",
21351
+ label: "Default"
21352
+ }, {
21353
+ value: "custom",
21354
+ label: "Custom"
21355
+ }],
21356
+ ui: { hidden: true }
21357
+ }] }
20821
21358
  },
20822
21359
  createButtonVariantField({
20823
21360
  id: "buttonVariant",
@@ -20825,46 +21362,42 @@ const newsletterFormFragment = defineFragment({
20825
21362
  defaultValue: "primary"
20826
21363
  }),
20827
21364
  {
20828
- id: "successMessage",
20829
- type: "text",
20830
- label: "Success message",
20831
- description: "Shown after successful subscription.",
20832
- defaultValue: "Thanks for subscribing!"
20833
- },
20834
- {
20835
- id: "privacyNote",
21365
+ id: "mailingListSource",
20836
21366
  type: "text",
20837
- label: "Privacy note",
20838
- description: "Optional small text below the form (e.g., privacy policy link)."
21367
+ label: "Mailing-list source",
21368
+ ui: {
21369
+ hidden: true,
21370
+ intent: "behavior"
21371
+ }
20839
21372
  },
20840
21373
  {
20841
- id: "tags",
21374
+ id: "mailingListSourceToken",
20842
21375
  type: "text",
20843
- label: "Tags (optional)",
20844
- description: "Optional tags to apply on subscribe (comma or semicolon separated).",
21376
+ label: "Mailing-list source token",
20845
21377
  ui: {
20846
- intent: "behavior",
20847
- visibleWhen: {
20848
- field: "destinationProvider",
20849
- equals: "riverbank"
20850
- }
21378
+ hidden: true,
21379
+ intent: "behavior"
20851
21380
  }
20852
21381
  }
20853
21382
  ],
20854
21383
  layout: [el("newsletter-form", withApiBindings({
20855
- source: "block",
20856
- destinationProvider: { $bind: { from: "destinationProvider" } },
20857
- listId: { $bind: { from: "listId" } },
20858
- preset: { $bind: { from: "preset" } },
20859
- includePhone: { $bind: { from: "includePhone" } },
20860
- buttonLabel: { $bind: { from: "buttonLabel" } },
21384
+ source: { $bind: {
21385
+ from: "mailingListSource",
21386
+ fallback: "block"
21387
+ } },
21388
+ sourceRef: {
21389
+ tag: "signed_source_ref",
21390
+ token: { $bind: { from: "mailingListSourceToken" } }
21391
+ },
21392
+ targeting: { $bind: {
21393
+ from: "targeting",
21394
+ fallback: { mode: "default" }
21395
+ } },
21396
+ mailingList: { $bind: { from: "$root.mailingList" } },
20861
21397
  buttonVariant: { $bind: {
20862
21398
  from: "buttonVariant",
20863
21399
  fallback: "primary"
20864
21400
  } },
20865
- successMessage: { $bind: { from: "successMessage" } },
20866
- privacyNote: { $bind: { from: "privacyNote" } },
20867
- tags: { $bind: { from: "tags" } },
20868
21401
  spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } }
20869
21402
  }), [])]
20870
21403
  });
@@ -23039,14 +23572,19 @@ const contactNewsletterLayout = styledSection({
23039
23572
  as: "h2",
23040
23573
  className: "newsletter-copy-title"
23041
23574
  }, when("newsletterTitle"), bind("newsletterTitle")), richText({ className: "newsletter-copy-subtitle rb-prose rb-max-w-none" }, when("newsletterSubtitle"), bind("newsletterSubtitle"))]), el("newsletter-form", withApiBindings({
23042
- source: "block",
23043
- listId: { $bind: { from: "listId" } },
23044
- preset: { $bind: { from: "preset" } },
23045
- includePhone: { $bind: { from: "includePhone" } },
23046
- buttonLabel: { $bind: { from: "newsletterButtonLabel" } },
23047
- successMessage: { $bind: { from: "newsletterSuccessMessage" } },
23048
- privacyNote: { $bind: { from: "newsletterPrivacyNote" } },
23049
- tags: { $bind: { from: "newsletterTags" } },
23575
+ source: { $bind: {
23576
+ from: "mailingListSource",
23577
+ fallback: "block"
23578
+ } },
23579
+ sourceRef: {
23580
+ tag: "signed_source_ref",
23581
+ token: { $bind: { from: "mailingListSourceToken" } }
23582
+ },
23583
+ targeting: { $bind: {
23584
+ from: "targeting",
23585
+ fallback: { mode: "default" }
23586
+ } },
23587
+ mailingList: { $bind: { from: "$root.mailingList" } },
23050
23588
  spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } }
23051
23589
  }), [])])])], { className: "contact-newsletter-container" }),
23052
23590
  baseClass: "",
@@ -23082,79 +23620,55 @@ const contactNewsletterBlockDefinition = {
23082
23620
  label: "Newsletter intro"
23083
23621
  },
23084
23622
  {
23085
- id: "listId",
23086
- type: "reference",
23087
- label: "Newsletter list",
23088
- description: "Select the list new subscribers should be added to.",
23623
+ id: "targeting",
23624
+ type: "group",
23625
+ label: "Mailing-list targeting",
23626
+ description: "Choose what kind of emails people signing up here will receive.",
23089
23627
  required: true,
23090
- referenceKind: "newsletterList",
23091
- allowManualEntry: false,
23092
- ui: { intent: "behavior" }
23093
- },
23094
- {
23095
- id: "preset",
23096
- type: "select",
23097
- label: "Newsletter fields",
23098
- defaultValue: "emailOnly",
23099
- ui: { intent: "display" },
23100
- options: [
23101
- {
23102
- value: "emailOnly",
23103
- label: "Email only"
23104
- },
23105
- {
23106
- value: "emailName",
23107
- label: "Email + Name"
23108
- },
23109
- {
23110
- value: "emailFirstLast",
23111
- label: "Email + First/Last Name"
23112
- },
23113
- {
23114
- value: "emailNameCompany",
23115
- label: "Email + Name + Company (B2B)"
23116
- }
23117
- ]
23118
- },
23119
- {
23120
- id: "includePhone",
23121
- type: "boolean",
23122
- label: "Include phone field",
23123
- defaultValue: false,
23124
- ui: { intent: "display" }
23125
- },
23126
- {
23127
- id: "newsletterButtonLabel",
23128
- type: "text",
23129
- label: "Newsletter button label",
23130
- defaultValue: "Subscribe",
23131
- ui: { intent: "display" }
23132
- },
23133
- {
23134
- id: "newsletterSuccessMessage",
23135
- type: "text",
23136
- label: "Newsletter success message",
23137
- defaultValue: "Thanks for subscribing!",
23138
- ui: { intent: "behavior" }
23628
+ defaultValue: { mode: "default" },
23629
+ ui: {
23630
+ intent: "behavior",
23631
+ widget: "mailingListTargeting"
23632
+ },
23633
+ schema: { fields: [{
23634
+ id: "mode",
23635
+ type: "select",
23636
+ label: "Mode",
23637
+ required: true,
23638
+ defaultValue: "default",
23639
+ options: [{
23640
+ value: "default",
23641
+ label: "Default"
23642
+ }, {
23643
+ value: "custom",
23644
+ label: "Custom"
23645
+ }],
23646
+ ui: { hidden: true }
23647
+ }] }
23139
23648
  },
23140
23649
  {
23141
- id: "newsletterPrivacyNote",
23650
+ id: "contactTitle",
23142
23651
  type: "text",
23143
- label: "Privacy note",
23144
- ui: { intent: "display" }
23652
+ label: "Contact title",
23653
+ defaultValue: "Get in touch"
23145
23654
  },
23146
23655
  {
23147
- id: "newsletterTags",
23656
+ id: "mailingListSource",
23148
23657
  type: "text",
23149
- label: "Newsletter tags",
23150
- description: "Optional tags to apply on subscribe, separated by comma or semicolon.",
23151
- ui: { intent: "behavior" }
23658
+ label: "Mailing-list source",
23659
+ ui: {
23660
+ hidden: true,
23661
+ intent: "behavior"
23662
+ }
23152
23663
  },
23153
23664
  {
23154
- id: "contactTitle",
23665
+ id: "mailingListSourceToken",
23155
23666
  type: "text",
23156
- label: "Contact title",
23157
- defaultValue: "Get in touch"
23667
+ label: "Mailing-list source token",
23668
+ ui: {
23669
+ hidden: true,
23670
+ intent: "behavior"
23671
+ }
23158
23672
  },
23159
23673
  {
23160
23674
  id: "contactIntro",
@@ -23355,6 +23869,12 @@ const arrayLengthTransform = {
23355
23869
  summary: "Get the length of an array",
23356
23870
  run: (value) => Array.isArray(value) ? value.length : 0
23357
23871
  };
23872
+ const booleanNotTransform = {
23873
+ id: "boolean.not",
23874
+ kind: "formatter",
23875
+ summary: "Invert a boolean value; fail closed to false for other inputs",
23876
+ run: (value) => typeof value === "boolean" ? !value : false
23877
+ };
23358
23878
  const jsonArrayTransform = {
23359
23879
  id: "value.jsonArray",
23360
23880
  kind: "formatter",
@@ -23405,33 +23925,51 @@ const marqueeLoopTransform = {
23405
23925
  function toUnknownArray(value) {
23406
23926
  return Array.isArray(value) ? value : [];
23407
23927
  }
23928
+ /**
23929
+ * Map a content state/enum to a theme token colour — the first-class way to turn
23930
+ * a value like a class's availability (`available`/`filling`/`waitlist`), a
23931
+ * card's featured flag, or a pricing tier level into a themed colour. Authors
23932
+ * write role names and get `rgb(var(--tb-<role>))`, so the result live-recolours
23933
+ * with the theme and drops directly into a `style` binding (e.g.
23934
+ * `borderLeftColor`). Returns `undefined` when the value matches no case and no
23935
+ * fallback is given, so the style property is simply omitted.
23936
+ */
23937
+ const mapTokenTransform = {
23938
+ id: "value.mapToken",
23939
+ kind: "formatter",
23940
+ summary: "Map a state/enum value to a theme token colour (rgb(var(--tb-<role>)))",
23941
+ schema: z.object({
23942
+ /**
23943
+ * Maps an input state/enum value (stringified) to a theme token ROLE name
23944
+ * (e.g. `secondary`, `accent`, `muted`, `border`). The role is wrapped to
23945
+ * `rgb(var(--tb-<role>))` so the result drops straight into a `style` binding.
23946
+ */
23947
+ cases: z.record(z.string(), z.string()),
23948
+ /** Role used when the value matches no case. Omit to yield `undefined`. */
23949
+ fallback: z.string().optional()
23950
+ }),
23951
+ run: (value, options) => {
23952
+ const role = options.cases[String(value)] ?? options.fallback;
23953
+ return role ? `rgb(var(--tb-${role}))` : void 0;
23954
+ }
23955
+ };
23408
23956
  const formattingTransforms = [
23957
+ {
23958
+ id: "value.equals",
23959
+ kind: "formatter",
23960
+ summary: "Compare a value to a fixed string",
23961
+ schema: z.object({ value: z.string() }),
23962
+ run: (value, options) => value === options.value
23963
+ },
23409
23964
  uppercaseTransform,
23410
23965
  fallbackTransform,
23411
23966
  dateFormatShort,
23412
23967
  arrayLengthTransform,
23968
+ booleanNotTransform,
23413
23969
  jsonArrayTransform,
23414
23970
  csvTransform,
23415
23971
  marqueeLoopTransform,
23416
- {
23417
- id: "value.mapToken",
23418
- kind: "formatter",
23419
- summary: "Map a state/enum value to a theme token colour (rgb(var(--tb-<role>)))",
23420
- schema: z.object({
23421
- /**
23422
- * Maps an input state/enum value (stringified) to a theme token ROLE name
23423
- * (e.g. `secondary`, `accent`, `muted`, `border`). The role is wrapped to
23424
- * `rgb(var(--tb-<role>))` so the result drops straight into a `style` binding.
23425
- */
23426
- cases: z.record(z.string(), z.string()),
23427
- /** Role used when the value matches no case. Omit to yield `undefined`. */
23428
- fallback: z.string().optional()
23429
- }),
23430
- run: (value, options) => {
23431
- const role = options.cases[String(value)] ?? options.fallback;
23432
- return role ? `rgb(var(--tb-${role}))` : void 0;
23433
- }
23434
- }
23972
+ mapTokenTransform
23435
23973
  ];
23436
23974
  //#endregion
23437
23975
  //#region ../blocks/src/system/transforms/registry/ui.ts
@@ -24848,13 +25386,9 @@ const footerStandardContactBindings = {
24848
25386
  };
24849
25387
  const footerMailingListBindings = {
24850
25388
  source: { $bind: { from: "module.engagement.runtime.source" } },
24851
- destinationRef: { $bind: { from: "module.engagement.runtime.destinationRef" } },
24852
- listId: { $bind: { from: "module.engagement.runtime.listId" } },
24853
- preset: { $bind: { from: "module.engagement.runtime.preset" } },
24854
- includePhone: { $bind: { from: "module.engagement.runtime.includePhone" } },
24855
- buttonLabel: { $bind: { from: "module.engagement.runtime.buttonLabel" } },
24856
- successMessage: { $bind: { from: "module.engagement.runtime.successMessage" } },
24857
- privacyNote: { $bind: { from: "module.engagement.runtime.privacyNote" } }
25389
+ sourceRef: { $bind: { from: "module.engagement.runtime.sourceRef" } },
25390
+ mailingList: { $bind: { from: "module.engagement.runtime.mailingList" } },
25391
+ targeting: { $bind: { from: "module.engagement.runtime.targeting" } }
24858
25392
  };
24859
25393
  const footerSpamProtectionBinding = { spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } } };
24860
25394
  const brandLogo = link({
@@ -26542,6 +27076,78 @@ const appointmentBookingBlockDefinition = {
26542
27076
  }
26543
27077
  }
26544
27078
  };
27079
+ const appointmentServicesBlockDefinition = {
27080
+ manifest: createBlockManifest({
27081
+ id: "block.appointment-services",
27082
+ version: "1.0.0",
27083
+ title: "Appointment Services",
27084
+ titleSource: "heading",
27085
+ description: "List public appointment services with booking links",
27086
+ skipSectionStyles: true,
27087
+ additionalFields: [
27088
+ {
27089
+ id: "heading",
27090
+ type: "text",
27091
+ label: "Heading",
27092
+ required: false,
27093
+ multiline: false,
27094
+ defaultValue: "Appointments",
27095
+ ui: { intent: "content" }
27096
+ },
27097
+ {
27098
+ id: "description",
27099
+ type: "richText",
27100
+ label: "Description",
27101
+ required: false,
27102
+ ui: { intent: "content" }
27103
+ },
27104
+ sectionStylesField({
27105
+ id: "_sectionStyles",
27106
+ label: "Section styles",
27107
+ renderInHeader: false
27108
+ })
27109
+ ],
27110
+ styleTokens: {
27111
+ background: "surface",
27112
+ typography: "body",
27113
+ spacing: "md"
27114
+ },
27115
+ behaviours: {
27116
+ supportsThemeSwitching: true,
27117
+ inlineEditing: true,
27118
+ animation: false,
27119
+ paletteHidden: false
27120
+ },
27121
+ category: "interactive",
27122
+ contentSource: { kind: "inline" },
27123
+ tags: [
27124
+ "booking",
27125
+ "appointment",
27126
+ "services",
27127
+ "scheduling"
27128
+ ],
27129
+ icon: "CalendarDays",
27130
+ layout: [styledSection({
27131
+ baseClass: "",
27132
+ spacing: "spacious",
27133
+ children: sectionContainer([
27134
+ text({ as: "h2" }, bind("content.heading"), when("content.heading")),
27135
+ richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
27136
+ el("appointment-services", {
27137
+ sectionHeading: { $bind: { from: "content.heading" } },
27138
+ services: { $bind: { from: "data.services" } },
27139
+ pathname: { $bind: { from: "$root.pathname" } }
27140
+ })
27141
+ ], { gap: "lg" })
27142
+ })]
27143
+ }),
27144
+ dataSchemas: { services: z.unknown().optional() },
27145
+ dataLoaders: { services: {
27146
+ endpoint: "getPublicBookingServices",
27147
+ params: { siteId: { $bind: { from: "$root.siteId" } } },
27148
+ mode: "server"
27149
+ } }
27150
+ };
26545
27151
  //#endregion
26546
27152
  //#region ../blocks/src/system/blocks/weekly-slot-availability.ts
26547
27153
  const weeklySlotAvailabilityManifest = createBlockManifest({
@@ -27780,8 +28386,8 @@ z.object({
27780
28386
  previewToken: z.string().nullable().optional(),
27781
28387
  stage: previewStageSchema.optional()
27782
28388
  });
27783
- function buildEventRegistrationDisplay(source) {
27784
- const selectedRun = resolveSelectedWholeSeriesRun(source);
28389
+ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents) {
28390
+ const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
27785
28391
  if (selectedRun && (!selectedRun.run.enrollmentOpen || toWholeSeriesRegistrationEvent(selectedRun) === null)) return {
27786
28392
  kind: "event-registration",
27787
28393
  state: "registration-closed",
@@ -27789,7 +28395,7 @@ function buildEventRegistrationDisplay(source) {
27789
28395
  titleText: "Registration closed",
27790
28396
  bodyText: "This run is no longer open for bookings."
27791
28397
  };
27792
- const stepLabels = buildEventRegistrationShellStepLabels(source);
28398
+ const stepLabels = buildEventRegistrationShellStepLabels(source, scopedSourceEvents);
27793
28399
  return {
27794
28400
  kind: "event-registration",
27795
28401
  state: "generic-shell",
@@ -27800,8 +28406,8 @@ function buildEventRegistrationDisplay(source) {
27800
28406
  summaryButtonText: source.buttonText?.trim() || "Register"
27801
28407
  };
27802
28408
  }
27803
- function buildEventRegistrationShellStepLabels(source) {
27804
- const selectedRun = resolveSelectedWholeSeriesRun(source);
28409
+ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
28410
+ const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
27805
28411
  if (selectedRun) {
27806
28412
  const selectedEvent = toWholeSeriesRegistrationEvent(selectedRun);
27807
28413
  if (!selectedEvent) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
@@ -27811,7 +28417,7 @@ function buildEventRegistrationShellStepLabels(source) {
27811
28417
  event: selectedEvent
27812
28418
  })).map((step) => shellLabelForRegistrationStep(step, [selectedEvent]));
27813
28419
  }
27814
- const scopedEvents = scopeEventRegistrationEvents(source);
28420
+ const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
27815
28421
  if (scopedEvents.length === 0) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
27816
28422
  const preSelectedEvent = findPreSelectedEvent({
27817
28423
  scopedEvents,
@@ -27824,31 +28430,25 @@ function buildEventRegistrationShellStepLabels(source) {
27824
28430
  event: preSelectedEvent
27825
28431
  }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
27826
28432
  }
27827
- function scopeEventRegistrationEvents(source) {
27828
- const scoped = scopeEventRegistrationSourceEvents(source);
27829
- return [...toRegistrationEvents(scoped), ...toWholeSeriesRegistrationEvents(scoped)];
28433
+ function toScopedEventRegistrationEvents(scopedSourceEvents) {
28434
+ return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
27830
28435
  }
27831
- /**
27832
- * Every event this surface may register for, from both of its loads.
27833
- *
27834
- * The date page and the run read answer different questions and can name the
27835
- * same occurrence, so the two lists are joined and any occurrence that arrives
27836
- * twice is kept once, in the order the date page gave it.
27837
- */
27838
28436
  function scopeEventRegistrationSourceEvents(source) {
27839
- const byOccurrenceId = /* @__PURE__ */ new Map();
27840
- for (const event of [...source.events ?? [], ...source.wholeSeriesRunEvents ?? []]) if (!byOccurrenceId.has(event.occurrenceId)) byOccurrenceId.set(event.occurrenceId, event);
27841
- return resolveEventRegistrationScopedEvents([...byOccurrenceId.values()], {
28437
+ return resolveEventRegistrationScopedEvents(source.wholeSeriesRunEvents ?? source.events ?? [], {
27842
28438
  occurrenceContext: source.occurrenceContext,
27843
28439
  subject: source.subject
27844
28440
  });
27845
28441
  }
28442
+ function resolveLoadedEventRegistrationSourceEvents(source, scopedSourceEvents) {
28443
+ if (source.events == null && source.wholeSeriesRunEvents == null) return null;
28444
+ return [...scopedSourceEvents];
28445
+ }
27846
28446
  /**
27847
28447
  * The one run this surface is registering for, when the scoped events describe
27848
28448
  * exactly one. More than one run means the visitor still has to choose.
27849
28449
  */
27850
- function resolveSelectedWholeSeriesRun(source) {
27851
- const runs = groupPublicEventsByRun(scopeEventRegistrationSourceEvents(source));
28450
+ function resolveSelectedWholeSeriesRun(scopedSourceEvents) {
28451
+ const runs = groupPublicEventsByRun(scopedSourceEvents);
27852
28452
  return runs.length === 1 ? runs[0] : null;
27853
28453
  }
27854
28454
  function shellLabelForRegistrationStep(step, events) {
@@ -27882,9 +28482,10 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
27882
28482
  render: createZodCodec(eventRegistrationRenderSchema)
27883
28483
  },
27884
28484
  buildRender: (source) => {
27885
- const events = source.events == null ? null : scopeEventRegistrationSourceEvents(source);
28485
+ const scopedSourceEvents = scopeEventRegistrationSourceEvents(source);
28486
+ const events = resolveLoadedEventRegistrationSourceEvents(source, scopedSourceEvents);
27886
28487
  return {
27887
- display: buildEventRegistrationDisplay(source),
28488
+ display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents),
27888
28489
  hydration: {
27889
28490
  occurrenceContext: source.occurrenceContext ?? null,
27890
28491
  subject: source.subject ?? null,
@@ -28085,6 +28686,11 @@ const eventRegistrationBlockDefinition = {
28085
28686
  dataLoaders: {
28086
28687
  events: {
28087
28688
  endpoint: "listPublicEvents",
28689
+ when: { $bind: {
28690
+ from: "$root.subject.isWholeSeriesBooking",
28691
+ fallback: true,
28692
+ transforms: [{ id: "boolean.not" }]
28693
+ } },
28088
28694
  params: {
28089
28695
  siteId: { $bind: { from: "$root.siteId" } },
28090
28696
  limit: "50",
@@ -28106,13 +28712,9 @@ const eventRegistrationBlockDefinition = {
28106
28712
  /**
28107
28713
  * The runs this page can register for, finished ones included.
28108
28714
  *
28109
- * The `events` page above is date-bounded, so a run whose first session has
28110
- * passed contributes no rows to it and the surface would have nothing to
28111
- * render as closed. This asks for runs instead, and only on a page whose
28112
- * subject books whole runs. It is scoped exactly like the date page — same
28113
- * series, same run subroute — because scope and grain are separate
28114
- * questions. The two lists are joined by occurrence, so a run that appears
28115
- * in both is still one run.
28715
+ * A run read is the sole Event source on a whole-series surface. It is
28716
+ * status-bounded rather than date-bounded, so completed runs can still
28717
+ * render their closed state and every run carries its complete schedule.
28116
28718
  */
28117
28719
  wholeSeriesRunEvents: {
28118
28720
  endpoint: "listPublicEvents",
@@ -30705,20 +31307,7 @@ function isRecord$2(value) {
30705
31307
  return typeof value === "object" && value !== null && !Array.isArray(value);
30706
31308
  }
30707
31309
  //#endregion
30708
- //#region ../blocks/src/system/blocks/offerings-preview/index.ts
30709
- /**
30710
- * The whole-series presets, as the CSV `listPublicEvents` expects. Both
30711
- * whole-series presets share one loader: the block renders them as a single
30712
- * run group.
30713
- */
30714
- const WHOLE_SERIES_EVENT_PRESETS_PARAM = WHOLE_SERIES_EVENT_PRESET_VALUES.join(",");
30715
- const publicRouteCandidatesSchema = z.object({ routes: z.array(z.object({
30716
- entityId: z.string(),
30717
- path: z.string(),
30718
- title: z.string(),
30719
- kind: z.enum(RESOLVED_INTERNAL_LINK_ENTITY_TYPES),
30720
- contentTypeKey: z.string().nullable().optional()
30721
- })) });
31310
+ //#region ../blocks/src/system/blocks/offerings-preview/fields.ts
30722
31311
  const groupOverrideFields = [
30723
31312
  {
30724
31313
  id: "sourceKey",
@@ -30924,7 +31513,65 @@ const offeringsPreviewFields = [
30924
31513
  renderInHeader: false
30925
31514
  })
30926
31515
  ];
31516
+ function labelForOfferingPreviewSourceKey(sourceKey) {
31517
+ const eventDefinition = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.find((definition) => definition.sourceKey === sourceKey);
31518
+ if (eventDefinition) return eventDefinition.label;
31519
+ return "Courses";
31520
+ }
31521
+ //#endregion
31522
+ //#region ../blocks/src/system/blocks/offerings-preview/index.ts
31523
+ /**
31524
+ * The whole-series presets, as the CSV `listPublicEvents` expects. Both
31525
+ * whole-series presets share one loader: the block renders them as a single
31526
+ * run group.
31527
+ */
31528
+ const WHOLE_SERIES_EVENT_PRESETS_PARAM = WHOLE_SERIES_EVENT_PRESET_VALUES.join(",");
31529
+ const publicRouteCandidatesSchema = z.object({ routes: z.array(z.object({
31530
+ entityId: z.string(),
31531
+ path: z.string(),
31532
+ title: z.string(),
31533
+ kind: z.enum(RESOLVED_INTERNAL_LINK_ENTITY_TYPES),
31534
+ contentTypeKey: z.string().nullable().optional()
31535
+ })) });
30927
31536
  const offeringsPreviewEventDataBindings = Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.dataKey, { $bind: { from: `data.${definition.dataKey}` } }]));
31537
+ const DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE = "preview";
31538
+ const displayModeBinding = {
31539
+ from: "content.displayMode",
31540
+ fallback: DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE
31541
+ };
31542
+ const catalogueMode = { $bind: {
31543
+ ...displayModeBinding,
31544
+ transforms: [{
31545
+ id: "value.equals",
31546
+ options: { value: "catalogue" }
31547
+ }]
31548
+ } };
31549
+ const previewMode = { $bind: {
31550
+ ...displayModeBinding,
31551
+ transforms: [{
31552
+ id: "value.equals",
31553
+ options: { value: "preview" }
31554
+ }]
31555
+ } };
31556
+ function offeringsPreviewHeaderNodes() {
31557
+ return [text({
31558
+ as: "h2",
31559
+ className: { $bind: {
31560
+ from: "content.displayMode",
31561
+ fallback: "rb-text-center",
31562
+ transforms: [{
31563
+ id: "value.equals",
31564
+ options: { value: "catalogue" }
31565
+ }, {
31566
+ id: "ui.conditionalClass",
31567
+ options: {
31568
+ whenTrue: "rb-text-left",
31569
+ whenFalse: "rb-text-center"
31570
+ }
31571
+ }]
31572
+ } }
31573
+ }, bind("content.heading"), when("content.heading")), richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description"))];
31574
+ }
30928
31575
  const offeringsPreviewManifest = createBlockManifest({
30929
31576
  id: "block.offerings-preview",
30930
31577
  version: "1.0.0",
@@ -30955,48 +31602,45 @@ const offeringsPreviewManifest = createBlockManifest({
30955
31602
  ],
30956
31603
  icon: "PanelsTopLeft",
30957
31604
  layout: [styledSection({
30958
- children: sectionContainer([
30959
- text({
30960
- as: "h2",
30961
- className: "rb-text-center"
30962
- }, bind("content.heading"), when("content.heading")),
30963
- richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
30964
- el("offerings-preview", {
30965
- bookingOfferingPresets: { $bind: {
30966
- from: "$root.bookingOfferingPresets",
30967
- fallback: []
30968
- } },
30969
- groupOverrides: { $bind: {
30970
- from: "content.groupOverrides",
30971
- fallback: []
30972
- } },
30973
- groupOrder: { $bind: { from: "content.groupOrder" } },
30974
- cardStyles: { $bind: { from: "content._cardStyles" } },
30975
- itemLimit: { $bind: {
30976
- from: "content.itemLimit",
30977
- fallback: "3"
30978
- } },
30979
- buttonVariant: { $bind: {
30980
- from: "content.buttonVariant",
30981
- fallback: "primary"
30982
- } },
30983
- buttonText: { $bind: {
30984
- from: "content.buttonText",
30985
- fallback: OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT
30986
- } },
30987
- emptyMessage: { $bind: {
30988
- from: "content.emptyMessage",
30989
- fallback: OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE
30990
- } },
30991
- previewStage: { $bind: {
30992
- from: "$root.previewStage",
30993
- fallback: "published"
30994
- } },
30995
- routeCandidates: { $bind: { from: "data.routeCandidates" } },
30996
- wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
30997
- ...offeringsPreviewEventDataBindings
30998
- })
30999
- ], { gap: "lg" }),
31605
+ children: sectionContainer([el("offerings-preview", {
31606
+ displayMode: { $bind: displayModeBinding },
31607
+ pathname: { $bind: { from: "$root.pathname" } },
31608
+ catalogueEvents: { $bind: { from: "data.catalogueEvents" } },
31609
+ quickDateEvents: { $bind: { from: "data.quickDateEvents" } },
31610
+ bookingOfferingPresets: { $bind: {
31611
+ from: "$root.bookingOfferingPresets",
31612
+ fallback: []
31613
+ } },
31614
+ groupOverrides: { $bind: {
31615
+ from: "content.groupOverrides",
31616
+ fallback: []
31617
+ } },
31618
+ groupOrder: { $bind: { from: "content.groupOrder" } },
31619
+ cardStyles: { $bind: { from: "content._cardStyles" } },
31620
+ itemLimit: { $bind: {
31621
+ from: "content.itemLimit",
31622
+ fallback: "3"
31623
+ } },
31624
+ buttonVariant: { $bind: {
31625
+ from: "content.buttonVariant",
31626
+ fallback: "primary"
31627
+ } },
31628
+ buttonText: { $bind: {
31629
+ from: "content.buttonText",
31630
+ fallback: OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT
31631
+ } },
31632
+ emptyMessage: { $bind: {
31633
+ from: "content.emptyMessage",
31634
+ fallback: OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE
31635
+ } },
31636
+ previewStage: { $bind: {
31637
+ from: "$root.previewStage",
31638
+ fallback: "published"
31639
+ } },
31640
+ routeCandidates: { $bind: { from: "data.routeCandidates" } },
31641
+ wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
31642
+ ...offeringsPreviewEventDataBindings
31643
+ }, offeringsPreviewHeaderNodes())], { gap: "lg" }),
31000
31644
  baseClass: "",
31001
31645
  spacing: "comfortable"
31002
31646
  })]
@@ -31010,16 +31654,48 @@ const offeringsPreviewBlockDefinition = {
31010
31654
  dataSchemas: {
31011
31655
  ...offeringsPreviewEventDataSchemas,
31012
31656
  routeCandidates: publicRouteCandidatesSchema.optional(),
31013
- wholeSeriesRunEvents: optionalPublicEventsArraySchema
31657
+ wholeSeriesRunEvents: optionalPublicEventsArraySchema,
31658
+ catalogueEvents: optionalPublicEventsArraySchema,
31659
+ quickDateEvents: optionalPublicEventsArraySchema
31014
31660
  },
31015
31661
  dataLoaders: {
31016
31662
  ...offeringsPreviewEventDataLoaders,
31017
31663
  routeCandidates: {
31018
31664
  endpoint: "listPublicRouteCandidates",
31665
+ when: previewMode,
31019
31666
  params: {},
31020
31667
  mode: "server"
31021
31668
  },
31022
- wholeSeriesRunEvents: wholeSeriesRunLoader()
31669
+ wholeSeriesRunEvents: wholeSeriesRunLoader(),
31670
+ catalogueEvents: {
31671
+ endpoint: "listPublicEvents",
31672
+ when: catalogueMode,
31673
+ params: {
31674
+ stage: { $bind: {
31675
+ from: "$root.previewStage",
31676
+ fallback: "published"
31677
+ } },
31678
+ detailLevel: "listing",
31679
+ readProfile: "listing-card",
31680
+ readIntent: "catalogue"
31681
+ },
31682
+ mode: "server"
31683
+ },
31684
+ quickDateEvents: {
31685
+ endpoint: "listPublicEvents",
31686
+ when: catalogueMode,
31687
+ params: {
31688
+ stage: { $bind: {
31689
+ from: "$root.previewStage",
31690
+ fallback: "published"
31691
+ } },
31692
+ detailLevel: "listing",
31693
+ readProfile: "listing-card",
31694
+ readIntent: "occurrences",
31695
+ limit: 40
31696
+ },
31697
+ mode: "server"
31698
+ }
31023
31699
  }
31024
31700
  };
31025
31701
  /**
@@ -31036,6 +31712,7 @@ function wholeSeriesRunLoader() {
31036
31712
  function eventPresetLoader(eventPresets, extraParams = {}) {
31037
31713
  return {
31038
31714
  endpoint: "listPublicEvents",
31715
+ when: previewMode,
31039
31716
  params: {
31040
31717
  siteId: { $bind: { from: "$root.siteId" } },
31041
31718
  limit: { $bind: {
@@ -31054,15 +31731,10 @@ function eventPresetLoader(eventPresets, extraParams = {}) {
31054
31731
  mode: "server"
31055
31732
  };
31056
31733
  }
31057
- function labelForOfferingPreviewSourceKey(sourceKey) {
31058
- const eventDefinition = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.find((definition) => definition.sourceKey === sourceKey);
31059
- if (eventDefinition) return eventDefinition.label;
31060
- return "Courses";
31061
- }
31062
31734
  function normalizeOfferingsPreviewBlockContent(value) {
31063
31735
  const record = normalizeOfferingsPreviewContentForWrite(value);
31064
- if (!record) return {};
31065
- const normalized = {};
31736
+ if (!record) return { displayMode: DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE };
31737
+ const normalized = { displayMode: normalizeOfferingsPreviewDisplayMode(record.displayMode) };
31066
31738
  if (typeof record.heading === "string" || record.heading === null) normalized.heading = record.heading;
31067
31739
  if ("description" in record) normalized.description = record.description;
31068
31740
  if (isOfferingsPreviewItemLimit(record.itemLimit)) normalized.itemLimit = record.itemLimit;
@@ -31079,6 +31751,9 @@ function normalizeOfferingsPreviewBlockContent(value) {
31079
31751
  if (sectionStyles !== void 0) normalized._sectionStyles = sectionStyles;
31080
31752
  return normalized;
31081
31753
  }
31754
+ function normalizeOfferingsPreviewDisplayMode(value) {
31755
+ return value === "catalogue" ? "catalogue" : DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE;
31756
+ }
31082
31757
  function isOfferingsPreviewItemLimit(value) {
31083
31758
  return value === "1" || value === "2" || value === "3" || value === "4" || value === "5" || value === "6" || value === null;
31084
31759
  }
@@ -34293,63 +34968,13 @@ const giftingBlockDefinition = {
34293
34968
  } }
34294
34969
  };
34295
34970
  //#endregion
34296
- //#region ../blocks/src/system/blocks/newsletter-signup-destination.ts
34297
- function parseNewsletterSignupBlockId(value) {
34298
- const trimmed = parseTrimmedString(value);
34299
- return trimmed && BLOCK_ID_PATTERN.test(trimmed) ? trimmed : null;
34300
- }
34301
- function parseSignedNewsletterDestinationRef(value) {
34302
- const trimmed = parseTrimmedString(value);
34303
- return trimmed && SIGNED_DESTINATION_REF_PATTERN.test(trimmed) ? trimmed : null;
34304
- }
34305
- function buildNewsletterSignupBlockRef(blockId) {
34306
- const parsed = parseNewsletterSignupBlockId(blockId);
34307
- return parsed ? {
34308
- tag: "block_ref",
34309
- blockId: parsed
34310
- } : null;
34311
- }
34312
- function normalizeNewsletterSignupEditorValuesForDestination(values) {
34313
- const destinationProvider = parseNewsletterSignupDestinationProvider(values.destinationProvider) ?? "riverbank";
34314
- if (destinationProvider === "mailchimp") return {
34315
- destinationProvider,
34316
- mailchimpAudienceId: values.mailchimpAudienceId,
34317
- mailchimpTags: values.mailchimpTags
34318
- };
34319
- return {
34320
- destinationProvider,
34321
- listId: values.listId,
34322
- tags: values.tags
34323
- };
34324
- }
34325
- function deriveNewsletterSignupRuntimeSource(values) {
34326
- const normalized = normalizeNewsletterSignupEditorValuesForDestination(values);
34327
- if (normalized.destinationProvider === "mailchimp") return {
34328
- destinationProvider: "mailchimp",
34329
- listId: null,
34330
- tags: null
34331
- };
34332
- return {
34333
- destinationProvider: "riverbank",
34334
- listId: parseTrimmedString(normalized.listId),
34335
- tags: parseTrimmedString(normalized.tags)
34336
- };
34337
- }
34338
- const BLOCK_ID_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/;
34339
- const SIGNED_DESTINATION_REF_PATTERN = /^[A-Za-z0-9._~-]{16,1000}$/;
34340
- function parseTrimmedString(value) {
34341
- if (typeof value !== "string") return null;
34342
- const trimmed = value.trim();
34343
- return trimmed.length > 0 ? trimmed : null;
34344
- }
34345
- //#endregion
34346
34971
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.types.ts
34347
34972
  /**
34348
34973
  * Get form fields based on preset configuration.
34349
34974
  */
34350
- function getPresetFields(preset, options) {
34351
- const includePhone = Boolean(options?.includePhone);
34352
- switch (preset) {
34975
+ function getGlobalFormFields(form) {
34976
+ const includePhone = form.includeOptionalPhone;
34977
+ switch (form.fields.preset) {
34353
34978
  case "emailOnly": return withOptionalPhone([{
34354
34979
  id: "email",
34355
34980
  type: "email",
@@ -34367,7 +34992,7 @@ function getPresetFields(preset, options) {
34367
34992
  id: "name",
34368
34993
  type: "text",
34369
34994
  label: "Name",
34370
- required: false,
34995
+ required: form.fields.name === "required",
34371
34996
  placeholder: "Your name"
34372
34997
  }], includePhone);
34373
34998
  case "emailFirstLast": return withOptionalPhone([
@@ -34382,14 +35007,14 @@ function getPresetFields(preset, options) {
34382
35007
  id: "firstName",
34383
35008
  type: "text",
34384
35009
  label: "First Name",
34385
- required: false,
35010
+ required: form.fields.firstName === "required",
34386
35011
  placeholder: "First name"
34387
35012
  },
34388
35013
  {
34389
35014
  id: "lastName",
34390
35015
  type: "text",
34391
35016
  label: "Last Name",
34392
- required: false,
35017
+ required: form.fields.lastName === "required",
34393
35018
  placeholder: "Last name"
34394
35019
  }
34395
35020
  ], includePhone);
@@ -34405,14 +35030,14 @@ function getPresetFields(preset, options) {
34405
35030
  id: "name",
34406
35031
  type: "text",
34407
35032
  label: "Name",
34408
- required: false,
35033
+ required: form.fields.name === "required",
34409
35034
  placeholder: "Your name"
34410
35035
  },
34411
35036
  {
34412
35037
  id: "company",
34413
35038
  type: "text",
34414
35039
  label: "Company",
34415
- required: false,
35040
+ required: form.fields.company === "required",
34416
35041
  placeholder: "Company name"
34417
35042
  }
34418
35043
  ], includePhone);
@@ -34431,14 +35056,29 @@ function withOptionalPhone(fields, includePhone) {
34431
35056
  }
34432
35057
  //#endregion
34433
35058
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.interactive.ts
34434
- const newsletterPresetSchema = z.enum([
34435
- "emailOnly",
34436
- "emailName",
34437
- "emailFirstLast",
34438
- "emailNameCompany"
34439
- ]);
34440
- const newsletterDestinationProviderSchema = z.enum(["riverbank", "mailchimp"]);
34441
35059
  const newsletterButtonVariantSchema = blockButtonRoleSchema;
35060
+ const mailingListContextSchema = z.unknown().transform((value, ctx) => {
35061
+ const parsed = parseMailingListRuntimeContext(value);
35062
+ if (!parsed.ok) {
35063
+ ctx.addIssue({
35064
+ code: z.ZodIssueCode.custom,
35065
+ message: "Invalid mailing-list runtime context"
35066
+ });
35067
+ return z.NEVER;
35068
+ }
35069
+ return parsed.value;
35070
+ });
35071
+ const mailingListTargetingSchema = z.unknown().transform((value, ctx) => {
35072
+ const parsed = parseMailingListPlacementTargeting(value);
35073
+ if (!parsed.ok) {
35074
+ ctx.addIssue({
35075
+ code: z.ZodIssueCode.custom,
35076
+ message: "Invalid mailing-list targeting"
35077
+ });
35078
+ return z.NEVER;
35079
+ }
35080
+ return parsed.value;
35081
+ });
34442
35082
  const newsletterFieldSchema = z.object({
34443
35083
  id: z.string(),
34444
35084
  type: z.enum(["email", "text"]),
@@ -34446,73 +35086,73 @@ const newsletterFieldSchema = z.object({
34446
35086
  required: z.boolean(),
34447
35087
  placeholder: z.string().optional()
34448
35088
  });
34449
- const newsletterSignupBlockIdSchema = z.string().trim().min(1).transform(parseRequiredNewsletterSignupBlockId);
34450
- const signedNewsletterDestinationRefSchema = z.string().trim().min(1).transform(parseRequiredSignedNewsletterDestinationRef);
35089
+ const mailingListSignupBlockIdSchema = z.string().trim().min(1).transform(parseRequiredMailingListSignupBlockId);
35090
+ const signedMailingListSourceTokenSchema = z.string().trim().min(1).transform(parseRequiredMailingListSignedSourceToken);
34451
35091
  const newsletterSourceCommonSchema = z.object({
34452
35092
  siteId: z.string().optional(),
34453
35093
  apiBaseUrl: z.string().optional(),
34454
- preset: newsletterPresetSchema.optional(),
34455
- includePhone: z.boolean().optional(),
34456
- buttonLabel: z.string().nullable().optional(),
35094
+ mailingList: mailingListContextSchema.optional(),
35095
+ targeting: mailingListTargetingSchema.optional(),
34457
35096
  buttonVariant: newsletterButtonVariantSchema.nullable().optional(),
34458
- successMessage: z.string().nullable().optional(),
34459
- privacyNote: z.string().nullable().optional(),
34460
- tags: z.string().nullable().optional(),
34461
35097
  spamProtectionEnabled: z.boolean().optional(),
34462
35098
  className: z.string().nullable().optional()
34463
35099
  });
34464
35100
  const newsletterSourceSchema = z.discriminatedUnion("source", [
34465
35101
  newsletterSourceCommonSchema.extend({
34466
35102
  source: z.literal("block"),
34467
- blockId: z.string().nullable().optional(),
34468
- listId: z.string().nullable().optional(),
34469
- destinationProvider: newsletterDestinationProviderSchema.nullable().optional()
35103
+ blockId: z.string().nullable().optional()
34470
35104
  }),
34471
35105
  newsletterSourceCommonSchema.extend({
34472
35106
  source: z.literal("signed-footer"),
34473
- destinationRef: z.object({
34474
- tag: z.literal("signed_destination_ref"),
34475
- token: signedNewsletterDestinationRefSchema
35107
+ sourceRef: z.object({
35108
+ tag: z.literal("signed_source_ref"),
35109
+ token: signedMailingListSourceTokenSchema
34476
35110
  })
34477
35111
  }),
34478
- newsletterSourceCommonSchema.extend({
34479
- source: z.literal("legacy-direct-list"),
34480
- listId: z.string().nullable()
34481
- })
35112
+ newsletterSourceCommonSchema.extend({ source: z.literal("preview") })
34482
35113
  ]);
34483
35114
  const newsletterDisplaySchema = z.object({
34484
35115
  kind: z.literal("newsletter-form"),
35116
+ availability: z.enum(["ready", "unavailable"]),
34485
35117
  className: z.string().nullable(),
34486
35118
  buttonLabel: z.string(),
34487
35119
  buttonVariant: newsletterButtonVariantSchema,
34488
- successMessage: z.string().nullable(),
34489
35120
  privacyNote: z.string().nullable(),
34490
- fields: z.array(newsletterFieldSchema)
35121
+ fields: z.array(newsletterFieldSchema),
35122
+ choices: z.array(z.object({
35123
+ id: z.string(),
35124
+ label: z.string()
35125
+ }))
34491
35126
  });
34492
35127
  const newsletterHydrationSchema = z.object({
34493
35128
  target: z.discriminatedUnion("tag", [
34494
35129
  z.object({
34495
35130
  tag: z.literal("block-source"),
34496
- destinationRef: z.object({
35131
+ sourceRef: z.object({
34497
35132
  tag: z.literal("block_ref"),
34498
- blockId: newsletterSignupBlockIdSchema
35133
+ blockId: mailingListSignupBlockIdSchema
34499
35134
  }).nullable()
34500
35135
  }),
34501
35136
  z.object({
34502
35137
  tag: z.literal("signed-footer-source"),
34503
- destinationRef: z.object({
34504
- tag: z.literal("signed_destination_ref"),
34505
- token: signedNewsletterDestinationRefSchema
35138
+ sourceRef: z.object({
35139
+ tag: z.literal("signed_source_ref"),
35140
+ token: signedMailingListSourceTokenSchema
34506
35141
  })
34507
35142
  }),
34508
- z.object({
34509
- tag: z.literal("legacy-direct-list"),
34510
- listId: z.string().nullable()
34511
- })
35143
+ z.object({ tag: z.literal("preview") })
35144
+ ]),
35145
+ setupRevision: z.number().int().positive().nullable(),
35146
+ choiceProvider: z.enum([
35147
+ "native",
35148
+ "brevo",
35149
+ "mailchimp"
35150
+ ]).nullable(),
35151
+ choicePresentation: z.enum([
35152
+ "hidden",
35153
+ "automatic",
35154
+ "visitor_choice"
34512
35155
  ]),
34513
- preset: newsletterPresetSchema,
34514
- includePhone: z.boolean(),
34515
- tags: z.string().nullable().optional(),
34516
35156
  spamProtectionEnabled: z.boolean().optional()
34517
35157
  });
34518
35158
  const newsletterRenderSchema = z.object({
@@ -34524,16 +35164,24 @@ z.object({
34524
35164
  apiBaseUrl: z.string().optional()
34525
35165
  });
34526
35166
  function buildNewsletterDisplay(source) {
34527
- const preset = source.preset ?? "emailOnly";
34528
- const includePhone = source.includePhone ?? false;
35167
+ const context = source.mailingList;
35168
+ const form = context?.tag === "configured" ? context.form : {
35169
+ fields: { preset: "emailOnly" },
35170
+ includeOptionalPhone: false,
35171
+ buttonLabel: "Subscribe",
35172
+ privacyNote: null
35173
+ };
35174
+ const targeting = source.targeting ?? { mode: "default" };
35175
+ const choices = resolveRuntimeChoices(context, targeting);
34529
35176
  return {
34530
35177
  kind: "newsletter-form",
35178
+ availability: runtimeTargetingIsAvailable(context, targeting, choices) ? "ready" : "unavailable",
34531
35179
  className: source.className ?? null,
34532
- buttonLabel: source.buttonLabel?.trim() || "Subscribe",
35180
+ buttonLabel: form.buttonLabel,
34533
35181
  buttonVariant: source.buttonVariant ?? "primary",
34534
- successMessage: source.successMessage ?? "Thanks for subscribing!",
34535
- privacyNote: source.privacyNote ?? null,
34536
- fields: getPresetFields(preset, { includePhone })
35182
+ privacyNote: form.privacyNote,
35183
+ fields: getGlobalFormFields(form),
35184
+ choices
34537
35185
  };
34538
35186
  }
34539
35187
  const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
@@ -34543,14 +35191,15 @@ const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
34543
35191
  render: createZodCodec(newsletterRenderSchema)
34544
35192
  },
34545
35193
  buildRender: (source) => {
34546
- const runtimeSource = source.source === "block" ? deriveNewsletterSignupRuntimeSource(source) : null;
35194
+ const targeting = source.targeting ?? { mode: "default" };
35195
+ const presentation = planMailingListChoicePresentation(targeting);
34547
35196
  return {
34548
35197
  display: buildNewsletterDisplay(source),
34549
35198
  hydration: {
34550
35199
  target: projectNewsletterFormHydrationTarget(source),
34551
- preset: source.preset ?? "emailOnly",
34552
- includePhone: source.includePhone ?? false,
34553
- tags: runtimeSource ? runtimeSource.tags : source.tags,
35200
+ setupRevision: source.mailingList?.tag === "configured" ? source.mailingList.revision : null,
35201
+ choiceProvider: targeting.mode === "custom" ? targeting.provider : null,
35202
+ choicePresentation: presentation.tag,
34554
35203
  spamProtectionEnabled: source.spamProtectionEnabled
34555
35204
  }
34556
35205
  };
@@ -34559,35 +35208,87 @@ const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
34559
35208
  function projectNewsletterFormHydrationTarget(source) {
34560
35209
  switch (source.source) {
34561
35210
  case "block": {
34562
- const destinationRef = buildNewsletterSignupBlockRef(source.blockId);
34563
- if (destinationRef?.tag === "block_ref") return {
35211
+ const sourceRef = buildMailingListSignupBlockSourceRef(source.blockId);
35212
+ if (sourceRef?.tag === "block_ref") return {
34564
35213
  tag: "block-source",
34565
- destinationRef
35214
+ sourceRef
34566
35215
  };
34567
35216
  return {
34568
- tag: "legacy-direct-list",
34569
- listId: source.listId ?? null
35217
+ tag: "block-source",
35218
+ sourceRef: null
34570
35219
  };
34571
35220
  }
34572
35221
  case "signed-footer": return {
34573
35222
  tag: "signed-footer-source",
34574
- destinationRef: source.destinationRef
34575
- };
34576
- case "legacy-direct-list": return {
34577
- tag: "legacy-direct-list",
34578
- listId: source.listId
35223
+ sourceRef: source.sourceRef
34579
35224
  };
35225
+ case "preview": return { tag: "preview" };
34580
35226
  default: return assertNever$2(source);
34581
35227
  }
34582
35228
  }
34583
- function parseRequiredNewsletterSignupBlockId(value) {
34584
- const parsed = parseNewsletterSignupBlockId(value);
34585
- if (!parsed) throw new Error("Newsletter signup block destination ref requires blockId");
35229
+ function resolveRuntimeChoices(context, targeting) {
35230
+ if (context?.tag !== "configured" || targeting.mode !== "custom" || targeting.selection.tag !== "visitor_choice" || context.provider.provider !== targeting.provider) return [];
35231
+ switch (targeting.provider) {
35232
+ case "native": {
35233
+ const offered = new Set(targeting.selection.offeredTopicIds);
35234
+ return context.provider.provider === "native" ? context.provider.topics.filter((item) => offered.has(item.id)).map((item) => ({
35235
+ id: item.id,
35236
+ label: item.name
35237
+ })) : [];
35238
+ }
35239
+ case "brevo": {
35240
+ const offered = new Set(targeting.selection.offeredListIds);
35241
+ return context.provider.provider === "brevo" ? context.provider.lists.filter((item) => offered.has(item.id)).map((item) => ({
35242
+ id: String(item.id),
35243
+ label: item.name
35244
+ })) : [];
35245
+ }
35246
+ case "mailchimp": {
35247
+ const offered = new Set(targeting.selection.offeredInterestIds);
35248
+ 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) => ({
35249
+ id: item.id,
35250
+ label: item.name
35251
+ })) ?? [];
35252
+ }
35253
+ }
35254
+ }
35255
+ function runtimeTargetingIsAvailable(context, targeting, choices) {
35256
+ if (context?.tag !== "configured") return false;
35257
+ const provider = context.provider;
35258
+ if (targeting.mode === "default") switch (provider.provider) {
35259
+ case "native": return provider.topics.some((item) => item.id === provider.defaultTopicId);
35260
+ case "brevo": return provider.lists.some((item) => item.id === provider.defaultListId);
35261
+ case "mailchimp": return provider.audiences.some((item) => item.id === provider.defaultAudienceId);
35262
+ }
35263
+ if (targeting.provider !== provider.provider) return false;
35264
+ switch (targeting.provider) {
35265
+ case "native": return provider.provider === "native" && nativeTargetIsAvailable(provider, targeting, choices);
35266
+ case "brevo": return provider.provider === "brevo" && brevoTargetIsAvailable(provider, targeting, choices);
35267
+ case "mailchimp": return provider.provider === "mailchimp" && mailchimpTargetIsAvailable(provider, targeting, choices);
35268
+ }
35269
+ }
35270
+ function nativeTargetIsAvailable(provider, targeting, choices) {
35271
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
35272
+ return ids.every((id) => provider.topics.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
35273
+ }
35274
+ function brevoTargetIsAvailable(provider, targeting, choices) {
35275
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
35276
+ return ids.every((id) => provider.lists.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
35277
+ }
35278
+ function mailchimpTargetIsAvailable(provider, targeting, choices) {
35279
+ const audience = provider.audiences.find((item) => item.id === targeting.audienceId);
35280
+ if (!audience) return false;
35281
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
35282
+ return ids.every((id) => audience.interests.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
35283
+ }
35284
+ function parseRequiredMailingListSignupBlockId(value) {
35285
+ const parsed = parseMailingListBlockId(value);
35286
+ if (!parsed) throw new Error("Mailing-list signup block source ref requires blockId");
34586
35287
  return parsed;
34587
35288
  }
34588
- function parseRequiredSignedNewsletterDestinationRef(value) {
34589
- const parsed = parseSignedNewsletterDestinationRef(value);
34590
- if (!parsed) throw new Error("Newsletter signup signed destination ref requires token");
35289
+ function parseRequiredMailingListSignedSourceToken(value) {
35290
+ const parsed = parseMailingListSignedSourceToken(value);
35291
+ if (!parsed) throw new Error("Mailing-list signup signed source ref requires token");
34591
35292
  return parsed;
34592
35293
  }
34593
35294
  //#endregion
@@ -35960,6 +36661,14 @@ const systemBlockDefinitions = [
35960
36661
  weight: "standard"
35961
36662
  })]
35962
36663
  })),
36664
+ withDesignCapability("block.appointment-services", appointmentServicesBlockDefinition, sectionOnly(), defineBlockDesignMetadata({
36665
+ flow: conversionFlow,
36666
+ layoutProfiles: [layoutProfile({
36667
+ variantKey: "default",
36668
+ family: "stack",
36669
+ weight: "standard"
36670
+ })]
36671
+ })),
35963
36672
  withDesignCapability("block.weekly-slot-availability", weeklySlotAvailabilityBlockDefinition, sectionOnly(), defineBlockDesignMetadata({
35964
36673
  flow: offeringsFlow,
35965
36674
  layoutProfiles: [layoutProfile({
@@ -37858,6 +38567,7 @@ const SYSTEM_BLOCK_KINDS = [
37858
38567
  "block.form",
37859
38568
  "block.contact-newsletter",
37860
38569
  "block.appointment-booking",
38570
+ "block.appointment-services",
37861
38571
  "block.weekly-slot-availability",
37862
38572
  "block.event-registration",
37863
38573
  "block.event-details",
@@ -38127,18 +38837,13 @@ const navigationItemConfigSchema = z.object({
38127
38837
  isCta: z.boolean().optional()
38128
38838
  }).extend({ children: z.lazy(() => z.array(navigationItemConfigSchema)).optional() });
38129
38839
  const navigationMenuConfigSchema = z.object({
38130
- identifier: z.string().min(1, "Identifier is required"),
38131
- name: z.string().min(1, "Name is required"),
38132
- isPrimary: z.boolean().optional(),
38133
- isFooter: z.boolean().optional(),
38134
- footerRole: z.enum(["main", "legal-policy"]).nullable().optional(),
38840
+ location: z.enum([
38841
+ "main",
38842
+ "footer",
38843
+ "legal"
38844
+ ]),
38135
38845
  items: z.array(navigationItemConfigSchema)
38136
- }).superRefine((menu, ctx) => {
38137
- if (menu.footerRole !== void 0 && menu.isFooter !== void 0 && menu.isFooter !== (menu.footerRole === "main")) ctx.addIssue({
38138
- code: "custom",
38139
- message: "footerRole and isFooter disagree",
38140
- path: ["footerRole"]
38141
- });
38846
+ }).strict().superRefine((menu, ctx) => {
38142
38847
  const portalStats = collectPortalLinkStats(menu.items);
38143
38848
  if (portalStats.count > 1) ctx.addIssue({
38144
38849
  code: z.ZodIssueCode.custom,
@@ -38225,11 +38930,11 @@ const contentConfigSchema = z.object({
38225
38930
  });
38226
38931
  }
38227
38932
  if (data.navigationMenus && data.navigationMenus.length > 1) {
38228
- const identifiers = data.navigationMenus.map((m) => m.identifier);
38229
- const duplicateIds = identifiers.filter((id, i) => identifiers.indexOf(id) !== i);
38230
- if (duplicateIds.length > 0) ctx.addIssue({
38933
+ const locations = data.navigationMenus.map((m) => m.location);
38934
+ const duplicateLocations = locations.filter((location, i) => locations.indexOf(location) !== i);
38935
+ if (duplicateLocations.length > 0) ctx.addIssue({
38231
38936
  code: z.ZodIssueCode.custom,
38232
- message: `Navigation menu identifiers must be unique. Duplicates: ${duplicateIds.join(", ")}`,
38937
+ message: `Navigation locations must be unique. Duplicates: ${duplicateLocations.join(", ")}`,
38233
38938
  path: ["navigationMenus"]
38234
38939
  });
38235
38940
  }