@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
@@ -5404,7 +5404,7 @@ const boxBackgroundSchema = z.discriminatedUnion("type", [
5404
5404
  })
5405
5405
  ]);
5406
5406
  function normalizeAuthoringOpacity(value) {
5407
- if (!isRecord$6(value)) return value;
5407
+ if (!isRecord$9(value)) return value;
5408
5408
  return {
5409
5409
  ...value,
5410
5410
  opacity: normalizeOpacity(value.opacity)
@@ -5413,7 +5413,7 @@ function normalizeAuthoringOpacity(value) {
5413
5413
  function normalizeOpacity(value) {
5414
5414
  return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
5415
5415
  }
5416
- function isRecord$6(value) {
5416
+ function isRecord$9(value) {
5417
5417
  return typeof value === "object" && value !== null && !Array.isArray(value);
5418
5418
  }
5419
5419
  //#endregion
@@ -10973,12 +10973,12 @@ function err(error) {
10973
10973
  }
10974
10974
  //#endregion
10975
10975
  //#region ../core/src/branded.ts
10976
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10976
+ 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;
10977
10977
  function parseUuidBrand(value, unsafeConstructor) {
10978
10978
  if (typeof value !== "string") return err({ kind: "not_string" });
10979
10979
  const normalized = value.trim().toLowerCase();
10980
10980
  if (normalized.length === 0) return err({ kind: "empty_string" });
10981
- if (!UUID_PATTERN.test(normalized)) return err({ kind: "invalid_uuid" });
10981
+ if (!UUID_PATTERN$1.test(normalized)) return err({ kind: "invalid_uuid" });
10982
10982
  return ok(unsafeConstructor(normalized));
10983
10983
  }
10984
10984
  const unsafeAsEventOfferingId = (id) => id;
@@ -11217,7 +11217,7 @@ function parseEventPageSurfaceDestinationMode(value) {
11217
11217
  return value === "cms-managed" || value === "system-managed" ? value : void 0;
11218
11218
  }
11219
11219
  function parseEventPageSurfaceSubject(value) {
11220
- if (!isRecord$5(value)) return err({ kind: "notObject" });
11220
+ if (!isRecord$8(value)) return err({ kind: "notObject" });
11221
11221
  switch (value.kind) {
11222
11222
  case "eventSeries": {
11223
11223
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
@@ -11272,7 +11272,7 @@ function parseEventSeriesIdList(values) {
11272
11272
  }
11273
11273
  return ok(parsed);
11274
11274
  }
11275
- function isRecord$5(value) {
11275
+ function isRecord$8(value) {
11276
11276
  return typeof value === "object" && value !== null && !Array.isArray(value);
11277
11277
  }
11278
11278
  //#endregion
@@ -11442,9 +11442,379 @@ function toLocationCoordinates(value) {
11442
11442
  };
11443
11443
  }
11444
11444
  //#endregion
11445
- //#region ../core/src/newsletter-signup-destination.ts
11446
- function parseNewsletterSignupDestinationProvider(value) {
11447
- return value === "riverbank" || value === "mailchimp" ? value : null;
11445
+ //#region ../core/src/mailing-list-identifiers.ts
11446
+ 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;
11447
+ const OPAQUE_ID_PATTERN = /^[A-Za-z0-9_-]{1,100}$/u;
11448
+ function parseTrimmedMatchingString(value, pattern) {
11449
+ if (typeof value !== "string") return null;
11450
+ const trimmed = value.trim();
11451
+ return pattern.test(trimmed) ? trimmed : null;
11452
+ }
11453
+ function parsePositiveInteger(value) {
11454
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
11455
+ }
11456
+ function parseNewsletterListId(value) {
11457
+ return parseTrimmedMatchingString(value, UUID_PATTERN);
11458
+ }
11459
+ function parseMailchimpAudienceId(value) {
11460
+ return parseTrimmedMatchingString(value, OPAQUE_ID_PATTERN);
11461
+ }
11462
+ function parseMailchimpInterestId(value) {
11463
+ return parseTrimmedMatchingString(value, OPAQUE_ID_PATTERN);
11464
+ }
11465
+ function parseBrevoListId(value) {
11466
+ return parsePositiveInteger(value);
11467
+ }
11468
+ //#endregion
11469
+ //#region ../core/src/mailing-list-setup-parser.ts
11470
+ function parseMailingListGlobalForm(value) {
11471
+ if (!isRecord$7(value)) return formError("shape");
11472
+ const fields = parseGlobalFields(value.fields);
11473
+ if (fields === null) return formError("fields");
11474
+ if (typeof value.includeOptionalPhone !== "boolean") return formError("includeOptionalPhone");
11475
+ const buttonLabel = parseRequiredString(value.buttonLabel);
11476
+ if (buttonLabel === null) return formError("buttonLabel");
11477
+ const privacyNote = parseOptionalString(value.privacyNote);
11478
+ if (privacyNote === void 0) return formError("privacyNote");
11479
+ return ok({
11480
+ fields,
11481
+ includeOptionalPhone: value.includeOptionalPhone,
11482
+ buttonLabel,
11483
+ privacyNote
11484
+ });
11485
+ }
11486
+ function parseGlobalFields(value) {
11487
+ if (!isRecord$7(value) || typeof value.preset !== "string") return null;
11488
+ switch (value.preset) {
11489
+ case "emailOnly": return { preset: "emailOnly" };
11490
+ case "emailName": return isRequirement(value.name) ? {
11491
+ preset: "emailName",
11492
+ name: value.name
11493
+ } : null;
11494
+ case "emailFirstLast": return isRequirement(value.firstName) && isRequirement(value.lastName) ? {
11495
+ preset: "emailFirstLast",
11496
+ firstName: value.firstName,
11497
+ lastName: value.lastName
11498
+ } : null;
11499
+ case "emailNameCompany": return isRequirement(value.name) && isRequirement(value.company) ? {
11500
+ preset: "emailNameCompany",
11501
+ name: value.name,
11502
+ company: value.company
11503
+ } : null;
11504
+ default: return null;
11505
+ }
11506
+ }
11507
+ function parseMailingListSetupRevision(value) {
11508
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
11509
+ }
11510
+ function parseRequiredString(value) {
11511
+ if (typeof value !== "string") return null;
11512
+ const trimmed = value.trim();
11513
+ return trimmed.length > 0 ? trimmed : null;
11514
+ }
11515
+ function parseOptionalString(value) {
11516
+ if (value === null || value === void 0) return null;
11517
+ if (typeof value !== "string") return void 0;
11518
+ const trimmed = value.trim();
11519
+ return trimmed.length > 0 ? trimmed : null;
11520
+ }
11521
+ function isRequirement(value) {
11522
+ return value === "required" || value === "optional";
11523
+ }
11524
+ function isRecord$7(value) {
11525
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11526
+ }
11527
+ function formError(field) {
11528
+ return err({
11529
+ tag: "invalid_mailing_list_global_form",
11530
+ field
11531
+ });
11532
+ }
11533
+ //#endregion
11534
+ //#region ../core/src/mailing-list-targeting.ts
11535
+ function planMailingListChoicePresentation(targeting) {
11536
+ if (targeting.mode === "default" || targeting.selection.tag === "fixed") return { tag: "hidden" };
11537
+ switch (targeting.provider) {
11538
+ case "native": return targeting.selection.offeredTopicIds.length === 1 ? {
11539
+ tag: "automatic",
11540
+ selection: {
11541
+ provider: "native",
11542
+ topicIds: targeting.selection.offeredTopicIds
11543
+ }
11544
+ } : {
11545
+ tag: "visitor_choice",
11546
+ provider: "native",
11547
+ optionCount: targeting.selection.offeredTopicIds.length
11548
+ };
11549
+ case "brevo": return targeting.selection.offeredListIds.length === 1 ? {
11550
+ tag: "automatic",
11551
+ selection: {
11552
+ provider: "brevo",
11553
+ listIds: targeting.selection.offeredListIds
11554
+ }
11555
+ } : {
11556
+ tag: "visitor_choice",
11557
+ provider: "brevo",
11558
+ optionCount: targeting.selection.offeredListIds.length
11559
+ };
11560
+ case "mailchimp": return targeting.selection.offeredInterestIds.length === 1 ? {
11561
+ tag: "automatic",
11562
+ selection: {
11563
+ provider: "mailchimp",
11564
+ interestIds: targeting.selection.offeredInterestIds
11565
+ }
11566
+ } : {
11567
+ tag: "visitor_choice",
11568
+ provider: "mailchimp",
11569
+ optionCount: targeting.selection.offeredInterestIds.length
11570
+ };
11571
+ }
11572
+ }
11573
+ //#endregion
11574
+ //#region ../core/src/mailing-list-runtime.ts
11575
+ function parseMailingListRuntimeContext(value) {
11576
+ if (!isRecord$6(value)) return invalidRuntimeContext();
11577
+ if (value.tag === "not_configured") return ok({ tag: "not_configured" });
11578
+ if (value.tag !== "configured") return invalidRuntimeContext();
11579
+ const revision = parseMailingListSetupRevision(value.revision);
11580
+ const form = parseMailingListGlobalForm(value.form);
11581
+ const provider = parseRuntimeProvider(value.provider);
11582
+ if (revision === null || !form.ok || provider === null) return invalidRuntimeContext();
11583
+ return ok({
11584
+ tag: "configured",
11585
+ revision,
11586
+ form: form.value,
11587
+ provider
11588
+ });
11589
+ }
11590
+ function parseRuntimeProvider(value) {
11591
+ if (!isRecord$6(value)) return null;
11592
+ switch (value.provider) {
11593
+ case "native": return parseNativeRuntimeProvider(value);
11594
+ case "brevo": return parseBrevoRuntimeProvider(value);
11595
+ case "mailchimp": return parseMailchimpRuntimeProvider(value);
11596
+ default: return null;
11597
+ }
11598
+ }
11599
+ function parseNativeRuntimeProvider(value) {
11600
+ const defaultTopicId = parseNewsletterListId(value.defaultTopicId);
11601
+ const topics = parseNamedItems(value.topics, parseNewsletterListId);
11602
+ return defaultTopicId !== null && topics !== null ? {
11603
+ provider: "native",
11604
+ defaultTopicId,
11605
+ topics
11606
+ } : null;
11607
+ }
11608
+ function parseBrevoRuntimeProvider(value) {
11609
+ const defaultListId = parseBrevoListId(value.defaultListId);
11610
+ const lists = parseNamedItems(value.lists, parseBrevoListId);
11611
+ return defaultListId !== null && lists !== null ? {
11612
+ provider: "brevo",
11613
+ defaultListId,
11614
+ lists
11615
+ } : null;
11616
+ }
11617
+ function parseMailchimpRuntimeProvider(value) {
11618
+ if (!Array.isArray(value.audiences)) return null;
11619
+ const defaultAudienceId = parseMailchimpAudienceId(value.defaultAudienceId);
11620
+ if (defaultAudienceId === null) return null;
11621
+ const audiences = [];
11622
+ for (const audience of value.audiences) {
11623
+ if (!isRecord$6(audience)) return null;
11624
+ const id = parseMailchimpAudienceId(audience.id);
11625
+ const name = parseName(audience.name);
11626
+ if (id === null || name === null || !Array.isArray(audience.interests)) return null;
11627
+ const interests = parseMailchimpInterests(audience.interests);
11628
+ if (interests === null) return null;
11629
+ audiences.push({
11630
+ id,
11631
+ name,
11632
+ interests
11633
+ });
11634
+ }
11635
+ return {
11636
+ provider: "mailchimp",
11637
+ defaultAudienceId,
11638
+ audiences
11639
+ };
11640
+ }
11641
+ function parseMailchimpInterests(value) {
11642
+ const interests = [];
11643
+ for (const interest of value) {
11644
+ if (!isRecord$6(interest)) return null;
11645
+ const id = parseMailchimpInterestId(interest.id);
11646
+ const name = parseName(interest.name);
11647
+ const categoryName = parseName(interest.categoryName);
11648
+ if (id === null || name === null || categoryName === null) return null;
11649
+ interests.push({
11650
+ id,
11651
+ name,
11652
+ categoryName
11653
+ });
11654
+ }
11655
+ return interests;
11656
+ }
11657
+ function parseNamedItems(value, parseId) {
11658
+ if (!Array.isArray(value)) return null;
11659
+ const items = [];
11660
+ for (const item of value) {
11661
+ if (!isRecord$6(item)) return null;
11662
+ const id = parseId(item.id);
11663
+ const name = parseName(item.name);
11664
+ if (id === null || name === null) return null;
11665
+ items.push({
11666
+ id,
11667
+ name
11668
+ });
11669
+ }
11670
+ return items;
11671
+ }
11672
+ function parseName(value) {
11673
+ if (typeof value !== "string") return null;
11674
+ const name = value.trim();
11675
+ return name.length > 0 ? name : null;
11676
+ }
11677
+ function isRecord$6(value) {
11678
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11679
+ }
11680
+ function invalidRuntimeContext() {
11681
+ return err({ tag: "invalid_mailing_list_runtime_context" });
11682
+ }
11683
+ //#endregion
11684
+ //#region ../core/src/mailing-list-targeting-parser.ts
11685
+ function parseMailingListPlacementTargeting(value) {
11686
+ if (!isRecord$5(value)) return targetingError("shape");
11687
+ if (value.mode === "default") return ok({ mode: "default" });
11688
+ if (value.mode !== "custom") return targetingError("mode");
11689
+ if (!isRecord$5(value.selection)) return targetingError("selection");
11690
+ switch (value.provider) {
11691
+ case "native": return parseNativeTargeting(value.selection);
11692
+ case "brevo": return parseBrevoTargeting(value.selection);
11693
+ case "mailchimp": return parseMailchimpTargeting(value, value.selection);
11694
+ default: return targetingError("provider");
11695
+ }
11696
+ }
11697
+ function parseNativeTargeting(selection) {
11698
+ if (selection.tag === "fixed") {
11699
+ const topicIds = parseNonEmptyUniqueIds(selection.topicIds, parseNewsletterListId);
11700
+ return topicIds.ok ? ok({
11701
+ mode: "custom",
11702
+ provider: "native",
11703
+ selection: {
11704
+ tag: "fixed",
11705
+ topicIds: topicIds.value
11706
+ }
11707
+ }) : topicIds;
11708
+ }
11709
+ if (selection.tag === "visitor_choice") {
11710
+ const offeredTopicIds = parseNonEmptyUniqueIds(selection.offeredTopicIds, parseNewsletterListId);
11711
+ return offeredTopicIds.ok ? ok({
11712
+ mode: "custom",
11713
+ provider: "native",
11714
+ selection: {
11715
+ tag: "visitor_choice",
11716
+ offeredTopicIds: offeredTopicIds.value
11717
+ }
11718
+ }) : offeredTopicIds;
11719
+ }
11720
+ return targetingError("selection");
11721
+ }
11722
+ function parseBrevoTargeting(selection) {
11723
+ if (selection.tag === "fixed") {
11724
+ const listIds = parseNonEmptyUniqueIds(selection.listIds, parseBrevoListId);
11725
+ return listIds.ok ? ok({
11726
+ mode: "custom",
11727
+ provider: "brevo",
11728
+ selection: {
11729
+ tag: "fixed",
11730
+ listIds: listIds.value
11731
+ }
11732
+ }) : listIds;
11733
+ }
11734
+ if (selection.tag === "visitor_choice") {
11735
+ const offeredListIds = parseNonEmptyUniqueIds(selection.offeredListIds, parseBrevoListId);
11736
+ return offeredListIds.ok ? ok({
11737
+ mode: "custom",
11738
+ provider: "brevo",
11739
+ selection: {
11740
+ tag: "visitor_choice",
11741
+ offeredListIds: offeredListIds.value
11742
+ }
11743
+ }) : offeredListIds;
11744
+ }
11745
+ return targetingError("selection");
11746
+ }
11747
+ function parseMailchimpTargeting(value, selection) {
11748
+ const audienceId = parseMailchimpAudienceId(value.audienceId);
11749
+ if (audienceId === null) return targetingError("audienceId");
11750
+ if (selection.tag === "fixed") {
11751
+ const interestIds = parseUniqueIds(selection.interestIds, parseMailchimpInterestId);
11752
+ return interestIds.ok ? ok({
11753
+ mode: "custom",
11754
+ provider: "mailchimp",
11755
+ audienceId,
11756
+ selection: {
11757
+ tag: "fixed",
11758
+ interestIds: interestIds.value
11759
+ }
11760
+ }) : interestIds;
11761
+ }
11762
+ if (selection.tag === "visitor_choice") {
11763
+ const offeredInterestIds = parseNonEmptyUniqueIds(selection.offeredInterestIds, parseMailchimpInterestId);
11764
+ return offeredInterestIds.ok ? ok({
11765
+ mode: "custom",
11766
+ provider: "mailchimp",
11767
+ audienceId,
11768
+ selection: {
11769
+ tag: "visitor_choice",
11770
+ offeredInterestIds: offeredInterestIds.value
11771
+ }
11772
+ }) : offeredInterestIds;
11773
+ }
11774
+ return targetingError("selection");
11775
+ }
11776
+ function parseNonEmptyUniqueIds(value, parseId) {
11777
+ const parsed = parseUniqueIds(value, parseId);
11778
+ if (!parsed.ok) return parsed;
11779
+ const [first, ...rest] = parsed.value;
11780
+ return first === void 0 ? targetingError("targets") : ok([first, ...rest]);
11781
+ }
11782
+ function parseUniqueIds(value, parseId) {
11783
+ if (!Array.isArray(value)) return targetingError("targets");
11784
+ const parsed = [];
11785
+ for (const item of value) {
11786
+ const id = parseId(item);
11787
+ if (id === null) return targetingError("targets");
11788
+ if (parsed.includes(id)) return targetingError("duplicate_target");
11789
+ parsed.push(id);
11790
+ }
11791
+ return ok(parsed);
11792
+ }
11793
+ function isRecord$5(value) {
11794
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11795
+ }
11796
+ function targetingError(field) {
11797
+ return err({
11798
+ tag: "invalid_mailing_list_targeting",
11799
+ field
11800
+ });
11801
+ }
11802
+ //#endregion
11803
+ //#region ../core/src/mailing-list-submission.ts
11804
+ function parseMailingListBlockId(value) {
11805
+ const parsed = typeof value === "string" ? value.trim() : "";
11806
+ 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;
11807
+ }
11808
+ function parseMailingListSignedSourceToken(value) {
11809
+ const parsed = typeof value === "string" ? value.trim() : "";
11810
+ return /^[A-Za-z0-9._~-]{16,1000}$/u.test(parsed) ? parsed : null;
11811
+ }
11812
+ function buildMailingListSignupBlockSourceRef(blockId) {
11813
+ const parsed = parseMailingListBlockId(blockId);
11814
+ return parsed !== null ? {
11815
+ tag: "block_ref",
11816
+ blockId: parsed
11817
+ } : null;
11448
11818
  }
11449
11819
  function sessionStartMs(session) {
11450
11820
  return Date.parse(session.startsAt);
@@ -16264,102 +16634,31 @@ const newsletterFormFragment = defineFragment({
16264
16634
  description: "Newsletter subscription form with preset field configurations.",
16265
16635
  fields: [
16266
16636
  {
16267
- id: "destinationProvider",
16268
- type: "select",
16269
- label: "Send signups to",
16270
- description: "Choose where new subscribers from this form should go.",
16271
- defaultValue: "riverbank",
16272
- required: false,
16273
- ui: { intent: "behavior" },
16274
- options: [{
16275
- value: "riverbank",
16276
- label: "Riverbank newsletter"
16277
- }]
16278
- },
16279
- {
16280
- id: "listId",
16281
- type: "reference",
16282
- label: "Newsletter list",
16283
- description: "Select the list new subscribers should be added to.",
16284
- required: false,
16285
- referenceKind: "newsletterList",
16286
- allowManualEntry: false,
16287
- ui: {
16288
- intent: "behavior",
16289
- visibleWhen: {
16290
- field: "destinationProvider",
16291
- equals: "riverbank"
16292
- }
16293
- }
16294
- },
16295
- {
16296
- id: "mailchimpAudienceId",
16297
- type: "reference",
16298
- label: "Mailchimp audience",
16299
- description: "Choose the Mailchimp audience for new subscribers.",
16300
- required: false,
16301
- referenceKind: "mailchimpAudience",
16302
- allowManualEntry: false,
16303
- ui: {
16304
- intent: "behavior",
16305
- visibleWhen: {
16306
- field: "destinationProvider",
16307
- equals: "mailchimp"
16308
- }
16309
- }
16310
- },
16311
- {
16312
- id: "mailchimpTags",
16313
- type: "text",
16314
- label: "Mailchimp tags",
16315
- description: "Optional tags to apply in Mailchimp, separated by commas or semicolons.",
16637
+ id: "targeting",
16638
+ type: "group",
16639
+ label: "Mailing-list targeting",
16640
+ description: "Choose what kind of emails people signing up here will receive.",
16641
+ defaultValue: { mode: "default" },
16642
+ required: true,
16316
16643
  ui: {
16317
16644
  intent: "behavior",
16318
- visibleWhen: {
16319
- field: "destinationProvider",
16320
- equals: "mailchimp"
16321
- }
16322
- }
16323
- },
16324
- {
16325
- id: "preset",
16326
- type: "select",
16327
- label: "Form fields",
16328
- description: "Choose which fields to collect.",
16329
- defaultValue: "emailOnly",
16330
- ui: { intent: "display" },
16331
- options: [
16332
- {
16333
- value: "emailOnly",
16334
- label: "Email only"
16335
- },
16336
- {
16337
- value: "emailName",
16338
- label: "Email + Name"
16339
- },
16340
- {
16341
- value: "emailFirstLast",
16342
- label: "Email + First/Last Name"
16343
- },
16344
- {
16345
- value: "emailNameCompany",
16346
- label: "Email + Name + Company (B2B)"
16347
- }
16348
- ]
16349
- },
16350
- {
16351
- id: "includePhone",
16352
- type: "boolean",
16353
- label: "Include phone field (optional)",
16354
- description: "Adds an optional phone field to the selected preset (never required).",
16355
- defaultValue: false,
16356
- ui: { intent: "display" }
16357
- },
16358
- {
16359
- id: "buttonLabel",
16360
- type: "text",
16361
- label: "Button label",
16362
- defaultValue: "Subscribe"
16645
+ widget: "mailingListTargeting"
16646
+ },
16647
+ schema: { fields: [{
16648
+ id: "mode",
16649
+ type: "select",
16650
+ label: "Mode",
16651
+ required: true,
16652
+ defaultValue: "default",
16653
+ options: [{
16654
+ value: "default",
16655
+ label: "Default"
16656
+ }, {
16657
+ value: "custom",
16658
+ label: "Custom"
16659
+ }],
16660
+ ui: { hidden: true }
16661
+ }] }
16363
16662
  },
16364
16663
  createButtonVariantField({
16365
16664
  id: "buttonVariant",
@@ -16367,46 +16666,42 @@ const newsletterFormFragment = defineFragment({
16367
16666
  defaultValue: "primary"
16368
16667
  }),
16369
16668
  {
16370
- id: "successMessage",
16371
- type: "text",
16372
- label: "Success message",
16373
- description: "Shown after successful subscription.",
16374
- defaultValue: "Thanks for subscribing!"
16375
- },
16376
- {
16377
- id: "privacyNote",
16669
+ id: "mailingListSource",
16378
16670
  type: "text",
16379
- label: "Privacy note",
16380
- description: "Optional small text below the form (e.g., privacy policy link)."
16671
+ label: "Mailing-list source",
16672
+ ui: {
16673
+ hidden: true,
16674
+ intent: "behavior"
16675
+ }
16381
16676
  },
16382
16677
  {
16383
- id: "tags",
16678
+ id: "mailingListSourceToken",
16384
16679
  type: "text",
16385
- label: "Tags (optional)",
16386
- description: "Optional tags to apply on subscribe (comma or semicolon separated).",
16680
+ label: "Mailing-list source token",
16387
16681
  ui: {
16388
- intent: "behavior",
16389
- visibleWhen: {
16390
- field: "destinationProvider",
16391
- equals: "riverbank"
16392
- }
16682
+ hidden: true,
16683
+ intent: "behavior"
16393
16684
  }
16394
16685
  }
16395
16686
  ],
16396
16687
  layout: [el("newsletter-form", withApiBindings({
16397
- source: "block",
16398
- destinationProvider: { $bind: { from: "destinationProvider" } },
16399
- listId: { $bind: { from: "listId" } },
16400
- preset: { $bind: { from: "preset" } },
16401
- includePhone: { $bind: { from: "includePhone" } },
16402
- buttonLabel: { $bind: { from: "buttonLabel" } },
16688
+ source: { $bind: {
16689
+ from: "mailingListSource",
16690
+ fallback: "block"
16691
+ } },
16692
+ sourceRef: {
16693
+ tag: "signed_source_ref",
16694
+ token: { $bind: { from: "mailingListSourceToken" } }
16695
+ },
16696
+ targeting: { $bind: {
16697
+ from: "targeting",
16698
+ fallback: { mode: "default" }
16699
+ } },
16700
+ mailingList: { $bind: { from: "$root.mailingList" } },
16403
16701
  buttonVariant: { $bind: {
16404
16702
  from: "buttonVariant",
16405
16703
  fallback: "primary"
16406
16704
  } },
16407
- successMessage: { $bind: { from: "successMessage" } },
16408
- privacyNote: { $bind: { from: "privacyNote" } },
16409
- tags: { $bind: { from: "tags" } },
16410
16705
  spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } }
16411
16706
  }), [])]
16412
16707
  });
@@ -18581,14 +18876,19 @@ const contactNewsletterLayout = styledSection({
18581
18876
  as: "h2",
18582
18877
  className: "newsletter-copy-title"
18583
18878
  }, when("newsletterTitle"), bind("newsletterTitle")), richText({ className: "newsletter-copy-subtitle rb-prose rb-max-w-none" }, when("newsletterSubtitle"), bind("newsletterSubtitle"))]), el("newsletter-form", withApiBindings({
18584
- source: "block",
18585
- listId: { $bind: { from: "listId" } },
18586
- preset: { $bind: { from: "preset" } },
18587
- includePhone: { $bind: { from: "includePhone" } },
18588
- buttonLabel: { $bind: { from: "newsletterButtonLabel" } },
18589
- successMessage: { $bind: { from: "newsletterSuccessMessage" } },
18590
- privacyNote: { $bind: { from: "newsletterPrivacyNote" } },
18591
- tags: { $bind: { from: "newsletterTags" } },
18879
+ source: { $bind: {
18880
+ from: "mailingListSource",
18881
+ fallback: "block"
18882
+ } },
18883
+ sourceRef: {
18884
+ tag: "signed_source_ref",
18885
+ token: { $bind: { from: "mailingListSourceToken" } }
18886
+ },
18887
+ targeting: { $bind: {
18888
+ from: "targeting",
18889
+ fallback: { mode: "default" }
18890
+ } },
18891
+ mailingList: { $bind: { from: "$root.mailingList" } },
18592
18892
  spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } }
18593
18893
  }), [])])])], { className: "contact-newsletter-container" }),
18594
18894
  baseClass: "",
@@ -18624,79 +18924,55 @@ const contactNewsletterBlockDefinition = {
18624
18924
  label: "Newsletter intro"
18625
18925
  },
18626
18926
  {
18627
- id: "listId",
18628
- type: "reference",
18629
- label: "Newsletter list",
18630
- description: "Select the list new subscribers should be added to.",
18927
+ id: "targeting",
18928
+ type: "group",
18929
+ label: "Mailing-list targeting",
18930
+ description: "Choose what kind of emails people signing up here will receive.",
18631
18931
  required: true,
18632
- referenceKind: "newsletterList",
18633
- allowManualEntry: false,
18634
- ui: { intent: "behavior" }
18635
- },
18636
- {
18637
- id: "preset",
18638
- type: "select",
18639
- label: "Newsletter fields",
18640
- defaultValue: "emailOnly",
18641
- ui: { intent: "display" },
18642
- options: [
18643
- {
18644
- value: "emailOnly",
18645
- label: "Email only"
18646
- },
18647
- {
18648
- value: "emailName",
18649
- label: "Email + Name"
18650
- },
18651
- {
18652
- value: "emailFirstLast",
18653
- label: "Email + First/Last Name"
18654
- },
18655
- {
18656
- value: "emailNameCompany",
18657
- label: "Email + Name + Company (B2B)"
18658
- }
18659
- ]
18660
- },
18661
- {
18662
- id: "includePhone",
18663
- type: "boolean",
18664
- label: "Include phone field",
18665
- defaultValue: false,
18666
- ui: { intent: "display" }
18667
- },
18668
- {
18669
- id: "newsletterButtonLabel",
18670
- type: "text",
18671
- label: "Newsletter button label",
18672
- defaultValue: "Subscribe",
18673
- ui: { intent: "display" }
18674
- },
18675
- {
18676
- id: "newsletterSuccessMessage",
18677
- type: "text",
18678
- label: "Newsletter success message",
18679
- defaultValue: "Thanks for subscribing!",
18680
- ui: { intent: "behavior" }
18932
+ defaultValue: { mode: "default" },
18933
+ ui: {
18934
+ intent: "behavior",
18935
+ widget: "mailingListTargeting"
18936
+ },
18937
+ schema: { fields: [{
18938
+ id: "mode",
18939
+ type: "select",
18940
+ label: "Mode",
18941
+ required: true,
18942
+ defaultValue: "default",
18943
+ options: [{
18944
+ value: "default",
18945
+ label: "Default"
18946
+ }, {
18947
+ value: "custom",
18948
+ label: "Custom"
18949
+ }],
18950
+ ui: { hidden: true }
18951
+ }] }
18681
18952
  },
18682
18953
  {
18683
- id: "newsletterPrivacyNote",
18954
+ id: "contactTitle",
18684
18955
  type: "text",
18685
- label: "Privacy note",
18686
- ui: { intent: "display" }
18956
+ label: "Contact title",
18957
+ defaultValue: "Get in touch"
18687
18958
  },
18688
18959
  {
18689
- id: "newsletterTags",
18960
+ id: "mailingListSource",
18690
18961
  type: "text",
18691
- label: "Newsletter tags",
18692
- description: "Optional tags to apply on subscribe, separated by comma or semicolon.",
18693
- ui: { intent: "behavior" }
18962
+ label: "Mailing-list source",
18963
+ ui: {
18964
+ hidden: true,
18965
+ intent: "behavior"
18966
+ }
18694
18967
  },
18695
18968
  {
18696
- id: "contactTitle",
18969
+ id: "mailingListSourceToken",
18697
18970
  type: "text",
18698
- label: "Contact title",
18699
- defaultValue: "Get in touch"
18971
+ label: "Mailing-list source token",
18972
+ ui: {
18973
+ hidden: true,
18974
+ intent: "behavior"
18975
+ }
18700
18976
  },
18701
18977
  {
18702
18978
  id: "contactIntro",
@@ -19206,13 +19482,9 @@ const footerStandardContactBindings = {
19206
19482
  };
19207
19483
  const footerMailingListBindings = {
19208
19484
  source: { $bind: { from: "module.engagement.runtime.source" } },
19209
- destinationRef: { $bind: { from: "module.engagement.runtime.destinationRef" } },
19210
- listId: { $bind: { from: "module.engagement.runtime.listId" } },
19211
- preset: { $bind: { from: "module.engagement.runtime.preset" } },
19212
- includePhone: { $bind: { from: "module.engagement.runtime.includePhone" } },
19213
- buttonLabel: { $bind: { from: "module.engagement.runtime.buttonLabel" } },
19214
- successMessage: { $bind: { from: "module.engagement.runtime.successMessage" } },
19215
- privacyNote: { $bind: { from: "module.engagement.runtime.privacyNote" } }
19485
+ sourceRef: { $bind: { from: "module.engagement.runtime.sourceRef" } },
19486
+ mailingList: { $bind: { from: "module.engagement.runtime.mailingList" } },
19487
+ targeting: { $bind: { from: "module.engagement.runtime.targeting" } }
19216
19488
  };
19217
19489
  const footerSpamProtectionBinding = { spamProtectionEnabled: { $bind: { from: "$root.spamProtectionEnabled" } } };
19218
19490
  const brandLogo = link({
@@ -20900,40 +21172,112 @@ const appointmentBookingBlockDefinition = {
20900
21172
  }
20901
21173
  }
20902
21174
  };
20903
- //#endregion
20904
- //#region ../blocks/src/system/blocks/weekly-slot-availability.ts
20905
- const weeklySlotAvailabilityManifest = createBlockManifest({
20906
- id: "block.weekly-slot-availability",
20907
- version: "1.0.0",
20908
- title: "Weekly Slot Availability",
20909
- titleSource: "heading",
20910
- description: "Shows available recurring appointment slot patterns",
20911
- skipSectionStyles: true,
20912
- additionalFields: [
20913
- {
20914
- id: "serviceId",
20915
- type: "reference",
20916
- label: "Appointment service",
20917
- description: "Choose which appointment service to show availability for",
20918
- required: true,
20919
- referenceKind: "appointmentService",
20920
- allowManualEntry: false,
20921
- ui: { intent: "behavior" }
20922
- },
20923
- {
20924
- id: "resourceId",
20925
- type: "reference",
20926
- label: "Appointment resource",
20927
- description: "Optionally narrow the availability to one practitioner or room",
20928
- required: false,
20929
- referenceKind: "appointmentResource",
20930
- allowManualEntry: false,
20931
- ui: { intent: "behavior" }
20932
- },
20933
- {
20934
- id: "horizonWeeks",
20935
- type: "select",
20936
- label: "Look ahead",
21175
+ const appointmentServicesBlockDefinition = {
21176
+ manifest: createBlockManifest({
21177
+ id: "block.appointment-services",
21178
+ version: "1.0.0",
21179
+ title: "Appointment Services",
21180
+ titleSource: "heading",
21181
+ description: "List public appointment services with booking links",
21182
+ skipSectionStyles: true,
21183
+ additionalFields: [
21184
+ {
21185
+ id: "heading",
21186
+ type: "text",
21187
+ label: "Heading",
21188
+ required: false,
21189
+ multiline: false,
21190
+ defaultValue: "Appointments",
21191
+ ui: { intent: "content" }
21192
+ },
21193
+ {
21194
+ id: "description",
21195
+ type: "richText",
21196
+ label: "Description",
21197
+ required: false,
21198
+ ui: { intent: "content" }
21199
+ },
21200
+ sectionStylesField({
21201
+ id: "_sectionStyles",
21202
+ label: "Section styles",
21203
+ renderInHeader: false
21204
+ })
21205
+ ],
21206
+ styleTokens: {
21207
+ background: "surface",
21208
+ typography: "body",
21209
+ spacing: "md"
21210
+ },
21211
+ behaviours: {
21212
+ supportsThemeSwitching: true,
21213
+ inlineEditing: true,
21214
+ animation: false,
21215
+ paletteHidden: false
21216
+ },
21217
+ category: "interactive",
21218
+ contentSource: { kind: "inline" },
21219
+ tags: [
21220
+ "booking",
21221
+ "appointment",
21222
+ "services",
21223
+ "scheduling"
21224
+ ],
21225
+ icon: "CalendarDays",
21226
+ layout: [styledSection({
21227
+ baseClass: "",
21228
+ spacing: "spacious",
21229
+ children: sectionContainer([
21230
+ text({ as: "h2" }, bind("content.heading"), when("content.heading")),
21231
+ richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
21232
+ el("appointment-services", {
21233
+ sectionHeading: { $bind: { from: "content.heading" } },
21234
+ services: { $bind: { from: "data.services" } },
21235
+ pathname: { $bind: { from: "$root.pathname" } }
21236
+ })
21237
+ ], { gap: "lg" })
21238
+ })]
21239
+ }),
21240
+ dataSchemas: { services: z.unknown().optional() },
21241
+ dataLoaders: { services: {
21242
+ endpoint: "getPublicBookingServices",
21243
+ params: { siteId: { $bind: { from: "$root.siteId" } } },
21244
+ mode: "server"
21245
+ } }
21246
+ };
21247
+ //#endregion
21248
+ //#region ../blocks/src/system/blocks/weekly-slot-availability.ts
21249
+ const weeklySlotAvailabilityManifest = createBlockManifest({
21250
+ id: "block.weekly-slot-availability",
21251
+ version: "1.0.0",
21252
+ title: "Weekly Slot Availability",
21253
+ titleSource: "heading",
21254
+ description: "Shows available recurring appointment slot patterns",
21255
+ skipSectionStyles: true,
21256
+ additionalFields: [
21257
+ {
21258
+ id: "serviceId",
21259
+ type: "reference",
21260
+ label: "Appointment service",
21261
+ description: "Choose which appointment service to show availability for",
21262
+ required: true,
21263
+ referenceKind: "appointmentService",
21264
+ allowManualEntry: false,
21265
+ ui: { intent: "behavior" }
21266
+ },
21267
+ {
21268
+ id: "resourceId",
21269
+ type: "reference",
21270
+ label: "Appointment resource",
21271
+ description: "Optionally narrow the availability to one practitioner or room",
21272
+ required: false,
21273
+ referenceKind: "appointmentResource",
21274
+ allowManualEntry: false,
21275
+ ui: { intent: "behavior" }
21276
+ },
21277
+ {
21278
+ id: "horizonWeeks",
21279
+ type: "select",
21280
+ label: "Look ahead",
20937
21281
  required: false,
20938
21282
  multiple: false,
20939
21283
  defaultValue: "4",
@@ -22208,8 +22552,8 @@ z.object({
22208
22552
  previewToken: z.string().nullable().optional(),
22209
22553
  stage: previewStageSchema.optional()
22210
22554
  });
22211
- function buildEventRegistrationDisplay(source) {
22212
- const selectedRun = resolveSelectedWholeSeriesRun(source);
22555
+ function buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents) {
22556
+ const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
22213
22557
  if (selectedRun && (!selectedRun.run.enrollmentOpen || toWholeSeriesRegistrationEvent(selectedRun) === null)) return {
22214
22558
  kind: "event-registration",
22215
22559
  state: "registration-closed",
@@ -22217,7 +22561,7 @@ function buildEventRegistrationDisplay(source) {
22217
22561
  titleText: "Registration closed",
22218
22562
  bodyText: "This run is no longer open for bookings."
22219
22563
  };
22220
- const stepLabels = buildEventRegistrationShellStepLabels(source);
22564
+ const stepLabels = buildEventRegistrationShellStepLabels(source, scopedSourceEvents);
22221
22565
  return {
22222
22566
  kind: "event-registration",
22223
22567
  state: "generic-shell",
@@ -22228,8 +22572,8 @@ function buildEventRegistrationDisplay(source) {
22228
22572
  summaryButtonText: source.buttonText?.trim() || "Register"
22229
22573
  };
22230
22574
  }
22231
- function buildEventRegistrationShellStepLabels(source) {
22232
- const selectedRun = resolveSelectedWholeSeriesRun(source);
22575
+ function buildEventRegistrationShellStepLabels(source, scopedSourceEvents) {
22576
+ const selectedRun = resolveSelectedWholeSeriesRun(scopedSourceEvents);
22233
22577
  if (selectedRun) {
22234
22578
  const selectedEvent = toWholeSeriesRegistrationEvent(selectedRun);
22235
22579
  if (!selectedEvent) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
@@ -22239,7 +22583,7 @@ function buildEventRegistrationShellStepLabels(source) {
22239
22583
  event: selectedEvent
22240
22584
  })).map((step) => shellLabelForRegistrationStep(step, [selectedEvent]));
22241
22585
  }
22242
- const scopedEvents = scopeEventRegistrationEvents(source);
22586
+ const scopedEvents = toScopedEventRegistrationEvents(scopedSourceEvents);
22243
22587
  if (scopedEvents.length === 0) return EVENT_REGISTRATION_GENERIC_SHELL_STEP_LABELS;
22244
22588
  const preSelectedEvent = findPreSelectedEvent({
22245
22589
  scopedEvents,
@@ -22252,31 +22596,25 @@ function buildEventRegistrationShellStepLabels(source) {
22252
22596
  event: preSelectedEvent
22253
22597
  }) : false).map((step) => shellLabelForRegistrationStep(step, scopedEvents));
22254
22598
  }
22255
- function scopeEventRegistrationEvents(source) {
22256
- const scoped = scopeEventRegistrationSourceEvents(source);
22257
- return [...toRegistrationEvents(scoped), ...toWholeSeriesRegistrationEvents(scoped)];
22599
+ function toScopedEventRegistrationEvents(scopedSourceEvents) {
22600
+ return [...toRegistrationEvents(scopedSourceEvents), ...toWholeSeriesRegistrationEvents(scopedSourceEvents)];
22258
22601
  }
22259
- /**
22260
- * Every event this surface may register for, from both of its loads.
22261
- *
22262
- * The date page and the run read answer different questions and can name the
22263
- * same occurrence, so the two lists are joined and any occurrence that arrives
22264
- * twice is kept once, in the order the date page gave it.
22265
- */
22266
22602
  function scopeEventRegistrationSourceEvents(source) {
22267
- const byOccurrenceId = /* @__PURE__ */ new Map();
22268
- for (const event of [...source.events ?? [], ...source.wholeSeriesRunEvents ?? []]) if (!byOccurrenceId.has(event.occurrenceId)) byOccurrenceId.set(event.occurrenceId, event);
22269
- return resolveEventRegistrationScopedEvents([...byOccurrenceId.values()], {
22603
+ return resolveEventRegistrationScopedEvents(source.wholeSeriesRunEvents ?? source.events ?? [], {
22270
22604
  occurrenceContext: source.occurrenceContext,
22271
22605
  subject: source.subject
22272
22606
  });
22273
22607
  }
22608
+ function resolveLoadedEventRegistrationSourceEvents(source, scopedSourceEvents) {
22609
+ if (source.events == null && source.wholeSeriesRunEvents == null) return null;
22610
+ return [...scopedSourceEvents];
22611
+ }
22274
22612
  /**
22275
22613
  * The one run this surface is registering for, when the scoped events describe
22276
22614
  * exactly one. More than one run means the visitor still has to choose.
22277
22615
  */
22278
- function resolveSelectedWholeSeriesRun(source) {
22279
- const runs = groupPublicEventsByRun(scopeEventRegistrationSourceEvents(source));
22616
+ function resolveSelectedWholeSeriesRun(scopedSourceEvents) {
22617
+ const runs = groupPublicEventsByRun(scopedSourceEvents);
22280
22618
  return runs.length === 1 ? runs[0] : null;
22281
22619
  }
22282
22620
  function shellLabelForRegistrationStep(step, events) {
@@ -22310,9 +22648,10 @@ const eventRegistrationBlockRuntime = defineClientOwnedBlockRuntime({
22310
22648
  render: createZodCodec(eventRegistrationRenderSchema)
22311
22649
  },
22312
22650
  buildRender: (source) => {
22313
- const events = source.events == null ? null : scopeEventRegistrationSourceEvents(source);
22651
+ const scopedSourceEvents = scopeEventRegistrationSourceEvents(source);
22652
+ const events = resolveLoadedEventRegistrationSourceEvents(source, scopedSourceEvents);
22314
22653
  return {
22315
- display: buildEventRegistrationDisplay(source),
22654
+ display: buildEventRegistrationDisplayFromScopedEvents(source, scopedSourceEvents),
22316
22655
  hydration: {
22317
22656
  occurrenceContext: source.occurrenceContext ?? null,
22318
22657
  subject: source.subject ?? null,
@@ -22513,6 +22852,11 @@ const eventRegistrationBlockDefinition = {
22513
22852
  dataLoaders: {
22514
22853
  events: {
22515
22854
  endpoint: "listPublicEvents",
22855
+ when: { $bind: {
22856
+ from: "$root.subject.isWholeSeriesBooking",
22857
+ fallback: true,
22858
+ transforms: [{ id: "boolean.not" }]
22859
+ } },
22516
22860
  params: {
22517
22861
  siteId: { $bind: { from: "$root.siteId" } },
22518
22862
  limit: "50",
@@ -22534,13 +22878,9 @@ const eventRegistrationBlockDefinition = {
22534
22878
  /**
22535
22879
  * The runs this page can register for, finished ones included.
22536
22880
  *
22537
- * The `events` page above is date-bounded, so a run whose first session has
22538
- * passed contributes no rows to it and the surface would have nothing to
22539
- * render as closed. This asks for runs instead, and only on a page whose
22540
- * subject books whole runs. It is scoped exactly like the date page — same
22541
- * series, same run subroute — because scope and grain are separate
22542
- * questions. The two lists are joined by occurrence, so a run that appears
22543
- * in both is still one run.
22881
+ * A run read is the sole Event source on a whole-series surface. It is
22882
+ * status-bounded rather than date-bounded, so completed runs can still
22883
+ * render their closed state and every run carries its complete schedule.
22544
22884
  */
22545
22885
  wholeSeriesRunEvents: {
22546
22886
  endpoint: "listPublicEvents",
@@ -25045,68 +25385,6 @@ const recurringScheduleBlockDefinition = {
25045
25385
  } }
25046
25386
  };
25047
25387
  //#endregion
25048
- //#region ../blocks/src/system/types/link.ts
25049
- const nullableString = z.union([z.string().min(1), z.null()]);
25050
- const RESOLVED_INTERNAL_LINK_ENTITY_TYPES = [
25051
- "page",
25052
- "content",
25053
- "pageSurface"
25054
- ];
25055
- const resolvedInternalLinkKeys = [
25056
- "entityId",
25057
- "entityType",
25058
- "href",
25059
- "title",
25060
- "typeLabel"
25061
- ];
25062
- const internalRouteOnlyLinkSchema = z.object({
25063
- kind: z.literal("internal"),
25064
- routeId: z.string().min(1)
25065
- }).passthrough().superRefine((value, ctx) => {
25066
- if (!resolvedInternalLinkKeys.some((key) => value[key] != null)) return;
25067
- ctx.addIssue({
25068
- code: "custom",
25069
- message: "Internal links with resolved metadata must include entityId, entityType, href, title, and typeLabel"
25070
- });
25071
- });
25072
- const internalResolvedLinkSchema = z.object({
25073
- kind: z.literal("internal"),
25074
- routeId: z.string().min(1),
25075
- entityId: z.string().min(1),
25076
- entityType: z.enum(RESOLVED_INTERNAL_LINK_ENTITY_TYPES),
25077
- href: z.string().min(1),
25078
- title: z.string().min(1),
25079
- typeLabel: z.string().min(1),
25080
- contentTypeKey: nullableString.optional(),
25081
- contentTypeName: nullableString.optional(),
25082
- updatedAt: nullableString.optional()
25083
- }).passthrough();
25084
- const internalLinkSchema = z.union([internalResolvedLinkSchema, internalRouteOnlyLinkSchema]);
25085
- const externalLinkSchema = z.object({
25086
- kind: z.literal("external"),
25087
- href: z.string().min(1)
25088
- }).passthrough();
25089
- const customLinkSchema = z.object({
25090
- kind: z.literal("url"),
25091
- href: z.string().min(1)
25092
- }).passthrough();
25093
- const pageLinkSchema = z.object({
25094
- kind: z.literal("page"),
25095
- identifier: z.string().min(1)
25096
- }).passthrough();
25097
- const entryLinkSchema = z.object({
25098
- kind: z.literal("entry"),
25099
- contentType: z.string().min(1),
25100
- identifier: z.string().min(1)
25101
- }).passthrough();
25102
- z.union([
25103
- internalLinkSchema,
25104
- externalLinkSchema,
25105
- customLinkSchema,
25106
- pageLinkSchema,
25107
- entryLinkSchema
25108
- ]);
25109
- //#endregion
25110
25388
  //#region ../blocks/src/system/blocks/offerings-preview/model.ts
25111
25389
  const OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT = "Book";
25112
25390
  const OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE = "Nothing scheduled just yet.";
@@ -25191,20 +25469,7 @@ function isRecord$2(value) {
25191
25469
  return typeof value === "object" && value !== null && !Array.isArray(value);
25192
25470
  }
25193
25471
  //#endregion
25194
- //#region ../blocks/src/system/blocks/offerings-preview/index.ts
25195
- /**
25196
- * The whole-series presets, as the CSV `listPublicEvents` expects. Both
25197
- * whole-series presets share one loader: the block renders them as a single
25198
- * run group.
25199
- */
25200
- const WHOLE_SERIES_EVENT_PRESETS_PARAM = WHOLE_SERIES_EVENT_PRESET_VALUES.join(",");
25201
- const publicRouteCandidatesSchema = z.object({ routes: z.array(z.object({
25202
- entityId: z.string(),
25203
- path: z.string(),
25204
- title: z.string(),
25205
- kind: z.enum(RESOLVED_INTERNAL_LINK_ENTITY_TYPES),
25206
- contentTypeKey: z.string().nullable().optional()
25207
- })) });
25472
+ //#region ../blocks/src/system/blocks/offerings-preview/fields.ts
25208
25473
  const groupOverrideFields = [
25209
25474
  {
25210
25475
  id: "sourceKey",
@@ -25410,7 +25675,127 @@ const offeringsPreviewFields = [
25410
25675
  renderInHeader: false
25411
25676
  })
25412
25677
  ];
25678
+ function labelForOfferingPreviewSourceKey(sourceKey) {
25679
+ const eventDefinition = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.find((definition) => definition.sourceKey === sourceKey);
25680
+ if (eventDefinition) return eventDefinition.label;
25681
+ return "Courses";
25682
+ }
25683
+ //#endregion
25684
+ //#region ../blocks/src/system/types/link.ts
25685
+ const nullableString = z.union([z.string().min(1), z.null()]);
25686
+ const RESOLVED_INTERNAL_LINK_ENTITY_TYPES = [
25687
+ "page",
25688
+ "content",
25689
+ "pageSurface"
25690
+ ];
25691
+ const resolvedInternalLinkKeys = [
25692
+ "entityId",
25693
+ "entityType",
25694
+ "href",
25695
+ "title",
25696
+ "typeLabel"
25697
+ ];
25698
+ const internalRouteOnlyLinkSchema = z.object({
25699
+ kind: z.literal("internal"),
25700
+ routeId: z.string().min(1)
25701
+ }).passthrough().superRefine((value, ctx) => {
25702
+ if (!resolvedInternalLinkKeys.some((key) => value[key] != null)) return;
25703
+ ctx.addIssue({
25704
+ code: "custom",
25705
+ message: "Internal links with resolved metadata must include entityId, entityType, href, title, and typeLabel"
25706
+ });
25707
+ });
25708
+ const internalResolvedLinkSchema = z.object({
25709
+ kind: z.literal("internal"),
25710
+ routeId: z.string().min(1),
25711
+ entityId: z.string().min(1),
25712
+ entityType: z.enum(RESOLVED_INTERNAL_LINK_ENTITY_TYPES),
25713
+ href: z.string().min(1),
25714
+ title: z.string().min(1),
25715
+ typeLabel: z.string().min(1),
25716
+ contentTypeKey: nullableString.optional(),
25717
+ contentTypeName: nullableString.optional(),
25718
+ updatedAt: nullableString.optional()
25719
+ }).passthrough();
25720
+ const internalLinkSchema = z.union([internalResolvedLinkSchema, internalRouteOnlyLinkSchema]);
25721
+ const externalLinkSchema = z.object({
25722
+ kind: z.literal("external"),
25723
+ href: z.string().min(1)
25724
+ }).passthrough();
25725
+ const customLinkSchema = z.object({
25726
+ kind: z.literal("url"),
25727
+ href: z.string().min(1)
25728
+ }).passthrough();
25729
+ const pageLinkSchema = z.object({
25730
+ kind: z.literal("page"),
25731
+ identifier: z.string().min(1)
25732
+ }).passthrough();
25733
+ const entryLinkSchema = z.object({
25734
+ kind: z.literal("entry"),
25735
+ contentType: z.string().min(1),
25736
+ identifier: z.string().min(1)
25737
+ }).passthrough();
25738
+ z.union([
25739
+ internalLinkSchema,
25740
+ externalLinkSchema,
25741
+ customLinkSchema,
25742
+ pageLinkSchema,
25743
+ entryLinkSchema
25744
+ ]);
25745
+ //#endregion
25746
+ //#region ../blocks/src/system/blocks/offerings-preview/index.ts
25747
+ /**
25748
+ * The whole-series presets, as the CSV `listPublicEvents` expects. Both
25749
+ * whole-series presets share one loader: the block renders them as a single
25750
+ * run group.
25751
+ */
25752
+ const WHOLE_SERIES_EVENT_PRESETS_PARAM = WHOLE_SERIES_EVENT_PRESET_VALUES.join(",");
25753
+ const publicRouteCandidatesSchema = z.object({ routes: z.array(z.object({
25754
+ entityId: z.string(),
25755
+ path: z.string(),
25756
+ title: z.string(),
25757
+ kind: z.enum(RESOLVED_INTERNAL_LINK_ENTITY_TYPES),
25758
+ contentTypeKey: z.string().nullable().optional()
25759
+ })) });
25413
25760
  const offeringsPreviewEventDataBindings = Object.fromEntries(OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.map((definition) => [definition.dataKey, { $bind: { from: `data.${definition.dataKey}` } }]));
25761
+ const DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE = "preview";
25762
+ const displayModeBinding = {
25763
+ from: "content.displayMode",
25764
+ fallback: DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE
25765
+ };
25766
+ const catalogueMode = { $bind: {
25767
+ ...displayModeBinding,
25768
+ transforms: [{
25769
+ id: "value.equals",
25770
+ options: { value: "catalogue" }
25771
+ }]
25772
+ } };
25773
+ const previewMode = { $bind: {
25774
+ ...displayModeBinding,
25775
+ transforms: [{
25776
+ id: "value.equals",
25777
+ options: { value: "preview" }
25778
+ }]
25779
+ } };
25780
+ function offeringsPreviewHeaderNodes() {
25781
+ return [text({
25782
+ as: "h2",
25783
+ className: { $bind: {
25784
+ from: "content.displayMode",
25785
+ fallback: "rb-text-center",
25786
+ transforms: [{
25787
+ id: "value.equals",
25788
+ options: { value: "catalogue" }
25789
+ }, {
25790
+ id: "ui.conditionalClass",
25791
+ options: {
25792
+ whenTrue: "rb-text-left",
25793
+ whenFalse: "rb-text-center"
25794
+ }
25795
+ }]
25796
+ } }
25797
+ }, bind("content.heading"), when("content.heading")), richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description"))];
25798
+ }
25414
25799
  const offeringsPreviewManifest = createBlockManifest({
25415
25800
  id: "block.offerings-preview",
25416
25801
  version: "1.0.0",
@@ -25441,48 +25826,45 @@ const offeringsPreviewManifest = createBlockManifest({
25441
25826
  ],
25442
25827
  icon: "PanelsTopLeft",
25443
25828
  layout: [styledSection({
25444
- children: sectionContainer([
25445
- text({
25446
- as: "h2",
25447
- className: "rb-text-center"
25448
- }, bind("content.heading"), when("content.heading")),
25449
- richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
25450
- el("offerings-preview", {
25451
- bookingOfferingPresets: { $bind: {
25452
- from: "$root.bookingOfferingPresets",
25453
- fallback: []
25454
- } },
25455
- groupOverrides: { $bind: {
25456
- from: "content.groupOverrides",
25457
- fallback: []
25458
- } },
25459
- groupOrder: { $bind: { from: "content.groupOrder" } },
25460
- cardStyles: { $bind: { from: "content._cardStyles" } },
25461
- itemLimit: { $bind: {
25462
- from: "content.itemLimit",
25463
- fallback: "3"
25464
- } },
25465
- buttonVariant: { $bind: {
25466
- from: "content.buttonVariant",
25467
- fallback: "primary"
25468
- } },
25469
- buttonText: { $bind: {
25470
- from: "content.buttonText",
25471
- fallback: OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT
25472
- } },
25473
- emptyMessage: { $bind: {
25474
- from: "content.emptyMessage",
25475
- fallback: OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE
25476
- } },
25477
- previewStage: { $bind: {
25478
- from: "$root.previewStage",
25479
- fallback: "published"
25480
- } },
25481
- routeCandidates: { $bind: { from: "data.routeCandidates" } },
25482
- wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
25483
- ...offeringsPreviewEventDataBindings
25484
- })
25485
- ], { gap: "lg" }),
25829
+ children: sectionContainer([el("offerings-preview", {
25830
+ displayMode: { $bind: displayModeBinding },
25831
+ pathname: { $bind: { from: "$root.pathname" } },
25832
+ catalogueEvents: { $bind: { from: "data.catalogueEvents" } },
25833
+ quickDateEvents: { $bind: { from: "data.quickDateEvents" } },
25834
+ bookingOfferingPresets: { $bind: {
25835
+ from: "$root.bookingOfferingPresets",
25836
+ fallback: []
25837
+ } },
25838
+ groupOverrides: { $bind: {
25839
+ from: "content.groupOverrides",
25840
+ fallback: []
25841
+ } },
25842
+ groupOrder: { $bind: { from: "content.groupOrder" } },
25843
+ cardStyles: { $bind: { from: "content._cardStyles" } },
25844
+ itemLimit: { $bind: {
25845
+ from: "content.itemLimit",
25846
+ fallback: "3"
25847
+ } },
25848
+ buttonVariant: { $bind: {
25849
+ from: "content.buttonVariant",
25850
+ fallback: "primary"
25851
+ } },
25852
+ buttonText: { $bind: {
25853
+ from: "content.buttonText",
25854
+ fallback: OFFERINGS_PREVIEW_DEFAULT_BUTTON_TEXT
25855
+ } },
25856
+ emptyMessage: { $bind: {
25857
+ from: "content.emptyMessage",
25858
+ fallback: OFFERINGS_PREVIEW_DEFAULT_EMPTY_MESSAGE
25859
+ } },
25860
+ previewStage: { $bind: {
25861
+ from: "$root.previewStage",
25862
+ fallback: "published"
25863
+ } },
25864
+ routeCandidates: { $bind: { from: "data.routeCandidates" } },
25865
+ wholeSeriesRunEvents: { $bind: { from: "data.wholeSeriesRunEvents" } },
25866
+ ...offeringsPreviewEventDataBindings
25867
+ }, offeringsPreviewHeaderNodes())], { gap: "lg" }),
25486
25868
  baseClass: "",
25487
25869
  spacing: "comfortable"
25488
25870
  })]
@@ -25496,16 +25878,48 @@ const offeringsPreviewBlockDefinition = {
25496
25878
  dataSchemas: {
25497
25879
  ...offeringsPreviewEventDataSchemas,
25498
25880
  routeCandidates: publicRouteCandidatesSchema.optional(),
25499
- wholeSeriesRunEvents: optionalPublicEventsArraySchema
25881
+ wholeSeriesRunEvents: optionalPublicEventsArraySchema,
25882
+ catalogueEvents: optionalPublicEventsArraySchema,
25883
+ quickDateEvents: optionalPublicEventsArraySchema
25500
25884
  },
25501
25885
  dataLoaders: {
25502
25886
  ...offeringsPreviewEventDataLoaders,
25503
25887
  routeCandidates: {
25504
25888
  endpoint: "listPublicRouteCandidates",
25889
+ when: previewMode,
25505
25890
  params: {},
25506
25891
  mode: "server"
25507
25892
  },
25508
- wholeSeriesRunEvents: wholeSeriesRunLoader()
25893
+ wholeSeriesRunEvents: wholeSeriesRunLoader(),
25894
+ catalogueEvents: {
25895
+ endpoint: "listPublicEvents",
25896
+ when: catalogueMode,
25897
+ params: {
25898
+ stage: { $bind: {
25899
+ from: "$root.previewStage",
25900
+ fallback: "published"
25901
+ } },
25902
+ detailLevel: "listing",
25903
+ readProfile: "listing-card",
25904
+ readIntent: "catalogue"
25905
+ },
25906
+ mode: "server"
25907
+ },
25908
+ quickDateEvents: {
25909
+ endpoint: "listPublicEvents",
25910
+ when: catalogueMode,
25911
+ params: {
25912
+ stage: { $bind: {
25913
+ from: "$root.previewStage",
25914
+ fallback: "published"
25915
+ } },
25916
+ detailLevel: "listing",
25917
+ readProfile: "listing-card",
25918
+ readIntent: "occurrences",
25919
+ limit: 40
25920
+ },
25921
+ mode: "server"
25922
+ }
25509
25923
  }
25510
25924
  };
25511
25925
  /**
@@ -25522,6 +25936,7 @@ function wholeSeriesRunLoader() {
25522
25936
  function eventPresetLoader(eventPresets, extraParams = {}) {
25523
25937
  return {
25524
25938
  endpoint: "listPublicEvents",
25939
+ when: previewMode,
25525
25940
  params: {
25526
25941
  siteId: { $bind: { from: "$root.siteId" } },
25527
25942
  limit: { $bind: {
@@ -25540,15 +25955,10 @@ function eventPresetLoader(eventPresets, extraParams = {}) {
25540
25955
  mode: "server"
25541
25956
  };
25542
25957
  }
25543
- function labelForOfferingPreviewSourceKey(sourceKey) {
25544
- const eventDefinition = OFFERINGS_PREVIEW_EVENT_PRESET_DEFINITIONS.find((definition) => definition.sourceKey === sourceKey);
25545
- if (eventDefinition) return eventDefinition.label;
25546
- return "Courses";
25547
- }
25548
25958
  function normalizeOfferingsPreviewBlockContent(value) {
25549
25959
  const record = normalizeOfferingsPreviewContentForWrite(value);
25550
- if (!record) return {};
25551
- const normalized = {};
25960
+ if (!record) return { displayMode: DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE };
25961
+ const normalized = { displayMode: normalizeOfferingsPreviewDisplayMode(record.displayMode) };
25552
25962
  if (typeof record.heading === "string" || record.heading === null) normalized.heading = record.heading;
25553
25963
  if ("description" in record) normalized.description = record.description;
25554
25964
  if (isOfferingsPreviewItemLimit(record.itemLimit)) normalized.itemLimit = record.itemLimit;
@@ -25565,6 +25975,9 @@ function normalizeOfferingsPreviewBlockContent(value) {
25565
25975
  if (sectionStyles !== void 0) normalized._sectionStyles = sectionStyles;
25566
25976
  return normalized;
25567
25977
  }
25978
+ function normalizeOfferingsPreviewDisplayMode(value) {
25979
+ return value === "catalogue" ? "catalogue" : DEFAULT_OFFERINGS_PREVIEW_DISPLAY_MODE;
25980
+ }
25568
25981
  function isOfferingsPreviewItemLimit(value) {
25569
25982
  return value === "1" || value === "2" || value === "3" || value === "4" || value === "5" || value === "6" || value === null;
25570
25983
  }
@@ -28779,63 +29192,13 @@ const giftingBlockDefinition = {
28779
29192
  } }
28780
29193
  };
28781
29194
  //#endregion
28782
- //#region ../blocks/src/system/blocks/newsletter-signup-destination.ts
28783
- function parseNewsletterSignupBlockId(value) {
28784
- const trimmed = parseTrimmedString(value);
28785
- return trimmed && BLOCK_ID_PATTERN.test(trimmed) ? trimmed : null;
28786
- }
28787
- function parseSignedNewsletterDestinationRef(value) {
28788
- const trimmed = parseTrimmedString(value);
28789
- return trimmed && SIGNED_DESTINATION_REF_PATTERN.test(trimmed) ? trimmed : null;
28790
- }
28791
- function buildNewsletterSignupBlockRef(blockId) {
28792
- const parsed = parseNewsletterSignupBlockId(blockId);
28793
- return parsed ? {
28794
- tag: "block_ref",
28795
- blockId: parsed
28796
- } : null;
28797
- }
28798
- function normalizeNewsletterSignupEditorValuesForDestination(values) {
28799
- const destinationProvider = parseNewsletterSignupDestinationProvider(values.destinationProvider) ?? "riverbank";
28800
- if (destinationProvider === "mailchimp") return {
28801
- destinationProvider,
28802
- mailchimpAudienceId: values.mailchimpAudienceId,
28803
- mailchimpTags: values.mailchimpTags
28804
- };
28805
- return {
28806
- destinationProvider,
28807
- listId: values.listId,
28808
- tags: values.tags
28809
- };
28810
- }
28811
- function deriveNewsletterSignupRuntimeSource(values) {
28812
- const normalized = normalizeNewsletterSignupEditorValuesForDestination(values);
28813
- if (normalized.destinationProvider === "mailchimp") return {
28814
- destinationProvider: "mailchimp",
28815
- listId: null,
28816
- tags: null
28817
- };
28818
- return {
28819
- destinationProvider: "riverbank",
28820
- listId: parseTrimmedString(normalized.listId),
28821
- tags: parseTrimmedString(normalized.tags)
28822
- };
28823
- }
28824
- const BLOCK_ID_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/;
28825
- const SIGNED_DESTINATION_REF_PATTERN = /^[A-Za-z0-9._~-]{16,1000}$/;
28826
- function parseTrimmedString(value) {
28827
- if (typeof value !== "string") return null;
28828
- const trimmed = value.trim();
28829
- return trimmed.length > 0 ? trimmed : null;
28830
- }
28831
- //#endregion
28832
29195
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.types.ts
28833
29196
  /**
28834
29197
  * Get form fields based on preset configuration.
28835
29198
  */
28836
- function getPresetFields(preset, options) {
28837
- const includePhone = Boolean(options?.includePhone);
28838
- switch (preset) {
29199
+ function getGlobalFormFields(form) {
29200
+ const includePhone = form.includeOptionalPhone;
29201
+ switch (form.fields.preset) {
28839
29202
  case "emailOnly": return withOptionalPhone([{
28840
29203
  id: "email",
28841
29204
  type: "email",
@@ -28853,7 +29216,7 @@ function getPresetFields(preset, options) {
28853
29216
  id: "name",
28854
29217
  type: "text",
28855
29218
  label: "Name",
28856
- required: false,
29219
+ required: form.fields.name === "required",
28857
29220
  placeholder: "Your name"
28858
29221
  }], includePhone);
28859
29222
  case "emailFirstLast": return withOptionalPhone([
@@ -28868,14 +29231,14 @@ function getPresetFields(preset, options) {
28868
29231
  id: "firstName",
28869
29232
  type: "text",
28870
29233
  label: "First Name",
28871
- required: false,
29234
+ required: form.fields.firstName === "required",
28872
29235
  placeholder: "First name"
28873
29236
  },
28874
29237
  {
28875
29238
  id: "lastName",
28876
29239
  type: "text",
28877
29240
  label: "Last Name",
28878
- required: false,
29241
+ required: form.fields.lastName === "required",
28879
29242
  placeholder: "Last name"
28880
29243
  }
28881
29244
  ], includePhone);
@@ -28891,14 +29254,14 @@ function getPresetFields(preset, options) {
28891
29254
  id: "name",
28892
29255
  type: "text",
28893
29256
  label: "Name",
28894
- required: false,
29257
+ required: form.fields.name === "required",
28895
29258
  placeholder: "Your name"
28896
29259
  },
28897
29260
  {
28898
29261
  id: "company",
28899
29262
  type: "text",
28900
29263
  label: "Company",
28901
- required: false,
29264
+ required: form.fields.company === "required",
28902
29265
  placeholder: "Company name"
28903
29266
  }
28904
29267
  ], includePhone);
@@ -28917,14 +29280,29 @@ function withOptionalPhone(fields, includePhone) {
28917
29280
  }
28918
29281
  //#endregion
28919
29282
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.interactive.ts
28920
- const newsletterPresetSchema = z.enum([
28921
- "emailOnly",
28922
- "emailName",
28923
- "emailFirstLast",
28924
- "emailNameCompany"
28925
- ]);
28926
- const newsletterDestinationProviderSchema = z.enum(["riverbank", "mailchimp"]);
28927
29283
  const newsletterButtonVariantSchema = blockButtonRoleSchema;
29284
+ const mailingListContextSchema = z.unknown().transform((value, ctx) => {
29285
+ const parsed = parseMailingListRuntimeContext(value);
29286
+ if (!parsed.ok) {
29287
+ ctx.addIssue({
29288
+ code: z.ZodIssueCode.custom,
29289
+ message: "Invalid mailing-list runtime context"
29290
+ });
29291
+ return z.NEVER;
29292
+ }
29293
+ return parsed.value;
29294
+ });
29295
+ const mailingListTargetingSchema = z.unknown().transform((value, ctx) => {
29296
+ const parsed = parseMailingListPlacementTargeting(value);
29297
+ if (!parsed.ok) {
29298
+ ctx.addIssue({
29299
+ code: z.ZodIssueCode.custom,
29300
+ message: "Invalid mailing-list targeting"
29301
+ });
29302
+ return z.NEVER;
29303
+ }
29304
+ return parsed.value;
29305
+ });
28928
29306
  const newsletterFieldSchema = z.object({
28929
29307
  id: z.string(),
28930
29308
  type: z.enum(["email", "text"]),
@@ -28932,73 +29310,73 @@ const newsletterFieldSchema = z.object({
28932
29310
  required: z.boolean(),
28933
29311
  placeholder: z.string().optional()
28934
29312
  });
28935
- const newsletterSignupBlockIdSchema = z.string().trim().min(1).transform(parseRequiredNewsletterSignupBlockId);
28936
- const signedNewsletterDestinationRefSchema = z.string().trim().min(1).transform(parseRequiredSignedNewsletterDestinationRef);
29313
+ const mailingListSignupBlockIdSchema = z.string().trim().min(1).transform(parseRequiredMailingListSignupBlockId);
29314
+ const signedMailingListSourceTokenSchema = z.string().trim().min(1).transform(parseRequiredMailingListSignedSourceToken);
28937
29315
  const newsletterSourceCommonSchema = z.object({
28938
29316
  siteId: z.string().optional(),
28939
29317
  apiBaseUrl: z.string().optional(),
28940
- preset: newsletterPresetSchema.optional(),
28941
- includePhone: z.boolean().optional(),
28942
- buttonLabel: z.string().nullable().optional(),
29318
+ mailingList: mailingListContextSchema.optional(),
29319
+ targeting: mailingListTargetingSchema.optional(),
28943
29320
  buttonVariant: newsletterButtonVariantSchema.nullable().optional(),
28944
- successMessage: z.string().nullable().optional(),
28945
- privacyNote: z.string().nullable().optional(),
28946
- tags: z.string().nullable().optional(),
28947
29321
  spamProtectionEnabled: z.boolean().optional(),
28948
29322
  className: z.string().nullable().optional()
28949
29323
  });
28950
29324
  const newsletterSourceSchema = z.discriminatedUnion("source", [
28951
29325
  newsletterSourceCommonSchema.extend({
28952
29326
  source: z.literal("block"),
28953
- blockId: z.string().nullable().optional(),
28954
- listId: z.string().nullable().optional(),
28955
- destinationProvider: newsletterDestinationProviderSchema.nullable().optional()
29327
+ blockId: z.string().nullable().optional()
28956
29328
  }),
28957
29329
  newsletterSourceCommonSchema.extend({
28958
29330
  source: z.literal("signed-footer"),
28959
- destinationRef: z.object({
28960
- tag: z.literal("signed_destination_ref"),
28961
- token: signedNewsletterDestinationRefSchema
29331
+ sourceRef: z.object({
29332
+ tag: z.literal("signed_source_ref"),
29333
+ token: signedMailingListSourceTokenSchema
28962
29334
  })
28963
29335
  }),
28964
- newsletterSourceCommonSchema.extend({
28965
- source: z.literal("legacy-direct-list"),
28966
- listId: z.string().nullable()
28967
- })
29336
+ newsletterSourceCommonSchema.extend({ source: z.literal("preview") })
28968
29337
  ]);
28969
29338
  const newsletterDisplaySchema = z.object({
28970
29339
  kind: z.literal("newsletter-form"),
29340
+ availability: z.enum(["ready", "unavailable"]),
28971
29341
  className: z.string().nullable(),
28972
29342
  buttonLabel: z.string(),
28973
29343
  buttonVariant: newsletterButtonVariantSchema,
28974
- successMessage: z.string().nullable(),
28975
29344
  privacyNote: z.string().nullable(),
28976
- fields: z.array(newsletterFieldSchema)
29345
+ fields: z.array(newsletterFieldSchema),
29346
+ choices: z.array(z.object({
29347
+ id: z.string(),
29348
+ label: z.string()
29349
+ }))
28977
29350
  });
28978
29351
  const newsletterHydrationSchema = z.object({
28979
29352
  target: z.discriminatedUnion("tag", [
28980
29353
  z.object({
28981
29354
  tag: z.literal("block-source"),
28982
- destinationRef: z.object({
29355
+ sourceRef: z.object({
28983
29356
  tag: z.literal("block_ref"),
28984
- blockId: newsletterSignupBlockIdSchema
29357
+ blockId: mailingListSignupBlockIdSchema
28985
29358
  }).nullable()
28986
29359
  }),
28987
29360
  z.object({
28988
29361
  tag: z.literal("signed-footer-source"),
28989
- destinationRef: z.object({
28990
- tag: z.literal("signed_destination_ref"),
28991
- token: signedNewsletterDestinationRefSchema
29362
+ sourceRef: z.object({
29363
+ tag: z.literal("signed_source_ref"),
29364
+ token: signedMailingListSourceTokenSchema
28992
29365
  })
28993
29366
  }),
28994
- z.object({
28995
- tag: z.literal("legacy-direct-list"),
28996
- listId: z.string().nullable()
28997
- })
29367
+ z.object({ tag: z.literal("preview") })
29368
+ ]),
29369
+ setupRevision: z.number().int().positive().nullable(),
29370
+ choiceProvider: z.enum([
29371
+ "native",
29372
+ "brevo",
29373
+ "mailchimp"
29374
+ ]).nullable(),
29375
+ choicePresentation: z.enum([
29376
+ "hidden",
29377
+ "automatic",
29378
+ "visitor_choice"
28998
29379
  ]),
28999
- preset: newsletterPresetSchema,
29000
- includePhone: z.boolean(),
29001
- tags: z.string().nullable().optional(),
29002
29380
  spamProtectionEnabled: z.boolean().optional()
29003
29381
  });
29004
29382
  const newsletterRenderSchema = z.object({
@@ -29010,16 +29388,24 @@ z.object({
29010
29388
  apiBaseUrl: z.string().optional()
29011
29389
  });
29012
29390
  function buildNewsletterDisplay(source) {
29013
- const preset = source.preset ?? "emailOnly";
29014
- const includePhone = source.includePhone ?? false;
29391
+ const context = source.mailingList;
29392
+ const form = context?.tag === "configured" ? context.form : {
29393
+ fields: { preset: "emailOnly" },
29394
+ includeOptionalPhone: false,
29395
+ buttonLabel: "Subscribe",
29396
+ privacyNote: null
29397
+ };
29398
+ const targeting = source.targeting ?? { mode: "default" };
29399
+ const choices = resolveRuntimeChoices(context, targeting);
29015
29400
  return {
29016
29401
  kind: "newsletter-form",
29402
+ availability: runtimeTargetingIsAvailable(context, targeting, choices) ? "ready" : "unavailable",
29017
29403
  className: source.className ?? null,
29018
- buttonLabel: source.buttonLabel?.trim() || "Subscribe",
29404
+ buttonLabel: form.buttonLabel,
29019
29405
  buttonVariant: source.buttonVariant ?? "primary",
29020
- successMessage: source.successMessage ?? "Thanks for subscribing!",
29021
- privacyNote: source.privacyNote ?? null,
29022
- fields: getPresetFields(preset, { includePhone })
29406
+ privacyNote: form.privacyNote,
29407
+ fields: getGlobalFormFields(form),
29408
+ choices
29023
29409
  };
29024
29410
  }
29025
29411
  const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
@@ -29029,14 +29415,15 @@ const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
29029
29415
  render: createZodCodec(newsletterRenderSchema)
29030
29416
  },
29031
29417
  buildRender: (source) => {
29032
- const runtimeSource = source.source === "block" ? deriveNewsletterSignupRuntimeSource(source) : null;
29418
+ const targeting = source.targeting ?? { mode: "default" };
29419
+ const presentation = planMailingListChoicePresentation(targeting);
29033
29420
  return {
29034
29421
  display: buildNewsletterDisplay(source),
29035
29422
  hydration: {
29036
29423
  target: projectNewsletterFormHydrationTarget(source),
29037
- preset: source.preset ?? "emailOnly",
29038
- includePhone: source.includePhone ?? false,
29039
- tags: runtimeSource ? runtimeSource.tags : source.tags,
29424
+ setupRevision: source.mailingList?.tag === "configured" ? source.mailingList.revision : null,
29425
+ choiceProvider: targeting.mode === "custom" ? targeting.provider : null,
29426
+ choicePresentation: presentation.tag,
29040
29427
  spamProtectionEnabled: source.spamProtectionEnabled
29041
29428
  }
29042
29429
  };
@@ -29045,35 +29432,87 @@ const newsletterFormBlockRuntime = defineProgressiveBlockRuntime({
29045
29432
  function projectNewsletterFormHydrationTarget(source) {
29046
29433
  switch (source.source) {
29047
29434
  case "block": {
29048
- const destinationRef = buildNewsletterSignupBlockRef(source.blockId);
29049
- if (destinationRef?.tag === "block_ref") return {
29435
+ const sourceRef = buildMailingListSignupBlockSourceRef(source.blockId);
29436
+ if (sourceRef?.tag === "block_ref") return {
29050
29437
  tag: "block-source",
29051
- destinationRef
29438
+ sourceRef
29052
29439
  };
29053
29440
  return {
29054
- tag: "legacy-direct-list",
29055
- listId: source.listId ?? null
29441
+ tag: "block-source",
29442
+ sourceRef: null
29056
29443
  };
29057
29444
  }
29058
29445
  case "signed-footer": return {
29059
29446
  tag: "signed-footer-source",
29060
- destinationRef: source.destinationRef
29061
- };
29062
- case "legacy-direct-list": return {
29063
- tag: "legacy-direct-list",
29064
- listId: source.listId
29447
+ sourceRef: source.sourceRef
29065
29448
  };
29449
+ case "preview": return { tag: "preview" };
29066
29450
  default: return assertNever$1(source);
29067
29451
  }
29068
29452
  }
29069
- function parseRequiredNewsletterSignupBlockId(value) {
29070
- const parsed = parseNewsletterSignupBlockId(value);
29071
- if (!parsed) throw new Error("Newsletter signup block destination ref requires blockId");
29453
+ function resolveRuntimeChoices(context, targeting) {
29454
+ if (context?.tag !== "configured" || targeting.mode !== "custom" || targeting.selection.tag !== "visitor_choice" || context.provider.provider !== targeting.provider) return [];
29455
+ switch (targeting.provider) {
29456
+ case "native": {
29457
+ const offered = new Set(targeting.selection.offeredTopicIds);
29458
+ return context.provider.provider === "native" ? context.provider.topics.filter((item) => offered.has(item.id)).map((item) => ({
29459
+ id: item.id,
29460
+ label: item.name
29461
+ })) : [];
29462
+ }
29463
+ case "brevo": {
29464
+ const offered = new Set(targeting.selection.offeredListIds);
29465
+ return context.provider.provider === "brevo" ? context.provider.lists.filter((item) => offered.has(item.id)).map((item) => ({
29466
+ id: String(item.id),
29467
+ label: item.name
29468
+ })) : [];
29469
+ }
29470
+ case "mailchimp": {
29471
+ const offered = new Set(targeting.selection.offeredInterestIds);
29472
+ 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) => ({
29473
+ id: item.id,
29474
+ label: item.name
29475
+ })) ?? [];
29476
+ }
29477
+ }
29478
+ }
29479
+ function runtimeTargetingIsAvailable(context, targeting, choices) {
29480
+ if (context?.tag !== "configured") return false;
29481
+ const provider = context.provider;
29482
+ if (targeting.mode === "default") switch (provider.provider) {
29483
+ case "native": return provider.topics.some((item) => item.id === provider.defaultTopicId);
29484
+ case "brevo": return provider.lists.some((item) => item.id === provider.defaultListId);
29485
+ case "mailchimp": return provider.audiences.some((item) => item.id === provider.defaultAudienceId);
29486
+ }
29487
+ if (targeting.provider !== provider.provider) return false;
29488
+ switch (targeting.provider) {
29489
+ case "native": return provider.provider === "native" && nativeTargetIsAvailable(provider, targeting, choices);
29490
+ case "brevo": return provider.provider === "brevo" && brevoTargetIsAvailable(provider, targeting, choices);
29491
+ case "mailchimp": return provider.provider === "mailchimp" && mailchimpTargetIsAvailable(provider, targeting, choices);
29492
+ }
29493
+ }
29494
+ function nativeTargetIsAvailable(provider, targeting, choices) {
29495
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.topicIds : targeting.selection.offeredTopicIds;
29496
+ return ids.every((id) => provider.topics.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
29497
+ }
29498
+ function brevoTargetIsAvailable(provider, targeting, choices) {
29499
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.listIds : targeting.selection.offeredListIds;
29500
+ return ids.every((id) => provider.lists.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
29501
+ }
29502
+ function mailchimpTargetIsAvailable(provider, targeting, choices) {
29503
+ const audience = provider.audiences.find((item) => item.id === targeting.audienceId);
29504
+ if (!audience) return false;
29505
+ const ids = targeting.selection.tag === "fixed" ? targeting.selection.interestIds : targeting.selection.offeredInterestIds;
29506
+ return ids.every((id) => audience.interests.some((item) => item.id === id)) && (targeting.selection.tag !== "visitor_choice" || choices.length === ids.length);
29507
+ }
29508
+ function parseRequiredMailingListSignupBlockId(value) {
29509
+ const parsed = parseMailingListBlockId(value);
29510
+ if (!parsed) throw new Error("Mailing-list signup block source ref requires blockId");
29072
29511
  return parsed;
29073
29512
  }
29074
- function parseRequiredSignedNewsletterDestinationRef(value) {
29075
- const parsed = parseSignedNewsletterDestinationRef(value);
29076
- if (!parsed) throw new Error("Newsletter signup signed destination ref requires token");
29513
+ function parseRequiredMailingListSignedSourceToken(value) {
29514
+ const parsed = parseMailingListSignedSourceToken(value);
29515
+ if (!parsed) throw new Error("Mailing-list signup signed source ref requires token");
29077
29516
  return parsed;
29078
29517
  }
29079
29518
  //#endregion
@@ -30446,6 +30885,14 @@ const systemBlockDefinitions = [
30446
30885
  weight: "standard"
30447
30886
  })]
30448
30887
  })),
30888
+ withDesignCapability("block.appointment-services", appointmentServicesBlockDefinition, sectionOnly(), defineBlockDesignMetadata({
30889
+ flow: conversionFlow,
30890
+ layoutProfiles: [layoutProfile({
30891
+ variantKey: "default",
30892
+ family: "stack",
30893
+ weight: "standard"
30894
+ })]
30895
+ })),
30449
30896
  withDesignCapability("block.weekly-slot-availability", weeklySlotAvailabilityBlockDefinition, sectionOnly(), defineBlockDesignMetadata({
30450
30897
  flow: offeringsFlow,
30451
30898
  layoutProfiles: [layoutProfile({