@riverbankcms/sdk 0.91.0 → 0.93.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 (118) hide show
  1. package/dist/{PreviewEditorSidebar-3TT47WCF.mjs → PreviewEditorSidebar-D3PAF7NW.mjs} +3 -3
  2. package/dist/{PreviewEditorUI-ORCLLUNZ.mjs → PreviewEditorUI-G5JVJEIJ.mjs} +3 -3
  3. package/dist/SdkPreviewModeRuntime-NHTLRNQD.mjs +8 -0
  4. package/dist/_dts/api/src/bookingConfig.d.ts +13 -1
  5. package/dist/_dts/api/src/bookingOperations.d.ts +1 -0
  6. package/dist/_dts/api/src/bookingRefundOutcomes.d.ts +6 -6
  7. package/dist/_dts/api/src/common/envelope.d.ts +10 -0
  8. package/dist/_dts/api/src/customerMembershipOutcomes.d.ts +4 -4
  9. package/dist/_dts/api/src/index.d.ts +2 -2
  10. package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +6 -0
  11. package/dist/_dts/api/src/request.d.ts +1 -1
  12. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +32 -0
  13. package/dist/_dts/blocks/src/system/blocks/courses/shared/pricing.d.ts +12 -1
  14. package/dist/_dts/blocks/src/system/blocks/courses/shared/schemas.d.ts +12 -0
  15. package/dist/_dts/blocks/src/system/blocks/courses/shared/types.d.ts +4 -1
  16. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +44 -0
  17. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +8 -0
  18. package/dist/_dts/blocks/src/system/runtime/api/types.d.ts +8 -1
  19. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.d.ts +8 -1
  20. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.d.ts +1 -0
  21. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +4 -0
  22. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.interactive.d.ts +40 -0
  23. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.reserved-fields.d.ts +1 -1
  24. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +29 -0
  25. package/dist/_dts/blocks/src/system/runtime/nodes/booking-runtime-sources.d.ts +1 -0
  26. package/dist/_dts/blocks/src/system/runtime/nodes/course-registration/components.d.ts +6 -0
  27. package/dist/_dts/blocks/src/system/runtime/nodes/course-registration/useCourseRegistrationWizard.d.ts +8 -0
  28. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/TicketSelectionStep.d.ts +8 -0
  29. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +1 -0
  30. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +1 -1
  31. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +1 -0
  32. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +9 -0
  33. package/dist/_dts/content-editor/src/controller/EntryControllerProvider.d.ts +14 -14
  34. package/dist/_dts/content-editor/src/controller/useEntrySyncHelpers.d.ts +1 -1
  35. package/dist/_dts/content-editor/src/hooks/useEntryActions.d.ts +5 -15
  36. package/dist/_dts/content-editor/src/ui/editorSnapshot.d.ts +46 -0
  37. package/dist/_dts/content-editor/src/ui/editorUiStore.d.ts +43 -36
  38. package/dist/_dts/content-model/src/entry-slots/types.d.ts +110 -2
  39. package/dist/_dts/core/src/branded.d.ts +3 -0
  40. package/dist/_dts/core/src/event-pricing.d.ts +18 -1
  41. package/dist/_dts/core/src/money-input.d.ts +34 -4
  42. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +70 -0
  43. package/dist/_dts/media-core/src/fileAssetCompatibility.d.ts +1 -1
  44. package/dist/_dts/media-core/src/filePersistence.d.ts +5 -1
  45. package/dist/_dts/media-core/src/fileStorage.d.ts +1 -0
  46. package/dist/_dts/media-core/src/fileUploadFinalization.d.ts +29 -1
  47. package/dist/_dts/media-core/src/index.d.ts +1 -0
  48. package/dist/_dts/media-core/src/legacyMediaValue.d.ts +11 -0
  49. package/dist/_dts/media-core/src/videoPrep.d.ts +15 -6
  50. package/dist/_dts/media-core/src/videoSlotPolicy.d.ts +5 -5
  51. package/dist/_dts/media-react/src/hooks/useApiMediaUploader.d.ts +2 -1
  52. package/dist/_dts/sdk/src/cli/commands/push/execute/diff.d.ts +2 -0
  53. package/dist/_dts/sdk/src/cli/commands/push/execute/localMediaReporting.d.ts +23 -0
  54. package/dist/_dts/sdk/src/cli/commands/push/scopes/commandBackedDatedOfferingScope.d.ts +54 -0
  55. package/dist/_dts/sdk/src/cli/commands/push/scopes/mediaPrep.d.ts +7 -2
  56. package/dist/_dts/sdk/src/cli/commands/push/scopes/types.d.ts +6 -0
  57. package/dist/_dts/sdk/src/cli/media/local-sync.d.ts +35 -0
  58. package/dist/_dts/sdk/src/rendering/islands/types.d.ts +23 -1
  59. package/dist/_dts/sdk/src/version.d.ts +1 -1
  60. package/dist/{chunk-GDT2QGIL.mjs → chunk-A63UXN3P.mjs} +21 -19
  61. package/dist/{chunk-U5YM4AWE.mjs → chunk-AJZGT6YG.mjs} +1 -1
  62. package/dist/{chunk-AFPMCLS7.mjs → chunk-AQ6RT6LZ.mjs} +2 -2
  63. package/dist/{chunk-ZA2S7H34.mjs → chunk-NT76QGIJ.mjs} +98 -1
  64. package/dist/{chunk-RQUZYSES.mjs → chunk-PCNCETBI.mjs} +1196 -426
  65. package/dist/{chunk-S3YHJWEU.mjs → chunk-XIK2HYI3.mjs} +7 -7
  66. package/dist/cli/index.mjs +749 -166
  67. package/dist/client/{accordion-AFX37652.mjs → accordion-RNQIPG55.mjs} +2 -2
  68. package/dist/client/{bookingForm-32V5QCOW.mjs → bookingForm-HYEEVRLP.mjs} +89 -14
  69. package/dist/client/bookings.mjs +243 -17
  70. package/dist/client/{carousel-TJGQJ7ER.mjs → carousel-W7DQ3YPU.mjs} +3 -3
  71. package/dist/client/{cart-JUU2JEW4.mjs → cart-PXJ3SXXO.mjs} +7 -7
  72. package/dist/client/{checkout-RD6BTFX4.mjs → checkout-X3JYSOXI.mjs} +7 -7
  73. package/dist/client/{chunk-MLBNVN4H.mjs → chunk-6SYGVVWA.mjs} +67 -4
  74. package/dist/client/{chunk-E6VIWZUG.mjs → chunk-DUOHLWLF.mjs} +3 -3
  75. package/dist/client/{chunk-6QZIBNGV.mjs → chunk-IXDSXF2D.mjs} +154 -16
  76. package/dist/client/{chunk-CKXEUWWA.mjs → chunk-J6TMMMXW.mjs} +3 -3
  77. package/dist/client/{chunk-PMNAV6LO.mjs → chunk-JNE6UI6U.mjs} +7 -6
  78. package/dist/client/{chunk-ISAUXU5R.mjs → chunk-OLAOAZEO.mjs} +1 -1
  79. package/dist/client/{chunk-RWYFG625.mjs → chunk-PCHACQEB.mjs} +30 -2
  80. package/dist/client/{chunk-FMZZHAIR.mjs → chunk-PICR3NTQ.mjs} +52 -5
  81. package/dist/client/{chunk-6RN62XBV.mjs → chunk-Q47XGAGS.mjs} +1 -1
  82. package/dist/client/{chunk-WHZQDPBZ.mjs → chunk-TNGL2WJF.mjs} +1 -0
  83. package/dist/client/{chunk-C4LP6BPG.mjs → chunk-WERRJXNJ.mjs} +1 -1
  84. package/dist/client/{chunk-RMOFBBNA.mjs → chunk-YYVGSYBO.mjs} +6 -6
  85. package/dist/client/client.mjs +641 -54
  86. package/dist/client/{courseRegistration-X63QAIVV.mjs → courseRegistration-QZ67PVHO.mjs} +111 -11
  87. package/dist/client/{eventCalendar-5T24GCZL.mjs → eventCalendar-V3BDHQNT.mjs} +9 -9
  88. package/dist/client/{eventCombined-AZVTF22X.mjs → eventCombined-XYFRN2RG.mjs} +9 -9
  89. package/dist/client/{eventDetails-7GPFNLGF.mjs → eventDetails-TFE5TWKV.mjs} +9 -9
  90. package/dist/client/{eventListing-QEUMTMUM.mjs → eventListing-IQ6AGWZ6.mjs} +9 -9
  91. package/dist/client/{eventRegistration-P2EDKCQZ.mjs → eventRegistration-WGJVFKQJ.mjs} +9 -9
  92. package/dist/client/{form-NUJ7H4IN.mjs → form-6UZY6RUA.mjs} +3 -3
  93. package/dist/client/{headerSection-JB7LECPL.mjs → headerSection-RSFXY6V3.mjs} +3 -3
  94. package/dist/client/hooks.mjs +57 -5
  95. package/dist/client/{locationMap-UEYWUCSW.mjs → locationMap-24FH67I4.mjs} +3 -3
  96. package/dist/client/{newsletterForm-S2YZQ2X7.mjs → newsletterForm-QVL3MLTN.mjs} +3 -3
  97. package/dist/client/{productDetail-H5MADGG7.mjs → productDetail-ZDYN3G4T.mjs} +7 -7
  98. package/dist/client/{productList-DK7YON7H.mjs → productList-4BMJLQHZ.mjs} +7 -7
  99. package/dist/client/rendering/client.mjs +503 -36
  100. package/dist/client/rendering/islands.mjs +19 -19
  101. package/dist/client/rendering.mjs +583 -39
  102. package/dist/client/{shop-6PA7RDRJ.mjs → shop-U5C634FF.mjs} +7 -7
  103. package/dist/preview-next/client/runtime.mjs +5 -5
  104. package/dist/{sdk-runtime-JP5347OB.mjs → sdk-runtime-VGV46WVB.mjs} +6 -6
  105. package/dist/server/components.mjs +287 -68
  106. package/dist/server/config-validation.mjs +58 -5
  107. package/dist/server/config.mjs +58 -5
  108. package/dist/server/data.mjs +57 -5
  109. package/dist/server/index.mjs +104 -29
  110. package/dist/server/next.mjs +347 -85
  111. package/dist/server/page-converter.mjs +22 -5
  112. package/dist/server/prebuild.mjs +1 -1
  113. package/dist/server/rendering/server.mjs +287 -68
  114. package/dist/server/rendering.mjs +287 -68
  115. package/dist/server/routing.mjs +131 -67
  116. package/dist/server/server.mjs +189 -94
  117. package/package.json +1 -1
  118. package/dist/SdkPreviewModeRuntime-GMCDQPZZ.mjs +0 -8
@@ -117,12 +117,13 @@ var init_result = __esm({
117
117
  });
118
118
 
119
119
  // ../core/src/branded.ts
120
- var unsafeAsMediaAssetId, unsafeAsStorageBucketName, unsafeAsPositiveByteCount, unsafeAsPositiveSeconds, unsafeAsFormId, unsafeAsPassId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, unsafeAsAppointmentResourceId;
120
+ var unsafeAsMediaAssetId, unsafeAsStorageBucketName, unsafeAsStoragePath, unsafeAsPositiveByteCount, unsafeAsPositiveSeconds, unsafeAsFormId, unsafeAsPassId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, unsafeAsAppointmentResourceId;
121
121
  var init_branded = __esm({
122
122
  "../core/src/branded.ts"() {
123
123
  "use strict";
124
124
  unsafeAsMediaAssetId = (id) => id;
125
125
  unsafeAsStorageBucketName = (value) => value;
126
+ unsafeAsStoragePath = (value) => value;
126
127
  unsafeAsPositiveByteCount = (value) => value;
127
128
  unsafeAsPositiveSeconds = (value) => value;
128
129
  unsafeAsFormId = (id) => id;
@@ -268,6 +269,7 @@ function publicEventPricingType(pricing) {
268
269
  return "free";
269
270
  case "single_paid":
270
271
  case "ticket_types":
272
+ case "sliding_scale":
271
273
  case "course":
272
274
  return isFreePublicEventPriceDisplay(pricing.display) ? "free" : "paid";
273
275
  case "external":
@@ -280,6 +282,7 @@ function publicEventPricingCurrency(pricing) {
280
282
  case "free_registration":
281
283
  case "single_paid":
282
284
  case "ticket_types":
285
+ case "sliding_scale":
283
286
  return pricing.currency;
284
287
  case "course":
285
288
  return pricing.display.display.currency;
@@ -297,6 +300,8 @@ function publicEventPricingMode(pricing) {
297
300
  return "single";
298
301
  case "ticket_types":
299
302
  return "ticket_types";
303
+ case "sliding_scale":
304
+ return "sliding_scale";
300
305
  case "drop_in":
301
306
  case "course":
302
307
  case "external":
@@ -330,6 +335,8 @@ function formatCoursePriceDisplayLabel(display, options) {
330
335
  return `Course (${formatCurrencyAmount(display.amount, display.currency, options)})`;
331
336
  case "from":
332
337
  return `Course (From ${formatCurrencyAmount(display.amount, display.currency, options)})`;
338
+ case "range":
339
+ return `Course (${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)})`;
333
340
  default:
334
341
  return assertNever(display);
335
342
  }
@@ -1074,9 +1081,20 @@ var init_src = __esm({
1074
1081
  });
1075
1082
 
1076
1083
  // ../media-core/src/fileSafety.ts
1084
+ function containsUnsafePathSegmentCharacter(value) {
1085
+ return value.includes("/") || value.includes("\\") || CONTROL_CHARACTER_PATTERN.test(value);
1086
+ }
1087
+ function isReservedPathSegment(value) {
1088
+ return value === "." || value === "..";
1089
+ }
1090
+ function isUnsafePathSegment(segment) {
1091
+ return segment.trim().length === 0 || segment !== segment.trim() || isReservedPathSegment(segment) || containsUnsafePathSegmentCharacter(segment);
1092
+ }
1093
+ var CONTROL_CHARACTER_PATTERN;
1077
1094
  var init_fileSafety = __esm({
1078
1095
  "../media-core/src/fileSafety.ts"() {
1079
1096
  "use strict";
1097
+ CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
1080
1098
  }
1081
1099
  });
1082
1100
 
@@ -1116,11 +1134,21 @@ var init_fileDomain = __esm({
1116
1134
  });
1117
1135
 
1118
1136
  // ../media-core/src/fileStorage.ts
1137
+ function parseStoragePath(value) {
1138
+ return joinStoragePathSegments(value.split("/"));
1139
+ }
1140
+ function joinStoragePathSegments(segments) {
1141
+ if (segments.some(isUnsafePathSegment)) {
1142
+ return err("unsafe-path-segment");
1143
+ }
1144
+ return ok(unsafeAsStoragePath(segments.join("/")));
1145
+ }
1119
1146
  var FILE_STORAGE_BUCKETS, PUBLIC_SITE_FILE_STORAGE_BUCKETS, PRIVATE_SITE_FILE_BUCKET_MIME_TYPES, TEMP_UPLOAD_BUCKET_MIME_TYPES;
1120
1147
  var init_fileStorage = __esm({
1121
1148
  "../media-core/src/fileStorage.ts"() {
1122
1149
  "use strict";
1123
1150
  init_src();
1151
+ init_fileSafety();
1124
1152
  FILE_STORAGE_BUCKETS = {
1125
1153
  publicSite: unsafeAsStorageBucketName("media"),
1126
1154
  privateSite: unsafeAsStorageBucketName("site-files-private"),
@@ -1306,14 +1334,18 @@ var init_uploadVerificationContract = __esm({
1306
1334
  });
1307
1335
 
1308
1336
  // ../media-core/src/videoPrep.ts
1337
+ function unsafeAsPositiveVideoDurationSeconds(value) {
1338
+ return value;
1339
+ }
1309
1340
  var VIDEO_PREP_LIMITS;
1310
1341
  var init_videoPrep = __esm({
1311
1342
  "../media-core/src/videoPrep.ts"() {
1312
1343
  "use strict";
1344
+ init_src();
1313
1345
  VIDEO_PREP_LIMITS = {
1314
- maxRawByteCount: 500 * 1024 * 1024,
1315
- inlineContentMaxDurationSeconds: 60,
1316
- backgroundVideoMaxDurationSeconds: 30,
1346
+ maxRawByteCount: unsafeAsPositiveByteCount(500 * 1024 * 1024),
1347
+ inlineContentMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(60),
1348
+ backgroundVideoMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(30),
1317
1349
  targetMaxDimensionPixels: 1080
1318
1350
  };
1319
1351
  }
@@ -1384,10 +1416,10 @@ var init_identifiers = __esm({
1384
1416
 
1385
1417
  // ../media-core/src/url.ts
1386
1418
  function isRelativeDirectImageStoragePath(storagePath) {
1387
- return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix));
1419
+ return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix)) && parseStoragePath(storagePath).ok;
1388
1420
  }
1389
1421
  function isSiteScopedDirectImageStoragePath(storagePath) {
1390
- return storagePath.startsWith("sites/");
1422
+ return storagePath.startsWith("sites/") && parseStoragePath(storagePath).ok;
1391
1423
  }
1392
1424
  function encodeStoragePath(storagePath) {
1393
1425
  return storagePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
@@ -1468,6 +1500,7 @@ var DEFAULT_BASE_PATH, RELATIVE_STORAGE_PATH_PREFIXES, formatNumber, ORDER, getI
1468
1500
  var init_url = __esm({
1469
1501
  "../media-core/src/url.ts"() {
1470
1502
  "use strict";
1503
+ init_fileStorage();
1471
1504
  DEFAULT_BASE_PATH = "/api/img";
1472
1505
  RELATIVE_STORAGE_PATH_PREFIXES = [
1473
1506
  "originals/",
@@ -1711,6 +1744,28 @@ var init_renderableMediaAsset = __esm({
1711
1744
  }
1712
1745
  });
1713
1746
 
1747
+ // ../media-core/src/legacyMediaValue.ts
1748
+ function legacyStringMediaTypeForField(field) {
1749
+ if (field.type === "file") {
1750
+ return field.fileKinds?.[0] ?? "document";
1751
+ }
1752
+ return field.mediaKinds?.[0] ?? "image";
1753
+ }
1754
+ function normalizeLegacyStringMediaValueForField(value, field) {
1755
+ if (typeof value !== "string") return value;
1756
+ const src = value.trim();
1757
+ if (!src) return value;
1758
+ return {
1759
+ type: legacyStringMediaTypeForField(field),
1760
+ src
1761
+ };
1762
+ }
1763
+ var init_legacyMediaValue = __esm({
1764
+ "../media-core/src/legacyMediaValue.ts"() {
1765
+ "use strict";
1766
+ }
1767
+ });
1768
+
1714
1769
  // ../media-core/src/index.ts
1715
1770
  var init_src2 = __esm({
1716
1771
  "../media-core/src/index.ts"() {
@@ -1753,6 +1808,7 @@ var init_src2 = __esm({
1753
1808
  init_mediaUsageProjection();
1754
1809
  init_portableMediaReferences();
1755
1810
  init_renderableMediaAsset();
1811
+ init_legacyMediaValue();
1756
1812
  }
1757
1813
  });
1758
1814
 
@@ -2051,9 +2107,12 @@ var init_schemas = __esm({
2051
2107
  });
2052
2108
  coursePricingSchema = z5.object({
2053
2109
  bookingRequirement: z5.enum(["none", "required"]),
2054
- pricingStrategy: z5.enum(["free", "single", "tiered"]),
2110
+ pricingStrategy: z5.enum(["free", "single", "tiered", "sliding_scale"]),
2055
2111
  currency: z5.string(),
2056
2112
  singlePriceAmount: z5.number().nullable(),
2113
+ slidingMinAmount: z5.number().nullable().optional(),
2114
+ slidingMaxAmount: z5.number().nullable().optional(),
2115
+ slidingSuggestedAmount: z5.number().nullable().optional(),
2057
2116
  paymentTerms: blockBookingPaymentTermsSchema,
2058
2117
  options: z5.array(coursePricingOptionSchema)
2059
2118
  });
@@ -2218,6 +2277,19 @@ var init_schemas2 = __esm({
2218
2277
  ticketTypes: z6.array(publicEventTicketTypeSchema),
2219
2278
  display: publicEventTicketTypePriceDisplaySchema
2220
2279
  }),
2280
+ z6.object({
2281
+ kind: z6.literal("sliding_scale"),
2282
+ minAmount: z6.number(),
2283
+ maxAmount: z6.number(),
2284
+ suggestedAmount: z6.number().nullable(),
2285
+ currency: z6.string(),
2286
+ display: z6.object({
2287
+ kind: z6.literal("range"),
2288
+ minAmount: z6.number(),
2289
+ maxAmount: z6.number(),
2290
+ currency: z6.string()
2291
+ })
2292
+ }),
2221
2293
  z6.object({
2222
2294
  kind: z6.literal("course"),
2223
2295
  display: z6.object({ kind: z6.literal("course"), display: publicCoursePriceDisplaySchema })
@@ -23338,6 +23410,7 @@ var init_booking_form_reserved_fields = __esm({
23338
23410
  "selectedDate",
23339
23411
  "selectedSlot",
23340
23412
  "discountCode",
23413
+ "selectedSlidingAmountInput",
23341
23414
  "selectedCollectionOptionKind",
23342
23415
  "selectedAppointmentPackageId"
23343
23416
  ];
@@ -23623,6 +23696,32 @@ function readOptionalString(value) {
23623
23696
  function readOptionalNumber(value) {
23624
23697
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
23625
23698
  }
23699
+ function getBookingServiceSlidingScale(service) {
23700
+ const pricing = service?.pricing;
23701
+ if (!pricing || pricing.pricingStrategy !== "sliding_scale" || pricing.slidingMinAmount === null || pricing.slidingMinAmount === void 0 || pricing.slidingMaxAmount === null || pricing.slidingMaxAmount === void 0) {
23702
+ return null;
23703
+ }
23704
+ return {
23705
+ minAmount: pricing.slidingMinAmount,
23706
+ maxAmount: pricing.slidingMaxAmount,
23707
+ suggestedAmount: pricing.slidingSuggestedAmount ?? null,
23708
+ currency: pricing.currency
23709
+ };
23710
+ }
23711
+ function parseBookingCurrencyInputToCents(value) {
23712
+ const trimmed = value?.trim() ?? "";
23713
+ if (!trimmed) return null;
23714
+ if (!/^(?:\d+(?:\.\d{0,2})?|\.\d{1,2})$/.test(trimmed)) return null;
23715
+ const parsed = Number(trimmed);
23716
+ if (!Number.isFinite(parsed) || parsed < 0) return null;
23717
+ return Math.round(parsed * 100);
23718
+ }
23719
+ function formatBookingCentsAsCurrencyInput(amount) {
23720
+ return amount % 100 === 0 ? String(amount / 100) : (amount / 100).toFixed(2);
23721
+ }
23722
+ function isBookingServiceSlidingAmountInRange(slidingScale, selectedAmount) {
23723
+ return selectedAmount !== null && selectedAmount >= slidingScale.minAmount && selectedAmount <= slidingScale.maxAmount;
23724
+ }
23626
23725
  function readStringArray(value) {
23627
23726
  if (!Array.isArray(value)) return void 0;
23628
23727
  const strings = value.filter((item) => typeof item === "string");
@@ -23811,6 +23910,20 @@ function prepareBookingSubmission(input) {
23811
23910
  };
23812
23911
  }
23813
23912
  const paymentSelectionMode = getBookingServicePaymentSelectionMode(selectedService);
23913
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
23914
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
23915
+ input.data.selectedSlidingAmountInput
23916
+ );
23917
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(slidingScale, selectedSlidingAmount)) {
23918
+ return {
23919
+ ok: false,
23920
+ error: {
23921
+ tag: selectedSlidingAmount === null ? "sliding-amount-required" : "sliding-amount-out-of-range",
23922
+ serviceId,
23923
+ serviceTitle: selectedService.title || serviceId
23924
+ }
23925
+ };
23926
+ }
23814
23927
  if (paymentSelectionMode === "choice_required" && !toSelectedCollectionOptionKind(input.data.selectedCollectionOptionKind)) {
23815
23928
  return {
23816
23929
  ok: false,
@@ -23838,6 +23951,7 @@ function prepareBookingSubmission(input) {
23838
23951
  ...common,
23839
23952
  kind: "standard",
23840
23953
  ...discountCode ? { discountCode } : {},
23954
+ ...slidingScale && selectedSlidingAmount !== null ? { selectedSlidingAmount } : {},
23841
23955
  ...selectedCollectionOptionKind ? { selectedCollectionOptionKind } : {},
23842
23956
  altchaSolution: input.altchaSolution ?? null
23843
23957
  }
@@ -23851,6 +23965,10 @@ function formatBookingSubmissionPreparationError(error) {
23851
23965
  return `Service not found: ${error.serviceId}`;
23852
23966
  case "payment-option-required":
23853
23967
  return `Please choose how you want to pay for "${error.serviceTitle}".`;
23968
+ case "sliding-amount-required":
23969
+ return `Please choose an amount for "${error.serviceTitle}".`;
23970
+ case "sliding-amount-out-of-range":
23971
+ return `Please choose an amount within the sliding scale for "${error.serviceTitle}".`;
23854
23972
  case "payment-option-unsupported":
23855
23973
  return `Service "${error.serviceTitle}" has a payment option that is not supported yet.`;
23856
23974
  case "service-missing-duration":
@@ -24001,6 +24119,7 @@ var init_booking_form_interactive = __esm({
24001
24119
  title: z36.string(),
24002
24120
  description: z36.string().nullable().optional().transform((value) => value ?? void 0),
24003
24121
  durationMinutes: z36.number().optional(),
24122
+ pricing: z36.custom().optional(),
24004
24123
  priceAmount: z36.number().nullable().optional(),
24005
24124
  priceCurrency: z36.string().nullable().optional(),
24006
24125
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
@@ -24989,7 +25108,7 @@ function isRecord6(value) {
24989
25108
  }
24990
25109
  function isPublicEventPricing(value) {
24991
25110
  if (!isRecord6(value) || typeof value.kind !== "string") return false;
24992
- return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "ticket_types" || value.kind === "course" || value.kind === "external";
25111
+ return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "sliding_scale" || value.kind === "ticket_types" || value.kind === "course" || value.kind === "external";
24993
25112
  }
24994
25113
  function isPublicEventTicketType(value) {
24995
25114
  return isRecord6(value) && typeof value.id === "string";
@@ -33593,12 +33712,13 @@ var init_carousel_client = __esm({
33593
33712
  ]
33594
33713
  );
33595
33714
  const resolvedSlides = resolveSlidesToShowMobile(normalized.slidesToShow);
33715
+ const canAutoplay = normalized.autoplay && slides.length > 1;
33596
33716
  const cssVars = React5.useMemo(
33597
33717
  () => getCarouselSlidesToShowCssVars(normalized.slidesToShow),
33598
33718
  [normalized.slidesToShow]
33599
33719
  );
33600
33720
  const plugins = React5.useMemo(() => {
33601
- if (!normalized.autoplay) return void 0;
33721
+ if (!canAutoplay) return void 0;
33602
33722
  return [
33603
33723
  Autoplay({
33604
33724
  delay: normalized.autoplayDelay,
@@ -33608,7 +33728,7 @@ var init_carousel_client = __esm({
33608
33728
  // Continue after manual navigation
33609
33729
  })
33610
33730
  ];
33611
- }, [normalized.autoplay, normalized.autoplayDelay]);
33731
+ }, [canAutoplay, normalized.autoplayDelay]);
33612
33732
  const [viewportRef, api] = useEmblaCarousel(
33613
33733
  {
33614
33734
  align: "start",
@@ -33647,7 +33767,7 @@ var init_carousel_client = __esm({
33647
33767
  };
33648
33768
  }, [api]);
33649
33769
  React5.useEffect(() => {
33650
- if (!api || !normalized.autoplay) return;
33770
+ if (!api || !canAutoplay) return;
33651
33771
  const autoplayPlugin = api.plugins()?.autoplay;
33652
33772
  if (!autoplayPlugin) return;
33653
33773
  const handleVisibility = () => {
@@ -33656,7 +33776,7 @@ var init_carousel_client = __esm({
33656
33776
  };
33657
33777
  document.addEventListener("visibilitychange", handleVisibility);
33658
33778
  return () => document.removeEventListener("visibilitychange", handleVisibility);
33659
- }, [api, normalized.autoplay]);
33779
+ }, [api, canAutoplay]);
33660
33780
  const inner = /* @__PURE__ */ jsxs13(Fragment3, { children: [
33661
33781
  /* @__PURE__ */ jsx15("div", { className: "carousel-frame rb-overflow-hidden rb-w-full", children: /* @__PURE__ */ jsx15(
33662
33782
  "div",
@@ -33707,7 +33827,7 @@ var init_carousel_client = __esm({
33707
33827
  "data-show-controls": normalized.showControls ? "true" : "false",
33708
33828
  "data-show-dots": normalized.showDots ? "true" : "false",
33709
33829
  "data-arrow-visibility": normalized.arrowVisibility,
33710
- "data-autoplay": normalized.autoplay ? "true" : "false",
33830
+ "data-autoplay": canAutoplay ? "true" : "false",
33711
33831
  "data-loop": normalized.loop ? "true" : "false",
33712
33832
  children: inner
33713
33833
  }
@@ -34710,6 +34830,7 @@ function createBlockApi(config) {
34710
34830
  body: {
34711
34831
  courseId: data.courseId,
34712
34832
  ...data.pricingOptionId ? { pricingOptionId: data.pricingOptionId } : {},
34833
+ ...data.selectedSlidingAmount !== void 0 ? { selectedSlidingAmount: data.selectedSlidingAmount } : {},
34713
34834
  email: data.email,
34714
34835
  name: data.name,
34715
34836
  phone: data.phone,
@@ -38947,6 +39068,7 @@ function useBookingSubmission(_siteId) {
38947
39068
  customFields: data.customFields,
38948
39069
  timezone: data.timezone,
38949
39070
  discountCode: data.discountCode,
39071
+ selectedSlidingAmount: data.selectedSlidingAmount,
38950
39072
  selectedCollectionOptionKind: data.selectedCollectionOptionKind,
38951
39073
  altchaSolution: data.altchaSolution ?? null,
38952
39074
  successUrl: data.successUrl,
@@ -39475,6 +39597,9 @@ function createInitialBookingFlowReducerState(input) {
39475
39597
  fieldValues: collectInitialFieldValues(initialData),
39476
39598
  touchedFields: {},
39477
39599
  discountCode: toOptionalString(initialData.discountCode) ?? "",
39600
+ selectedSlidingAmountInput: toOptionalString(
39601
+ initialData.selectedSlidingAmountInput
39602
+ ) ?? "",
39478
39603
  selectedCollectionOptionKind: toSelectedCollectionOptionKind(
39479
39604
  initialData.selectedCollectionOptionKind
39480
39605
  ),
@@ -39527,6 +39652,7 @@ function bookingFlowReducer(state, event) {
39527
39652
  selectedDate: void 0,
39528
39653
  selectedSlot: null,
39529
39654
  selectedAppointmentPackageId: void 0,
39655
+ selectedSlidingAmountInput: "",
39530
39656
  selectedCollectionOptionKind: void 0,
39531
39657
  validationErrors: clearBookingFormError(state.validationErrors)
39532
39658
  };
@@ -39580,6 +39706,13 @@ function bookingFlowReducer(state, event) {
39580
39706
  selectedAppointmentPackageId: void 0,
39581
39707
  validationErrors: clearBookingFormError(state.validationErrors)
39582
39708
  };
39709
+ case "sliding-amount-changed":
39710
+ return {
39711
+ ...state,
39712
+ selectedSlidingAmountInput: event.value,
39713
+ selectedAppointmentPackageId: void 0,
39714
+ validationErrors: clearBookingFormError(state.validationErrors)
39715
+ };
39583
39716
  case "appointment-package-selected":
39584
39717
  return {
39585
39718
  ...state,
@@ -39682,6 +39815,7 @@ function toBookingFormData(state) {
39682
39815
  selectedDate: state.selectedDate,
39683
39816
  selectedSlot: state.selectedSlot?.startAt,
39684
39817
  discountCode: state.discountCode || void 0,
39818
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput || void 0,
39685
39819
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
39686
39820
  selectedAppointmentPackageId: state.selectedAppointmentPackageId
39687
39821
  };
@@ -39699,7 +39833,8 @@ function buildBookingStepResolution(input) {
39699
39833
  input.form.settings?.serviceIds && input.form.settings.serviceIds.length > 1
39700
39834
  );
39701
39835
  const selectedService = paymentResolution.service;
39702
- const needsPayment = paymentResolution.mode !== "hidden" || Boolean(
39836
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
39837
+ const needsPayment = Boolean(selectedServiceSlidingScale) || paymentResolution.mode !== "hidden" || Boolean(
39703
39838
  input.enableAppointmentPackages && selectedService && (selectedService.priceAmount ?? 0) > 0
39704
39839
  );
39705
39840
  const hasFields = Boolean(input.form.schema?.fields.length);
@@ -39743,6 +39878,18 @@ function validateBookingStep(input) {
39743
39878
  services: input.services,
39744
39879
  defaultServiceId: input.defaultServiceId
39745
39880
  });
39881
+ const slidingScale = getBookingServiceSlidingScale(resolution.service);
39882
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(
39883
+ slidingScale,
39884
+ parseBookingCurrencyInputToCents(input.data.selectedSlidingAmountInput)
39885
+ )) {
39886
+ return {
39887
+ ok: false,
39888
+ errors: createFormBookingValidationError(
39889
+ "Please choose an amount within the sliding scale"
39890
+ )
39891
+ };
39892
+ }
39746
39893
  if (resolution.mode === "hidden" || resolution.mode === "disclosure_only") {
39747
39894
  return { ok: true };
39748
39895
  }
@@ -40141,6 +40288,7 @@ var init_useHeadlessBookingFlow_state = __esm({
40141
40288
  "../blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.ts"() {
40142
40289
  "use strict";
40143
40290
  init_booking_form_headless_state();
40291
+ init_booking_form_state();
40144
40292
  }
40145
40293
  });
40146
40294
 
@@ -40180,11 +40328,27 @@ function useHeadlessBookingFlow(options) {
40180
40328
  const selectedServiceId = state.selectedServiceId ?? defaultServiceId;
40181
40329
  const selectedResourceId = state.selectedResourceId ?? defaultResourceId;
40182
40330
  const selectedService = options.services.find((service) => service.id === selectedServiceId) ?? null;
40331
+ const lastSlidingServiceIdRef = useRef9(null);
40332
+ useEffect8(() => {
40333
+ const selectedId = selectedService?.id ?? null;
40334
+ if (lastSlidingServiceIdRef.current === selectedId) return;
40335
+ lastSlidingServiceIdRef.current = selectedId;
40336
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
40337
+ dispatch({
40338
+ type: "sliding-amount-changed",
40339
+ value: slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatBookingCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
40340
+ });
40341
+ }, [selectedService]);
40183
40342
  const hasPortalBearerAuth = (api.config.authOverrides?.portal ?? api.config.auth).type === "bearer";
40343
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
40344
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
40345
+ state.selectedSlidingAmountInput
40346
+ );
40347
+ const selectedAppointmentPriceAmount = selectedServiceSlidingScale && selectedSlidingAmount !== null ? selectedSlidingAmount : selectedService?.priceAmount ?? 0;
40184
40348
  const canLoadAppointmentPackages = Boolean(
40185
- options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && (selectedService.priceAmount ?? 0) > 0
40349
+ options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && !selectedServiceSlidingScale && (selectedService.priceAmount ?? 0) > 0
40186
40350
  );
40187
- const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
40351
+ const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || selectedServiceSlidingScale || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
40188
40352
  const data = useMemo6(() => toBookingFormData(state), [state]);
40189
40353
  const availabilityDatesQuery = useMemo6(
40190
40354
  () => buildBookingAvailabilityQuery({
@@ -40414,13 +40578,13 @@ function useHeadlessBookingFlow(options) {
40414
40578
  state.selectedSlot
40415
40579
  ]);
40416
40580
  const appointmentDiscountContext = useMemo6(
40417
- () => selectedService && (selectedService.priceAmount ?? 0) > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
40418
- [selectedService?.id, selectedService?.priceAmount]
40581
+ () => selectedService && selectedAppointmentPriceAmount > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
40582
+ [selectedAppointmentPriceAmount, selectedService?.id]
40419
40583
  );
40420
40584
  const discountUi = useDiscountCodeUi({
40421
40585
  api,
40422
40586
  discountContext: appointmentDiscountContext,
40423
- subtotalCents: selectedService?.priceAmount ?? 0
40587
+ subtotalCents: selectedAppointmentPriceAmount
40424
40588
  });
40425
40589
  const { submit, clearFeedback } = useBookingSubmission(options.siteId);
40426
40590
  const submittingRef = useRef9(false);
@@ -40563,6 +40727,7 @@ function useHeadlessBookingFlow(options) {
40563
40727
  type: "appointment-package-selected",
40564
40728
  packageId: packageId ? unsafeAsCustomerPassId(packageId) : null
40565
40729
  }),
40730
+ setSlidingAmountInput: (value) => dispatch({ type: "sliding-amount-changed", value }),
40566
40731
  setDiscountCode: (code) => {
40567
40732
  dispatch({ type: "discount-code-changed", code });
40568
40733
  latestRef.current.setDiscountCode(code);
@@ -40633,11 +40798,11 @@ function useHeadlessBookingFlow(options) {
40633
40798
  () => selectedService && paymentResolution.mode !== "unsupported" ? buildBookingPaymentSelectionViewModel({
40634
40799
  serviceTitle: selectedService.title,
40635
40800
  paymentTerms: selectedService.paymentTerms,
40636
- priceAmount: selectedService.priceAmount,
40801
+ priceAmount: selectedAppointmentPriceAmount,
40637
40802
  priceCurrency: selectedService.priceCurrency,
40638
40803
  bookingStartAt: state.selectedSlot?.startAt
40639
40804
  }) : null,
40640
- [paymentResolution.mode, selectedService, state.selectedSlot?.startAt]
40805
+ [paymentResolution.mode, selectedAppointmentPriceAmount, selectedService, state.selectedSlot?.startAt]
40641
40806
  );
40642
40807
  const appointmentPackageItems = appointmentPackages.tag === "loaded" ? appointmentPackages.value : [];
40643
40808
  const selectedAppointmentPackage = appointmentPackageItems.find(
@@ -40666,6 +40831,9 @@ function useHeadlessBookingFlow(options) {
40666
40831
  selection: null,
40667
40832
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
40668
40833
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
40834
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
40835
+ selectedSlidingAmount,
40836
+ slidingScale: selectedServiceSlidingScale,
40669
40837
  appointmentPackages: appointmentPackagesModel,
40670
40838
  errorMessage: paymentResolution.errorMessage ?? "This service cannot be booked right now."
40671
40839
  } : {
@@ -40674,6 +40842,9 @@ function useHeadlessBookingFlow(options) {
40674
40842
  selection: paymentSelection,
40675
40843
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
40676
40844
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
40845
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
40846
+ selectedSlidingAmount,
40847
+ slidingScale: selectedServiceSlidingScale,
40677
40848
  appointmentPackages: appointmentPackagesModel
40678
40849
  };
40679
40850
  return {
@@ -41368,6 +41539,9 @@ var init_FieldValidationMessage = __esm({
41368
41539
 
41369
41540
  // ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
41370
41541
  import { Fragment as Fragment5, jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
41542
+ function formatBookingAmount(amount, currency) {
41543
+ return formatCurrencyAmount(amount, currency, { decimals: "auto" });
41544
+ }
41371
41545
  function HeadlessBookingDefaultForm({
41372
41546
  booking,
41373
41547
  onSubmit
@@ -41644,6 +41818,7 @@ function HeadlessBookingPaymentSelectionStep({
41644
41818
  }
41645
41819
  const packageOptions = booking.payment.appointmentPackages.items;
41646
41820
  const hasPackageOptions = packageOptions.length > 0;
41821
+ const slidingScale = booking.payment.slidingScale;
41647
41822
  return /* @__PURE__ */ jsxs21("div", { className: "ab-stack", children: [
41648
41823
  /* @__PURE__ */ jsxs21("div", { className: "ab-info-box", children: [
41649
41824
  /* @__PURE__ */ jsx27("h3", { className: "ab-subtitle", children: viewModel.sectionTitle }),
@@ -41654,6 +41829,28 @@ function HeadlessBookingPaymentSelectionStep({
41654
41829
  booking.payment.appointmentPackages.state.message,
41655
41830
  " You can continue with the normal payment option."
41656
41831
  ] }) : null,
41832
+ slidingScale ? /* @__PURE__ */ jsxs21("div", { className: "rb-space-y-2", children: [
41833
+ /* @__PURE__ */ jsx27("label", { htmlFor: "appointment-sliding-amount", className: "form-label", children: "Choose your price" }),
41834
+ /* @__PURE__ */ jsxs21("p", { className: "rb-caption status-muted", children: [
41835
+ formatBookingAmount(slidingScale.minAmount, slidingScale.currency),
41836
+ " to ",
41837
+ formatBookingAmount(slidingScale.maxAmount, slidingScale.currency),
41838
+ slidingScale.suggestedAmount !== null ? `, suggested ${formatBookingAmount(slidingScale.suggestedAmount, slidingScale.currency)}` : ""
41839
+ ] }),
41840
+ /* @__PURE__ */ jsx27(
41841
+ "input",
41842
+ {
41843
+ id: "appointment-sliding-amount",
41844
+ type: "number",
41845
+ className: "form-input",
41846
+ min: slidingScale.minAmount / 100,
41847
+ max: slidingScale.maxAmount / 100,
41848
+ step: "0.01",
41849
+ value: booking.payment.selectedSlidingAmountInput,
41850
+ onChange: (event) => booking.actions.setSlidingAmountInput(event.target.value)
41851
+ }
41852
+ )
41853
+ ] }) : null,
41657
41854
  /* @__PURE__ */ jsxs21("div", { className: "ab-stack", role: "list", "aria-label": "Payment options", children: [
41658
41855
  packageOptions.map((appointmentPackage) => {
41659
41856
  const isSelected = booking.payment.selectedAppointmentPackageId === appointmentPackage.customerPassId;
@@ -43500,6 +43697,7 @@ function useEligibility(params) {
43500
43697
  occurrenceId: params.occurrenceId,
43501
43698
  attendanceMode: params.attendanceMode ?? void 0,
43502
43699
  ticketTypeId: params.ticketTypeId ?? void 0,
43700
+ selectedSlidingAmount: params.selectedSlidingAmount ?? void 0,
43503
43701
  email: params.email.trim().toLowerCase(),
43504
43702
  ticketsCount: params.ticketsCount
43505
43703
  });
@@ -43520,7 +43718,7 @@ function useEligibility(params) {
43520
43718
  return () => {
43521
43719
  cancelled = true;
43522
43720
  };
43523
- }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.email, params.ticketsCount]);
43721
+ }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.selectedSlidingAmount, params.email, params.ticketsCount]);
43524
43722
  return {
43525
43723
  state,
43526
43724
  eligibility: state.eligibility,
@@ -43871,6 +44069,41 @@ function TicketCounterLimit(props2) {
43871
44069
  ] }) : null
43872
44070
  ] });
43873
44071
  }
44072
+ function formatCurrencyInputAmount(amount) {
44073
+ const major = amount / 100;
44074
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
44075
+ }
44076
+ function SlidingScaleAmountField(props2) {
44077
+ const minLabel = formatPrice(props2.slidingScale.minAmount, props2.currency);
44078
+ const maxLabel = formatPrice(props2.slidingScale.maxAmount, props2.currency);
44079
+ const suggestedLabel = props2.slidingScale.suggestedAmount !== null ? formatPrice(props2.slidingScale.suggestedAmount, props2.currency) : null;
44080
+ return /* @__PURE__ */ jsxs28("div", { className: "er-field", children: [
44081
+ /* @__PURE__ */ jsx42("label", { htmlFor: "er-sliding-amount", className: "er-label", children: "Amount" }),
44082
+ /* @__PURE__ */ jsx42(
44083
+ "input",
44084
+ {
44085
+ id: "er-sliding-amount",
44086
+ type: "number",
44087
+ min: formatCurrencyInputAmount(props2.slidingScale.minAmount),
44088
+ max: formatCurrencyInputAmount(props2.slidingScale.maxAmount),
44089
+ step: "0.01",
44090
+ inputMode: "decimal",
44091
+ className: "er-input",
44092
+ value: props2.value,
44093
+ onChange: (event) => props2.onChange?.(event.target.value)
44094
+ }
44095
+ ),
44096
+ /* @__PURE__ */ jsxs28("p", { className: "er-text er-text--muted", children: [
44097
+ "Choose between ",
44098
+ minLabel,
44099
+ " and ",
44100
+ maxLabel,
44101
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
44102
+ "."
44103
+ ] }),
44104
+ props2.message ? /* @__PURE__ */ jsx42("p", { className: "er-text er-text--muted", children: props2.message }) : null
44105
+ ] });
44106
+ }
43874
44107
  function getTicketTypeCounterAvailabilityLabel(input) {
43875
44108
  if (input.selectedRemainingCapacity !== null) {
43876
44109
  return `${input.selectedRemainingCapacity} remaining for this ticket type`;
@@ -43942,6 +44175,10 @@ var init_TicketSelectionStep = __esm({
43942
44175
  maxTickets,
43943
44176
  selectedTicketTypeId = null,
43944
44177
  onTicketTypeChange,
44178
+ slidingScale = null,
44179
+ selectedSlidingAmountInput = "",
44180
+ slidingScaleSelectionMessage = null,
44181
+ onSlidingAmountChange,
43945
44182
  onTicketsChange
43946
44183
  }) => {
43947
44184
  const deliveryMode = event?.deliveryMode ?? "in_person";
@@ -44057,6 +44294,16 @@ var init_TicketSelectionStep = __esm({
44057
44294
  ] })
44058
44295
  ] })
44059
44296
  ] }) : /* @__PURE__ */ jsxs28(Fragment8, { children: [
44297
+ slidingScale ? /* @__PURE__ */ jsx42(
44298
+ SlidingScaleAmountField,
44299
+ {
44300
+ slidingScale,
44301
+ currency: eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2,
44302
+ value: selectedSlidingAmountInput,
44303
+ message: slidingScaleSelectionMessage,
44304
+ onChange: onSlidingAmountChange
44305
+ }
44306
+ ) : null,
44060
44307
  /* @__PURE__ */ jsx42("h3", { className: "er-title", children: canJoinWaitlist ? "How many waitlist spots?" : "How many tickets?" }),
44061
44308
  /* @__PURE__ */ jsxs28("div", { className: "er-ticket-counter-group", children: [
44062
44309
  /* @__PURE__ */ jsx42(
@@ -45071,6 +45318,10 @@ function EventRegistrationWizardContent(props2) {
45071
45318
  maxTickets: wizard.maxTicketsNum,
45072
45319
  selectedTicketTypeId: wizard.selectedTicketTypeId,
45073
45320
  onTicketTypeChange: wizard.setSelectedTicketTypeId,
45321
+ slidingScale: wizard.selectedEventSlidingScale,
45322
+ selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
45323
+ slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
45324
+ onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
45074
45325
  onTicketsChange: wizard.setTicketsCount
45075
45326
  }
45076
45327
  )
@@ -45287,7 +45538,7 @@ function EventRegistrationSummaryBar(props2) {
45287
45538
  const summaryTimeZone = wizard.occurrenceContext?.timeZone ?? summaryEvent?.timeZone ?? null;
45288
45539
  const occurrenceOverrideTitle = wizard.occurrenceContext?.overrides?.title;
45289
45540
  const summaryTitle = occurrenceOverrideTitle ?? summaryEvent?.title ?? "";
45290
- const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
45541
+ const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (wizard.selectedEventSlidingScale ? wizard.selectedSlidingAmount : null) ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
45291
45542
  const summaryCurrency = (summaryEvent ? publicEventPricingCurrency(summaryEvent.pricing) : null) ?? DEFAULT_EVENT_CURRENCY2;
45292
45543
  const teamMemberNames = (summaryEvent?.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
45293
45544
  const subtotalCents = Math.max(0, summaryPrice * wizard.ticketsCount);
@@ -45738,6 +45989,7 @@ function useEventRegistrationSubmit(options) {
45738
45989
  occurrenceId: submitOccurrenceId,
45739
45990
  attendanceMode: options.selectedAttendanceMode ?? void 0,
45740
45991
  ticketTypeId: options.selectedTicketTypeId ?? void 0,
45992
+ selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
45741
45993
  email: options.formData.email.trim().toLowerCase(),
45742
45994
  name: options.formData.name.trim(),
45743
45995
  ticketsCount: options.ticketsCount,
@@ -45847,6 +46099,35 @@ var init_useEventRegistrationSubmit = __esm({
45847
46099
 
45848
46100
  // ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.ts
45849
46101
  import { useCallback as useCallback8, useEffect as useEffect15, useMemo as useMemo10, useReducer as useReducer10, useState as useState7 } from "react";
46102
+ function parseCurrencyInputToCents(value) {
46103
+ const trimmed = value.trim();
46104
+ if (!trimmed) return null;
46105
+ const amount = Number(trimmed);
46106
+ if (!Number.isFinite(amount) || amount < 0) return null;
46107
+ return Math.round(amount * 100);
46108
+ }
46109
+ function formatCentsAsCurrencyInput(amount) {
46110
+ const major = amount / 100;
46111
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
46112
+ }
46113
+ function getSlidingScaleForEvent(event) {
46114
+ const pricing = event ? resolveRuntimeEventPricing(event) : null;
46115
+ return pricing?.kind === "sliding_scale" ? {
46116
+ minAmount: pricing.minAmount,
46117
+ maxAmount: pricing.maxAmount,
46118
+ suggestedAmount: pricing.suggestedAmount
46119
+ } : null;
46120
+ }
46121
+ function getSlidingScaleSelectionMessage(input) {
46122
+ if (!input.slidingScale) return null;
46123
+ if (input.selectedAmount === null) {
46124
+ return "Choose an amount to continue.";
46125
+ }
46126
+ if (input.selectedAmount < input.slidingScale.minAmount || input.selectedAmount > input.slidingScale.maxAmount) {
46127
+ return "Choose an amount within the sliding scale.";
46128
+ }
46129
+ return null;
46130
+ }
45850
46131
  function useEventRegistrationWizard(options) {
45851
46132
  const api = useBlockApi();
45852
46133
  const registrationEvents = useMemo10(
@@ -45891,6 +46172,10 @@ function useEventRegistrationWizard(options) {
45891
46172
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
45892
46173
  );
45893
46174
  const [selectedTicketTypeId, setSelectedTicketTypeId] = useState7(null);
46175
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState7(() => {
46176
+ const slidingScale = getSlidingScaleForEvent(preSelectedEvent ?? null);
46177
+ return slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : "";
46178
+ });
45894
46179
  const [registrationResult, setRegistrationResult] = useState7(null);
45895
46180
  const [errorMessage, setErrorMessage] = useState7("");
45896
46181
  const [altchaSolution, setAltchaSolution] = useState7(null);
@@ -45927,6 +46212,7 @@ function useEventRegistrationWizard(options) {
45927
46212
  occurrenceId,
45928
46213
  attendanceMode: selectedAttendanceMode,
45929
46214
  ticketTypeId: selectedTicketTypeId,
46215
+ selectedSlidingAmount: parseCurrencyInputToCents(selectedSlidingAmountInput),
45930
46216
  email: formData.email,
45931
46217
  ticketsCount
45932
46218
  });
@@ -46062,6 +46348,18 @@ function useEventRegistrationWizard(options) {
46062
46348
  }),
46063
46349
  [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility, selectedTicketTypeId]
46064
46350
  );
46351
+ const selectedSlidingAmount = useMemo10(
46352
+ () => parseCurrencyInputToCents(selectedSlidingAmountInput),
46353
+ [selectedSlidingAmountInput]
46354
+ );
46355
+ const selectedEventSlidingScale = useMemo10(
46356
+ () => getSlidingScaleForEvent(selectedEvent ?? preSelectedEvent),
46357
+ [selectedEvent, preSelectedEvent]
46358
+ );
46359
+ const slidingScaleSelectionMessage = getSlidingScaleSelectionMessage({
46360
+ slidingScale: selectedEventSlidingScale,
46361
+ selectedAmount: selectedSlidingAmount
46362
+ });
46065
46363
  const ticketTypeSelectionRequired = useMemo10(() => {
46066
46364
  const event = selectedEvent ?? preSelectedEvent;
46067
46365
  if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
@@ -46130,12 +46428,24 @@ function useEventRegistrationWizard(options) {
46130
46428
  setGuestSectionShake(false);
46131
46429
  };
46132
46430
  const eventRequiresPayment = useMemo10(
46133
- () => resolveEventRequiresPayment({
46431
+ () => {
46432
+ if (selectedEventSlidingScale) {
46433
+ return (selectedSlidingAmount ?? 0) > 0;
46434
+ }
46435
+ return resolveEventRequiresPayment({
46436
+ eligibility,
46437
+ selectedTicketType,
46438
+ event: selectedEvent ?? preSelectedEvent
46439
+ });
46440
+ },
46441
+ [
46134
46442
  eligibility,
46135
46443
  selectedTicketType,
46136
- event: selectedEvent ?? preSelectedEvent
46137
- }),
46138
- [eligibility, selectedTicketType, selectedEvent, preSelectedEvent]
46444
+ selectedEvent,
46445
+ preSelectedEvent,
46446
+ selectedEventSlidingScale,
46447
+ selectedSlidingAmount
46448
+ ]
46139
46449
  );
46140
46450
  const handleDetailsComplete = async () => {
46141
46451
  if (ticketTypeSelectionRequired && !hasValidSelectedTicketType) {
@@ -46144,6 +46454,11 @@ function useEventRegistrationWizard(options) {
46144
46454
  setStep("error");
46145
46455
  return { ok: false, reason: "blocked", message };
46146
46456
  }
46457
+ if (slidingScaleSelectionMessage) {
46458
+ setErrorMessage(slidingScaleSelectionMessage);
46459
+ setStep("error");
46460
+ return { ok: false, reason: "blocked", message: slidingScaleSelectionMessage };
46461
+ }
46147
46462
  if (!validateForm()) {
46148
46463
  setGuestSectionShake(true);
46149
46464
  return {
@@ -46228,9 +46543,18 @@ function useEventRegistrationWizard(options) {
46228
46543
  const discountSubtotalCents = useMemo10(() => {
46229
46544
  if (discountContext?.type !== "event") return void 0;
46230
46545
  const event = selectedEvent ?? preSelectedEvent;
46231
- const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
46546
+ const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (selectedEventSlidingScale ? selectedSlidingAmount : null) ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
46232
46547
  return Math.max(0, eventPriceCents * ticketsCount);
46233
- }, [discountContext, selectedTicketType, eligibility, selectedEvent, preSelectedEvent, ticketsCount]);
46548
+ }, [
46549
+ discountContext,
46550
+ selectedTicketType,
46551
+ eligibility,
46552
+ selectedEvent,
46553
+ preSelectedEvent,
46554
+ selectedEventSlidingScale,
46555
+ selectedSlidingAmount,
46556
+ ticketsCount
46557
+ ]);
46234
46558
  const discountUi = useDiscountCodeUi({ api, discountContext, subtotalCents: discountSubtotalCents });
46235
46559
  const selectionMethod = paymentSelection.method;
46236
46560
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
@@ -46239,6 +46563,7 @@ function useEventRegistrationWizard(options) {
46239
46563
  registrationTarget,
46240
46564
  selectedAttendanceMode,
46241
46565
  selectedTicketTypeId,
46566
+ selectedSlidingAmount,
46242
46567
  ticketsCount,
46243
46568
  formData,
46244
46569
  altchaSolution,
@@ -46253,6 +46578,10 @@ function useEventRegistrationWizard(options) {
46253
46578
  });
46254
46579
  const handleEventSelect = (event) => {
46255
46580
  setSelectedEvent(event);
46581
+ const slidingScale = getSlidingScaleForEvent(event);
46582
+ setSelectedSlidingAmountInput(
46583
+ slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
46584
+ );
46256
46585
  setSelectedAttendanceMode(
46257
46586
  (event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person"
46258
46587
  );
@@ -46262,6 +46591,7 @@ function useEventRegistrationWizard(options) {
46262
46591
  const handleRetry = () => {
46263
46592
  dispatchFlow({ type: "reset", hasPreSelectedContext });
46264
46593
  setSelectedTicketTypeId(null);
46594
+ setSelectedSlidingAmountInput("");
46265
46595
  setSelectedAttendanceMode(
46266
46596
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
46267
46597
  );
@@ -46298,6 +46628,11 @@ function useEventRegistrationWizard(options) {
46298
46628
  setSelectedAttendanceMode,
46299
46629
  selectedTicketTypeId,
46300
46630
  setSelectedTicketTypeId,
46631
+ selectedSlidingAmountInput,
46632
+ setSelectedSlidingAmountInput,
46633
+ selectedSlidingAmount,
46634
+ selectedEventSlidingScale,
46635
+ slidingScaleSelectionMessage,
46301
46636
  selectedTicketType,
46302
46637
  visibleTicketTypes,
46303
46638
  ticketTypeSelectionRequired,
@@ -47255,7 +47590,9 @@ function getSelectableCoursePricingOptionsForNow(course, now) {
47255
47590
  return left.name.localeCompare(right.name);
47256
47591
  });
47257
47592
  }
47258
- function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {}) {
47593
+ function resolveCoursePricingState(course, selectedPricingOptionId, selectedSlidingAmountOrNowInput, nowInput = {}) {
47594
+ const selectedSlidingAmount = typeof selectedSlidingAmountOrNowInput === "number" || selectedSlidingAmountOrNowInput === null ? selectedSlidingAmountOrNowInput : null;
47595
+ const effectiveNowInput = typeof selectedSlidingAmountOrNowInput === "object" && selectedSlidingAmountOrNowInput !== null ? selectedSlidingAmountOrNowInput : nowInput;
47259
47596
  const currency = course.pricing?.currency ?? course.currency;
47260
47597
  if (!course.pricing) {
47261
47598
  const legacyAmount = course.priceCents ?? 0;
@@ -47265,6 +47602,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47265
47602
  selectableOptions: [],
47266
47603
  requiresSelection: false,
47267
47604
  selectedOption: null,
47605
+ slidingScale: null,
47606
+ selectedSlidingAmount: null,
47268
47607
  selectedPriceAmount: legacyAmount,
47269
47608
  selectedRequiresPayment: legacyAmount > 0,
47270
47609
  hasPaidSelection: legacyAmount > 0,
@@ -47279,6 +47618,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47279
47618
  selectableOptions: [],
47280
47619
  requiresSelection: false,
47281
47620
  selectedOption: null,
47621
+ slidingScale: null,
47622
+ selectedSlidingAmount: null,
47282
47623
  selectedPriceAmount: 0,
47283
47624
  selectedRequiresPayment: false,
47284
47625
  hasPaidSelection: false,
@@ -47292,6 +47633,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47292
47633
  selectableOptions: [],
47293
47634
  requiresSelection: false,
47294
47635
  selectedOption: null,
47636
+ slidingScale: null,
47637
+ selectedSlidingAmount: null,
47295
47638
  selectedPriceAmount: amount,
47296
47639
  selectedRequiresPayment: amount > 0,
47297
47640
  hasPaidSelection: amount > 0,
@@ -47301,7 +47644,7 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47301
47644
  case "tiered": {
47302
47645
  const selectableOptions = getSelectableCoursePricingOptionsForNow(
47303
47646
  course,
47304
- resolvePlanningDateInput(nowInput)
47647
+ resolvePlanningDateInput(effectiveNowInput)
47305
47648
  );
47306
47649
  const selectedOption = (selectedPricingOptionId ? selectableOptions.find((option) => option.id === selectedPricingOptionId) : null) ?? (selectableOptions.length === 1 ? selectableOptions[0] ?? null : null);
47307
47650
  const lowestAmount = selectableOptions.reduce((lowest, option) => {
@@ -47317,12 +47660,51 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47317
47660
  selectableOptions,
47318
47661
  requiresSelection: selectableOptions.length > 1,
47319
47662
  selectedOption,
47663
+ slidingScale: null,
47664
+ selectedSlidingAmount: null,
47320
47665
  selectedPriceAmount: selectedOption?.priceAmount ?? null,
47321
47666
  selectedRequiresPayment: (selectedOption?.priceAmount ?? 0) > 0,
47322
47667
  hasPaidSelection: selectableOptions.some((option) => option.priceAmount > 0),
47323
47668
  hasFreeSelection: selectableOptions.some((option) => option.priceAmount === 0)
47324
47669
  };
47325
47670
  }
47671
+ case "sliding_scale": {
47672
+ const minAmount = course.pricing.slidingMinAmount ?? null;
47673
+ const maxAmount = course.pricing.slidingMaxAmount ?? null;
47674
+ if (minAmount === null || maxAmount === null || minAmount < 0 || maxAmount < minAmount) {
47675
+ return {
47676
+ currency,
47677
+ display: { kind: "unavailable", currency },
47678
+ selectableOptions: [],
47679
+ requiresSelection: true,
47680
+ selectedOption: null,
47681
+ slidingScale: null,
47682
+ selectedSlidingAmount: null,
47683
+ selectedPriceAmount: null,
47684
+ selectedRequiresPayment: false,
47685
+ hasPaidSelection: false,
47686
+ hasFreeSelection: false
47687
+ };
47688
+ }
47689
+ const normalizedSelectedAmount = typeof selectedSlidingAmount === "number" && Number.isFinite(selectedSlidingAmount) ? selectedSlidingAmount : null;
47690
+ return {
47691
+ currency,
47692
+ display: { kind: "range", minAmount, maxAmount, currency },
47693
+ selectableOptions: [],
47694
+ requiresSelection: normalizedSelectedAmount === null,
47695
+ selectedOption: null,
47696
+ slidingScale: {
47697
+ minAmount,
47698
+ maxAmount,
47699
+ suggestedAmount: course.pricing.slidingSuggestedAmount ?? null
47700
+ },
47701
+ selectedSlidingAmount: normalizedSelectedAmount,
47702
+ selectedPriceAmount: normalizedSelectedAmount,
47703
+ selectedRequiresPayment: (normalizedSelectedAmount ?? 0) > 0,
47704
+ hasPaidSelection: maxAmount > 0,
47705
+ hasFreeSelection: minAmount === 0
47706
+ };
47707
+ }
47326
47708
  }
47327
47709
  }
47328
47710
  function formatCoursePriceDisplay(display, locale) {
@@ -51514,6 +51896,44 @@ function CoursePricingSelector({
51514
51896
  )
51515
51897
  ] });
51516
51898
  }
51899
+ function CourseSlidingScaleInput({
51900
+ pricing,
51901
+ value,
51902
+ onChange
51903
+ }) {
51904
+ const slidingScale = pricing.slidingScale;
51905
+ if (!slidingScale) {
51906
+ return null;
51907
+ }
51908
+ const minLabel = formatCoursePriceAmount(slidingScale.minAmount, pricing.currency);
51909
+ const maxLabel = formatCoursePriceAmount(slidingScale.maxAmount, pricing.currency);
51910
+ const suggestedLabel = slidingScale.suggestedAmount !== null ? formatCoursePriceAmount(slidingScale.suggestedAmount, pricing.currency) : null;
51911
+ return /* @__PURE__ */ jsxs65("div", { className: "cr-form__field", children: [
51912
+ /* @__PURE__ */ jsx95("label", { htmlFor: "cr-sliding-amount", className: "cr-form__label", children: "Amount" }),
51913
+ /* @__PURE__ */ jsx95(
51914
+ "input",
51915
+ {
51916
+ id: "cr-sliding-amount",
51917
+ type: "number",
51918
+ min: slidingScale.minAmount / 100,
51919
+ max: slidingScale.maxAmount / 100,
51920
+ step: "0.01",
51921
+ inputMode: "decimal",
51922
+ className: "cr-form__input",
51923
+ value,
51924
+ onChange: (event) => onChange(event.target.value)
51925
+ }
51926
+ ),
51927
+ /* @__PURE__ */ jsxs65("p", { className: "cr-text cr-text--muted", children: [
51928
+ "Choose between ",
51929
+ minLabel,
51930
+ " and ",
51931
+ maxLabel,
51932
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
51933
+ "."
51934
+ ] })
51935
+ ] });
51936
+ }
51517
51937
  function EnrollmentForm({ formData, errors, onChange, isEmailLocked, isLoading, onLogout }) {
51518
51938
  const nameErrorId = "cr-name-error";
51519
51939
  const emailErrorId = "cr-email-error";
@@ -51609,6 +52029,17 @@ var init_components2 = __esm({
51609
52029
 
51610
52030
  // ../blocks/src/system/runtime/nodes/course-registration/useCourseRegistrationWizard.ts
51611
52031
  import { useEffect as useEffect25, useMemo as useMemo19, useState as useState18 } from "react";
52032
+ function parseCurrencyInputToCents2(value) {
52033
+ const trimmed = value.trim();
52034
+ if (!trimmed) return null;
52035
+ const amount = Number(trimmed);
52036
+ if (!Number.isFinite(amount) || amount < 0) return null;
52037
+ return Math.round(amount * 100);
52038
+ }
52039
+ function formatCentsAsCurrencyInput2(amount) {
52040
+ const major = amount / 100;
52041
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
52042
+ }
51612
52043
  function shouldFetchCourseEligibility(course, hasPaidSelection, portalAuthState, hasWaitlistClaimToken) {
51613
52044
  if (!course || portalAuthState === "checking") return false;
51614
52045
  if (hasWaitlistClaimToken) return false;
@@ -51626,12 +52057,27 @@ function getSessionIdFromUrl() {
51626
52057
  }
51627
52058
  function getMissingCoursePricingSelectionMessage(pricing) {
51628
52059
  if (pricing?.requiresSelection && !pricing.selectedOption) {
52060
+ if (pricing.slidingScale) {
52061
+ return "Choose an amount to continue.";
52062
+ }
51629
52063
  return "Choose a pricing option to continue.";
51630
52064
  }
52065
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount < pricing.slidingScale.minAmount) {
52066
+ return "Choose an amount within the sliding scale.";
52067
+ }
52068
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount > pricing.slidingScale.maxAmount) {
52069
+ return "Choose an amount within the sliding scale.";
52070
+ }
51631
52071
  return null;
51632
52072
  }
51633
52073
  function buildCoursePricingOptionPayload(pricing) {
51634
- return pricing?.selectedOption ? { pricingOptionId: pricing.selectedOption.id } : {};
52074
+ if (pricing?.selectedOption) {
52075
+ return { pricingOptionId: pricing.selectedOption.id };
52076
+ }
52077
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null) {
52078
+ return { selectedSlidingAmount: pricing.selectedSlidingAmount };
52079
+ }
52080
+ return {};
51635
52081
  }
51636
52082
  function resolveHasCoursePayment(pricing) {
51637
52083
  if (!pricing) {
@@ -51672,6 +52118,7 @@ function useCourseRegistrationWizard(props2) {
51672
52118
  scopedCourses.length === 1 ? scopedCourses[0] ?? null : null
51673
52119
  );
51674
52120
  const [selectedPricingOptionId, setSelectedPricingOptionId] = useState18(null);
52121
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState18("");
51675
52122
  const [formData, setFormData] = useState18({
51676
52123
  name: "",
51677
52124
  email: "",
@@ -51693,8 +52140,12 @@ function useCourseRegistrationWizard(props2) {
51693
52140
  const [bookingMode, setBookingMode] = useState18(null);
51694
52141
  const waitlistClaim = useWaitlistClaim();
51695
52142
  const selectedCoursePricing = useMemo19(
51696
- () => selectedCourse ? resolveCoursePricingState(selectedCourse, selectedPricingOptionId) : null,
51697
- [selectedCourse, selectedPricingOptionId]
52143
+ () => selectedCourse ? resolveCoursePricingState(
52144
+ selectedCourse,
52145
+ selectedPricingOptionId,
52146
+ parseCurrencyInputToCents2(selectedSlidingAmountInput)
52147
+ ) : null,
52148
+ [selectedCourse, selectedPricingOptionId, selectedSlidingAmountInput]
51698
52149
  );
51699
52150
  const missingPricingSelectionMessage = getMissingCoursePricingSelectionMessage(
51700
52151
  selectedCoursePricing
@@ -51767,9 +52218,13 @@ function useCourseRegistrationWizard(props2) {
51767
52218
  useEffect25(() => {
51768
52219
  if (!selectedCourse) {
51769
52220
  setSelectedPricingOptionId(null);
52221
+ setSelectedSlidingAmountInput("");
51770
52222
  return;
51771
52223
  }
51772
52224
  const defaultPricing = resolveCoursePricingState(selectedCourse, null);
52225
+ setSelectedSlidingAmountInput(
52226
+ defaultPricing.slidingScale?.suggestedAmount !== null && defaultPricing.slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput2(defaultPricing.slidingScale.suggestedAmount) : ""
52227
+ );
51773
52228
  setSelectedPricingOptionId((previous) => {
51774
52229
  if (previous && defaultPricing.selectableOptions.some((option) => option.id === previous)) {
51775
52230
  return previous;
@@ -52144,6 +52599,8 @@ function useCourseRegistrationWizard(props2) {
52144
52599
  selectedCourse,
52145
52600
  selectedPricingOptionId,
52146
52601
  setSelectedPricingOptionId,
52602
+ selectedSlidingAmountInput,
52603
+ setSelectedSlidingAmountInput,
52147
52604
  selectedCoursePricing,
52148
52605
  setSelectedCourse,
52149
52606
  formData,
@@ -52209,6 +52666,19 @@ var init_useCourseRegistrationWizard = __esm({
52209
52666
 
52210
52667
  // ../blocks/src/system/runtime/nodes/course-registration/CourseRegistrationWizard.tsx
52211
52668
  import { Fragment as Fragment23, jsx as jsx96, jsxs as jsxs66 } from "react/jsx-runtime";
52669
+ function getCourseSlidingScaleMessage(pricing) {
52670
+ if (!pricing.slidingScale) {
52671
+ return null;
52672
+ }
52673
+ const selectedAmount = pricing.selectedSlidingAmount;
52674
+ if (selectedAmount === null) {
52675
+ return "Choose an amount to continue.";
52676
+ }
52677
+ if (selectedAmount < pricing.slidingScale.minAmount || selectedAmount > pricing.slidingScale.maxAmount) {
52678
+ return "Choose an amount within the sliding scale.";
52679
+ }
52680
+ return null;
52681
+ }
52212
52682
  function CourseRegistrationWizard(props2) {
52213
52683
  const {
52214
52684
  showSessions = true,
@@ -52230,6 +52700,8 @@ function CourseRegistrationWizard(props2) {
52230
52700
  selectedCourse,
52231
52701
  selectedPricingOptionId,
52232
52702
  setSelectedPricingOptionId,
52703
+ selectedSlidingAmountInput,
52704
+ setSelectedSlidingAmountInput,
52233
52705
  selectedCoursePricing,
52234
52706
  formData,
52235
52707
  setFormData,
@@ -52304,7 +52776,8 @@ function CourseRegistrationWizard(props2) {
52304
52776
  const canShowDiscountUi = !discountsAvailableLoading && paymentChoice === "checkout" && discountsAvailable && manualCodeEntryAllowed;
52305
52777
  const courseSoldOut = waitlistClaim.claim === null && courseEligibility?.isFull === true && courseEligibility.canJoinWaitlist !== true;
52306
52778
  const detailsSubmitLabel = courseEligibility?.canJoinWaitlist ? "Join waitlist" : buttonText;
52307
- const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption;
52779
+ const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption && !selectedCoursePricing.slidingScale;
52780
+ const slidingScaleMessage = selectedCoursePricing ? getCourseSlidingScaleMessage(selectedCoursePricing) : null;
52308
52781
  const pricingUnavailable = selectedCoursePricing?.display.kind === "unavailable";
52309
52782
  const selectedCourseWithPricing = selectedCourse && selectedCoursePricing ? {
52310
52783
  course: selectedCourse,
@@ -52397,6 +52870,14 @@ function CourseRegistrationWizard(props2) {
52397
52870
  onSelect: setSelectedPricingOptionId
52398
52871
  }
52399
52872
  ),
52873
+ /* @__PURE__ */ jsx96(
52874
+ CourseSlidingScaleInput,
52875
+ {
52876
+ pricing: selectedCourseWithPricing.pricing,
52877
+ value: selectedSlidingAmountInput,
52878
+ onChange: setSelectedSlidingAmountInput
52879
+ }
52880
+ ),
52400
52881
  showSpamProtection && /* @__PURE__ */ jsx96("div", { className: "cr-altcha", children: /* @__PURE__ */ jsx96(
52401
52882
  AltchaWidget,
52402
52883
  {
@@ -52434,7 +52915,7 @@ function CourseRegistrationWizard(props2) {
52434
52915
  {
52435
52916
  type: "submit",
52436
52917
  className: buttonClass,
52437
- disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || pricingUnavailable,
52918
+ disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || slidingScaleMessage !== null || pricingUnavailable,
52438
52919
  children: eligibilityLoading ? "Checking availability\u2026" : courseSoldOut ? "Sold out" : detailsSubmitLabel
52439
52920
  }
52440
52921
  )
@@ -52444,6 +52925,7 @@ function CourseRegistrationWizard(props2) {
52444
52925
  ),
52445
52926
  courseSoldOut && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--muted", children: "This course is fully booked and there is no waitlist available right now." }),
52446
52927
  pricingSelectionMissing && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--muted", children: "Choose a pricing option to continue." }),
52928
+ slidingScaleMessage && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--muted", children: slidingScaleMessage }),
52447
52929
  pricingUnavailable && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--muted", children: "Pricing is not available for this course right now." }),
52448
52930
  eligibilityError && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--error", children: eligibilityError })
52449
52931
  ] })
@@ -55569,6 +56051,7 @@ import * as React37 from "react";
55569
56051
  import * as React35 from "react";
55570
56052
 
55571
56053
  // ../blocks/src/system/manifest/fieldValidation/index.ts
56054
+ init_src2();
55572
56055
  import { z as z53 } from "zod";
55573
56056
 
55574
56057
  // ../blocks/src/system/types/link.ts
@@ -56297,7 +56780,10 @@ function deriveFieldValidationPlan(field, path = [field.id]) {
56297
56780
  return makePlan("richText", "object", field, path, required, commonConstraints(required, { kind: "richTextDoc" }));
56298
56781
  case "media":
56299
56782
  case "file":
56300
- return makePlan("media", "object", field, path, required, commonConstraints(required, { kind: "mediaObject" }));
56783
+ return {
56784
+ ...makePlan("media", "object", field, path, required, commonConstraints(required, { kind: "mediaObject" })),
56785
+ legacyStringType: legacyStringMediaTypeForField(field)
56786
+ };
56301
56787
  case "link":
56302
56788
  return makePlan("link", "object", field, path, required, commonConstraints(required, { kind: "linkObject" }));
56303
56789
  case "boolean":
@@ -56482,15 +56968,23 @@ function normalizeFieldValue(plan, value, ctx) {
56482
56968
  return normalizeRepeaterItems(plan, value, ctx);
56483
56969
  case "boolean":
56484
56970
  case "richText":
56485
- case "media":
56486
56971
  case "link":
56487
56972
  case "select":
56488
56973
  case "passthrough":
56489
56974
  return value;
56975
+ case "media":
56976
+ return normalizeMediaInput(plan, value);
56490
56977
  default:
56491
56978
  return assertNever7(plan);
56492
56979
  }
56493
56980
  }
56981
+ function normalizeMediaInput(plan, value) {
56982
+ return normalizeLegacyStringMediaValueForField(value, {
56983
+ type: plan.fieldType,
56984
+ mediaKinds: plan.fieldType === "media" ? [plan.legacyStringType] : void 0,
56985
+ fileKinds: plan.fieldType === "file" ? [plan.legacyStringType] : void 0
56986
+ });
56987
+ }
56494
56988
  function normalizeObjectChildren(children, value, ctx) {
56495
56989
  if (!value || typeof value !== "object" || Array.isArray(value)) return value;
56496
56990
  const normalized = {
@@ -56754,6 +57248,21 @@ function fieldPlanToZod(plan, options = {}) {
56754
57248
  }
56755
57249
  });
56756
57250
  }
57251
+ function fieldPresenceForPlan(plan) {
57252
+ return plan.required ? { kind: "required" } : { kind: "optional" };
57253
+ }
57254
+ function fieldPlanToObjectPropertyZod(plan, options = {}) {
57255
+ const schema = fieldPlanToZod(plan, options);
57256
+ const presence = fieldPresenceForPlan(plan);
57257
+ switch (presence.kind) {
57258
+ case "required":
57259
+ return schema;
57260
+ case "optional":
57261
+ return schema.optional();
57262
+ default:
57263
+ return assertNever7(presence);
57264
+ }
57265
+ }
56757
57266
  function addFieldIssueToZodContext(zodCtx, validationIssue, rootPath) {
56758
57267
  const message = fieldIssueToMessage(validationIssue);
56759
57268
  const path = relativeZodIssuePath(validationIssue.path, rootPath);
@@ -56966,7 +57475,7 @@ function createManifestValidator(manifestOrFields, options = {}) {
56966
57475
  function deriveContentZodFromPlans(plans, options) {
56967
57476
  const shape = {};
56968
57477
  for (const plan of plans) {
56969
- shape[plan.fieldId] = fieldPlanToZod(plan, options);
57478
+ shape[plan.fieldId] = fieldPlanToObjectPropertyZod(plan, options);
56970
57479
  }
56971
57480
  return z54.object(shape).catchall(z54.unknown()).passthrough();
56972
57481
  }
@@ -97868,6 +98377,38 @@ var NAMED_SURFACE_IDS = SURFACE_IDS.filter(
97868
98377
  (surfaceId) => surfaceId !== DEFAULT_SURFACE_ID
97869
98378
  );
97870
98379
 
98380
+ // ../api/src/common/envelope.ts
98381
+ var API_ERROR_CODES = [
98382
+ "auth:unauthenticated",
98383
+ "auth:token_expired",
98384
+ "auth:token_invalid",
98385
+ "auth:forbidden",
98386
+ "auth:mfa_required",
98387
+ "auth:sudo_required",
98388
+ "auth:insufficient_permissions",
98389
+ "auth:impersonation_restricted",
98390
+ "validation:invalid_input",
98391
+ "validation:missing_field",
98392
+ "validation:invalid_format",
98393
+ "validation:payload_too_large",
98394
+ "resource:not_found",
98395
+ "resource:already_exists",
98396
+ "resource:conflict",
98397
+ "resource:gone",
98398
+ "rate_limit:exceeded",
98399
+ "billing:payment_required",
98400
+ "billing:plan_limit_exceeded",
98401
+ "billing:pass_redemption_failed",
98402
+ "server:internal_error",
98403
+ "server:unavailable",
98404
+ "external:service_error",
98405
+ "external:registrar_orphaned_contact_profile",
98406
+ "network:connection_error",
98407
+ "network:timeout",
98408
+ "network:dns_error"
98409
+ ];
98410
+ var API_ERROR_CODE_SET = new Set(API_ERROR_CODES);
98411
+
97871
98412
  // ../api/src/customerMembershipOutcomes.ts
97872
98413
  var CANONICAL_REFUND_CONFLICT_CODES = [
97873
98414
  "already_refunded",
@@ -97877,6 +98418,7 @@ var CANONICAL_REFUND_CONFLICT_CODES = [
97877
98418
  ];
97878
98419
  var CANONICAL_REFUND_PLANNER_ERROR_CODES = [
97879
98420
  ...CANONICAL_REFUND_CONFLICT_CODES,
98421
+ "invalid_amount",
97880
98422
  "amount_exceeds_remaining",
97881
98423
  "pass_revoke_requires_full_refund",
97882
98424
  "refund_failed"
@@ -97903,6 +98445,7 @@ var CUSTOMER_MEMBERSHIP_PAYMENT_REFUND_FAILED_CODES = [
97903
98445
  // ../api/src/bookingRefundOutcomes.ts
97904
98446
  var STANDARD_LINE_ITEM_REFUND_INVALID_CODES = [
97905
98447
  "missing_amount",
98448
+ "invalid_amount",
97906
98449
  "amount_exceeds_remaining",
97907
98450
  "credit_refunds_disabled",
97908
98451
  "missing_credit_customer",
@@ -97917,6 +98460,7 @@ var EVENT_ATTENDEE_REFUND_INVALID_CODES = [
97917
98460
  var MANAGED_PRODUCT_REFUND_INVALID_CODES = [
97918
98461
  "credit_refunds_disabled",
97919
98462
  "not_refundable",
98463
+ "invalid_amount",
97920
98464
  "amount_exceeds_remaining"
97921
98465
  ];
97922
98466
  var CUSTOMER_PASS_REFUND_INVALID_CODES = [