@riverbankcms/sdk 0.90.1 → 0.92.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 (113) hide show
  1. package/dist/{PreviewEditorSidebar-FNFRVSIM.mjs → PreviewEditorSidebar-3CI5MKV3.mjs} +3 -3
  2. package/dist/{PreviewEditorUI-3WXJFQXG.mjs → PreviewEditorUI-DKEP636I.mjs} +3 -3
  3. package/dist/SdkPreviewModeRuntime-BRGRDQFH.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/customerMembershipOutcomes.d.ts +4 -4
  8. package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +6 -0
  9. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +32 -0
  10. package/dist/_dts/blocks/src/system/blocks/courses/shared/pricing.d.ts +12 -1
  11. package/dist/_dts/blocks/src/system/blocks/courses/shared/schemas.d.ts +12 -0
  12. package/dist/_dts/blocks/src/system/blocks/courses/shared/types.d.ts +4 -1
  13. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +44 -0
  14. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +24 -0
  15. package/dist/_dts/blocks/src/system/runtime/api/types.d.ts +8 -1
  16. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.d.ts +8 -1
  17. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.d.ts +1 -0
  18. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +4 -0
  19. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.interactive.d.ts +40 -0
  20. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.reserved-fields.d.ts +1 -1
  21. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +29 -0
  22. package/dist/_dts/blocks/src/system/runtime/nodes/booking-runtime-sources.d.ts +1 -0
  23. package/dist/_dts/blocks/src/system/runtime/nodes/course-registration/components.d.ts +6 -0
  24. package/dist/_dts/blocks/src/system/runtime/nodes/course-registration/useCourseRegistrationWizard.d.ts +8 -0
  25. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/TicketSelectionStep.d.ts +8 -0
  26. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +1 -0
  27. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +1 -1
  28. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +1 -0
  29. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +9 -0
  30. package/dist/_dts/content-model/src/entry-slots/types.d.ts +110 -2
  31. package/dist/_dts/core/src/branded.d.ts +3 -0
  32. package/dist/_dts/core/src/event-pricing.d.ts +18 -1
  33. package/dist/_dts/core/src/money-input.d.ts +34 -4
  34. package/dist/_dts/core/src/string.d.ts +12 -0
  35. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +70 -0
  36. package/dist/_dts/media-core/src/fileAssetCompatibility.d.ts +1 -1
  37. package/dist/_dts/media-core/src/filePersistence.d.ts +5 -1
  38. package/dist/_dts/media-core/src/fileStorage.d.ts +1 -0
  39. package/dist/_dts/media-core/src/videoPrep.d.ts +15 -6
  40. package/dist/_dts/media-core/src/videoSlotPolicy.d.ts +5 -5
  41. package/dist/_dts/sdk/src/cli/commands/push/execute/diff.d.ts +2 -0
  42. package/dist/_dts/sdk/src/cli/commands/push/execute/localMediaReporting.d.ts +23 -0
  43. package/dist/_dts/sdk/src/cli/commands/push/scopes/commandBackedDatedOfferingScope.d.ts +54 -0
  44. package/dist/_dts/sdk/src/cli/commands/push/scopes/mediaPrep.d.ts +7 -2
  45. package/dist/_dts/sdk/src/cli/commands/push/scopes/types.d.ts +6 -0
  46. package/dist/_dts/sdk/src/cli/media/local-sync.d.ts +35 -0
  47. package/dist/_dts/sdk/src/cli/sync/validation.d.ts +2 -2
  48. package/dist/_dts/sdk/src/rendering/islands/types.d.ts +23 -1
  49. package/dist/_dts/sdk/src/version.d.ts +1 -1
  50. package/dist/{chunk-SYYJZPKU.mjs → chunk-6D7PLF4M.mjs} +2 -2
  51. package/dist/{chunk-CR2VX432.mjs → chunk-I6EOKV6B.mjs} +568 -76
  52. package/dist/{chunk-FA6LVZ7C.mjs → chunk-LERKJM3E.mjs} +7 -7
  53. package/dist/{chunk-UG7I7S7W.mjs → chunk-U5YM4AWE.mjs} +1 -1
  54. package/dist/{chunk-H6TT5ZXI.mjs → chunk-UOMTCHZD.mjs} +1 -1
  55. package/dist/{chunk-BOQXMCSP.mjs → chunk-ZA2S7H34.mjs} +3 -3
  56. package/dist/cli/index.mjs +755 -414
  57. package/dist/cli/init-docs/content/workflow-add-block.md +48 -0
  58. package/dist/cli/init-docs/content/workflow-custom-block.md +10 -0
  59. package/dist/client/{accordion-4K7VGZ3I.mjs → accordion-BUDH2G3S.mjs} +2 -2
  60. package/dist/client/{bookingForm-3QXA3CIS.mjs → bookingForm-NQMCELVB.mjs} +90 -15
  61. package/dist/client/bookings.mjs +259 -32
  62. package/dist/client/{carousel-BEJKNGQ7.mjs → carousel-ONCEW6IJ.mjs} +3 -3
  63. package/dist/client/{cart-44BBM6XG.mjs → cart-V2TCK2GI.mjs} +7 -7
  64. package/dist/client/{checkout-THMHEYKF.mjs → checkout-ZI7SWNCS.mjs} +7 -7
  65. package/dist/client/{chunk-PEDNDLPB.mjs → chunk-AUMRQ242.mjs} +52 -5
  66. package/dist/client/{chunk-EJBHK74Y.mjs → chunk-B5UC2D6X.mjs} +7 -6
  67. package/dist/client/{chunk-DQIRKNZE.mjs → chunk-ERQDHUS2.mjs} +1 -1
  68. package/dist/client/{chunk-3H5CLFKT.mjs → chunk-GH6IIOOE.mjs} +39 -10
  69. package/dist/client/{chunk-ZIAF2DA4.mjs → chunk-HQWIJLYN.mjs} +1 -1
  70. package/dist/client/{chunk-4COPESPO.mjs → chunk-JXY6NHG6.mjs} +6 -6
  71. package/dist/client/{chunk-JV7ZNAOU.mjs → chunk-KXYEKEV6.mjs} +155 -17
  72. package/dist/client/{chunk-HIHOQGIP.mjs → chunk-LJCX6V7Q.mjs} +9 -9
  73. package/dist/client/{chunk-5AJIMWKS.mjs → chunk-N2WJPTF7.mjs} +3 -3
  74. package/dist/client/{chunk-KNTLBQ3N.mjs → chunk-QQNVQ62W.mjs} +1 -1
  75. package/dist/client/{chunk-B4H6QMPY.mjs → chunk-R3CZQLXF.mjs} +73 -8
  76. package/dist/client/{chunk-WHZQDPBZ.mjs → chunk-TNGL2WJF.mjs} +1 -0
  77. package/dist/client/{chunk-SLY6UDAS.mjs → chunk-ZJDBOLX7.mjs} +3 -3
  78. package/dist/client/client.mjs +548 -68
  79. package/dist/client/{courseRegistration-URX7RCCK.mjs → courseRegistration-JPWAQ6KM.mjs} +112 -12
  80. package/dist/client/{eventCalendar-GTENOQCE.mjs → eventCalendar-QDOCHBBV.mjs} +10 -10
  81. package/dist/client/{eventCombined-EZVB72MF.mjs → eventCombined-ZS2CXSIE.mjs} +10 -10
  82. package/dist/client/{eventDetails-CH4BJR5Z.mjs → eventDetails-5LOBMPLN.mjs} +10 -10
  83. package/dist/client/{eventListing-4BVOI5HS.mjs → eventListing-SBGY4I3L.mjs} +10 -10
  84. package/dist/client/{eventRegistration-37LPIKIE.mjs → eventRegistration-XGPIU3Z2.mjs} +10 -10
  85. package/dist/client/{form-FX3Y54YK.mjs → form-R7D4LZWW.mjs} +4 -4
  86. package/dist/client/{headerSection-UOZZGVYL.mjs → headerSection-RRLYUCGC.mjs} +3 -3
  87. package/dist/client/hooks.mjs +29 -9
  88. package/dist/client/{locationMap-VSIPKV35.mjs → locationMap-35ZJMRBZ.mjs} +3 -3
  89. package/dist/client/{newsletterForm-6FRJKNAF.mjs → newsletterForm-MJ2VCFUT.mjs} +4 -4
  90. package/dist/client/{productDetail-6AHMLXCO.mjs → productDetail-2O7YQNBX.mjs} +7 -7
  91. package/dist/client/{productList-M2CYIZVH.mjs → productList-FDP5BH7W.mjs} +7 -7
  92. package/dist/client/rendering/client.mjs +526 -64
  93. package/dist/client/rendering/islands.mjs +19 -19
  94. package/dist/client/rendering.mjs +547 -67
  95. package/dist/client/{shop-AUOT6GEX.mjs → shop-BGYJ3LDT.mjs} +7 -7
  96. package/dist/client/spam-protection.mjs +10 -10
  97. package/dist/preview-next/client/runtime.mjs +5 -5
  98. package/dist/{sdk-runtime-6GPJNLU6.mjs → sdk-runtime-B3TFXRXZ.mjs} +6 -6
  99. package/dist/server/components.mjs +179 -18
  100. package/dist/server/config-validation.mjs +30 -9
  101. package/dist/server/config.mjs +30 -9
  102. package/dist/server/data.mjs +29 -9
  103. package/dist/server/index.mjs +1 -1
  104. package/dist/server/navigation.mjs +2 -2
  105. package/dist/server/next.mjs +180 -19
  106. package/dist/server/page-converter.mjs +24 -7
  107. package/dist/server/prebuild.mjs +1 -1
  108. package/dist/server/rendering/server.mjs +179 -18
  109. package/dist/server/rendering.mjs +179 -18
  110. package/dist/server/routing.mjs +43 -19
  111. package/dist/server/server.mjs +30 -10
  112. package/package.json +1 -1
  113. package/dist/SdkPreviewModeRuntime-2UCIMKUB.mjs +0 -8
@@ -23,12 +23,13 @@ var init_result = __esm({
23
23
  });
24
24
 
25
25
  // ../core/src/branded.ts
26
- var unsafeAsMediaAssetId, unsafeAsStorageBucketName, unsafeAsPositiveByteCount, unsafeAsPositiveSeconds, unsafeAsFormId, unsafeAsPassId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, unsafeAsAppointmentResourceId;
26
+ var unsafeAsMediaAssetId, unsafeAsStorageBucketName, unsafeAsStoragePath, unsafeAsPositiveByteCount, unsafeAsPositiveSeconds, unsafeAsFormId, unsafeAsPassId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, unsafeAsAppointmentResourceId;
27
27
  var init_branded = __esm({
28
28
  "../core/src/branded.ts"() {
29
29
  "use strict";
30
30
  unsafeAsMediaAssetId = (id) => id;
31
31
  unsafeAsStorageBucketName = (value) => value;
32
+ unsafeAsStoragePath = (value) => value;
32
33
  unsafeAsPositiveByteCount = (value) => value;
33
34
  unsafeAsPositiveSeconds = (value) => value;
34
35
  unsafeAsFormId = (id) => id;
@@ -174,6 +175,7 @@ function publicEventPricingType(pricing) {
174
175
  return "free";
175
176
  case "single_paid":
176
177
  case "ticket_types":
178
+ case "sliding_scale":
177
179
  case "course":
178
180
  return isFreePublicEventPriceDisplay(pricing.display) ? "free" : "paid";
179
181
  case "external":
@@ -186,6 +188,7 @@ function publicEventPricingCurrency(pricing) {
186
188
  case "free_registration":
187
189
  case "single_paid":
188
190
  case "ticket_types":
191
+ case "sliding_scale":
189
192
  return pricing.currency;
190
193
  case "course":
191
194
  return pricing.display.display.currency;
@@ -203,6 +206,8 @@ function publicEventPricingMode(pricing) {
203
206
  return "single";
204
207
  case "ticket_types":
205
208
  return "ticket_types";
209
+ case "sliding_scale":
210
+ return "sliding_scale";
206
211
  case "drop_in":
207
212
  case "course":
208
213
  case "external":
@@ -236,6 +241,8 @@ function formatCoursePriceDisplayLabel(display, options) {
236
241
  return `Course (${formatCurrencyAmount(display.amount, display.currency, options)})`;
237
242
  case "from":
238
243
  return `Course (From ${formatCurrencyAmount(display.amount, display.currency, options)})`;
244
+ case "range":
245
+ return `Course (${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)})`;
239
246
  default:
240
247
  return assertNever(display);
241
248
  }
@@ -379,10 +386,11 @@ function omitZeroAmountFutureRows(rows) {
379
386
  if (rows.length <= 1) {
380
387
  return rows;
381
388
  }
382
- const [firstRow, ...futureRows] = rows;
383
- if (!firstRow) {
389
+ const firstRow = rows[0];
390
+ if (firstRow === void 0) {
384
391
  return [];
385
392
  }
393
+ const futureRows = rows.slice(1);
386
394
  return [
387
395
  firstRow,
388
396
  ...futureRows.filter((row) => row.amountCents > 0)
@@ -427,11 +435,11 @@ function isValidTimeZone(timeZone) {
427
435
  }
428
436
  function getSafeTimeZone(timeZone) {
429
437
  const normalized = typeof timeZone === "string" ? timeZone.trim() : "";
430
- return normalized && isValidTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
438
+ return normalized.length > 0 && isValidTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
431
439
  }
432
440
  function parseStrictBookingTimeZone(timeZone) {
433
441
  const normalized = timeZone.trim();
434
- if (!normalized || !isValidTimeZone(normalized)) {
442
+ if (normalized.length === 0 || !isValidTimeZone(normalized)) {
435
443
  return null;
436
444
  }
437
445
  return normalized;
@@ -517,7 +525,7 @@ function parseScheduledBookingDateTime(input) {
517
525
  try {
518
526
  const startsAt = toUtcIsoDateTimeString(input.startsAt);
519
527
  const timeZone = parseStrictBookingTimeZone(input.timeZone);
520
- if (!timeZone) {
528
+ if (timeZone === null) {
521
529
  return {
522
530
  ok: false,
523
531
  field: "timeZone",
@@ -656,7 +664,7 @@ function getBookingLocalDateKey(input) {
656
664
  }
657
665
  function parseBookingLocalDate(value) {
658
666
  const match = LOCAL_DATE_PATTERN.exec(value.trim());
659
- if (!match?.groups) {
667
+ if (match === null || match.groups === void 0) {
660
668
  throw new Error(`Invalid booking date format: "${value}" (expected YYYY-MM-DD)`);
661
669
  }
662
670
  const year = Number(match.groups.year);
@@ -715,10 +723,10 @@ function normalizeLocationAddressText(value) {
715
723
  return null;
716
724
  }
717
725
  const trimmed = value.trim();
718
- return trimmed ? trimmed : null;
726
+ return trimmed.length > 0 ? trimmed : null;
719
727
  }
720
728
  function hasLocationCoordinates(value) {
721
- return typeof value?.lat === "number" && typeof value?.lng === "number";
729
+ return value != null && typeof value.lat === "number" && typeof value.lng === "number";
722
730
  }
723
731
  function toLocationCoordinates(value) {
724
732
  if (!hasLocationCoordinates(value)) {
@@ -735,6 +743,13 @@ var init_location = __esm({
735
743
  }
736
744
  });
737
745
 
746
+ // ../core/src/string.ts
747
+ var init_string = __esm({
748
+ "../core/src/string.ts"() {
749
+ "use strict";
750
+ }
751
+ });
752
+
738
753
  // ../core/src/business-address.ts
739
754
  var init_business_address = __esm({
740
755
  "../core/src/business-address.ts"() {
@@ -875,13 +890,6 @@ var init_slug = __esm({
875
890
  }
876
891
  });
877
892
 
878
- // ../core/src/string.ts
879
- var init_string = __esm({
880
- "../core/src/string.ts"() {
881
- "use strict";
882
- }
883
- });
884
-
885
893
  // ../core/src/uuid.ts
886
894
  var init_uuid = __esm({
887
895
  "../core/src/uuid.ts"() {
@@ -1038,9 +1046,20 @@ var init_types = __esm({
1038
1046
  });
1039
1047
 
1040
1048
  // ../media-core/src/fileSafety.ts
1049
+ function containsUnsafePathSegmentCharacter(value) {
1050
+ return value.includes("/") || value.includes("\\") || CONTROL_CHARACTER_PATTERN.test(value);
1051
+ }
1052
+ function isReservedPathSegment(value) {
1053
+ return value === "." || value === "..";
1054
+ }
1055
+ function isUnsafePathSegment(segment) {
1056
+ return segment.trim().length === 0 || segment !== segment.trim() || isReservedPathSegment(segment) || containsUnsafePathSegmentCharacter(segment);
1057
+ }
1058
+ var CONTROL_CHARACTER_PATTERN;
1041
1059
  var init_fileSafety = __esm({
1042
1060
  "../media-core/src/fileSafety.ts"() {
1043
1061
  "use strict";
1062
+ CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
1044
1063
  }
1045
1064
  });
1046
1065
 
@@ -1080,11 +1099,21 @@ var init_fileDomain = __esm({
1080
1099
  });
1081
1100
 
1082
1101
  // ../media-core/src/fileStorage.ts
1102
+ function parseStoragePath(value) {
1103
+ return joinStoragePathSegments(value.split("/"));
1104
+ }
1105
+ function joinStoragePathSegments(segments) {
1106
+ if (segments.some(isUnsafePathSegment)) {
1107
+ return err("unsafe-path-segment");
1108
+ }
1109
+ return ok(unsafeAsStoragePath(segments.join("/")));
1110
+ }
1083
1111
  var FILE_STORAGE_BUCKETS, PUBLIC_SITE_FILE_STORAGE_BUCKETS, PRIVATE_SITE_FILE_BUCKET_MIME_TYPES, TEMP_UPLOAD_BUCKET_MIME_TYPES;
1084
1112
  var init_fileStorage = __esm({
1085
1113
  "../media-core/src/fileStorage.ts"() {
1086
1114
  "use strict";
1087
1115
  init_src();
1116
+ init_fileSafety();
1088
1117
  FILE_STORAGE_BUCKETS = {
1089
1118
  publicSite: unsafeAsStorageBucketName("media"),
1090
1119
  privateSite: unsafeAsStorageBucketName("site-files-private"),
@@ -1270,14 +1299,18 @@ var init_uploadVerificationContract = __esm({
1270
1299
  });
1271
1300
 
1272
1301
  // ../media-core/src/videoPrep.ts
1302
+ function unsafeAsPositiveVideoDurationSeconds(value) {
1303
+ return value;
1304
+ }
1273
1305
  var VIDEO_PREP_LIMITS;
1274
1306
  var init_videoPrep = __esm({
1275
1307
  "../media-core/src/videoPrep.ts"() {
1276
1308
  "use strict";
1309
+ init_src();
1277
1310
  VIDEO_PREP_LIMITS = {
1278
- maxRawByteCount: 500 * 1024 * 1024,
1279
- inlineContentMaxDurationSeconds: 60,
1280
- backgroundVideoMaxDurationSeconds: 30,
1311
+ maxRawByteCount: unsafeAsPositiveByteCount(500 * 1024 * 1024),
1312
+ inlineContentMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(60),
1313
+ backgroundVideoMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(30),
1281
1314
  targetMaxDimensionPixels: 1080
1282
1315
  };
1283
1316
  }
@@ -1348,10 +1381,10 @@ var init_identifiers = __esm({
1348
1381
 
1349
1382
  // ../media-core/src/url.ts
1350
1383
  function isRelativeDirectImageStoragePath(storagePath) {
1351
- return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix));
1384
+ return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix)) && parseStoragePath(storagePath).ok;
1352
1385
  }
1353
1386
  function isSiteScopedDirectImageStoragePath(storagePath) {
1354
- return storagePath.startsWith("sites/");
1387
+ return storagePath.startsWith("sites/") && parseStoragePath(storagePath).ok;
1355
1388
  }
1356
1389
  function encodeStoragePath(storagePath) {
1357
1390
  return storagePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
@@ -1432,6 +1465,7 @@ var DEFAULT_BASE_PATH, RELATIVE_STORAGE_PATH_PREFIXES, formatNumber, ORDER, getI
1432
1465
  var init_url = __esm({
1433
1466
  "../media-core/src/url.ts"() {
1434
1467
  "use strict";
1468
+ init_fileStorage();
1435
1469
  DEFAULT_BASE_PATH = "/api/img";
1436
1470
  RELATIVE_STORAGE_PATH_PREFIXES = [
1437
1471
  "originals/",
@@ -22918,9 +22952,12 @@ var init_schemas = __esm({
22918
22952
  });
22919
22953
  coursePricingSchema = z45.object({
22920
22954
  bookingRequirement: z45.enum(["none", "required"]),
22921
- pricingStrategy: z45.enum(["free", "single", "tiered"]),
22955
+ pricingStrategy: z45.enum(["free", "single", "tiered", "sliding_scale"]),
22922
22956
  currency: z45.string(),
22923
22957
  singlePriceAmount: z45.number().nullable(),
22958
+ slidingMinAmount: z45.number().nullable().optional(),
22959
+ slidingMaxAmount: z45.number().nullable().optional(),
22960
+ slidingSuggestedAmount: z45.number().nullable().optional(),
22924
22961
  paymentTerms: blockBookingPaymentTermsSchema,
22925
22962
  options: z45.array(coursePricingOptionSchema)
22926
22963
  });
@@ -23085,6 +23122,19 @@ var init_schemas2 = __esm({
23085
23122
  ticketTypes: z46.array(publicEventTicketTypeSchema),
23086
23123
  display: publicEventTicketTypePriceDisplaySchema
23087
23124
  }),
23125
+ z46.object({
23126
+ kind: z46.literal("sliding_scale"),
23127
+ minAmount: z46.number(),
23128
+ maxAmount: z46.number(),
23129
+ suggestedAmount: z46.number().nullable(),
23130
+ currency: z46.string(),
23131
+ display: z46.object({
23132
+ kind: z46.literal("range"),
23133
+ minAmount: z46.number(),
23134
+ maxAmount: z46.number(),
23135
+ currency: z46.string()
23136
+ })
23137
+ }),
23088
23138
  z46.object({
23089
23139
  kind: z46.literal("course"),
23090
23140
  display: z46.object({ kind: z46.literal("course"), display: publicCoursePriceDisplaySchema })
@@ -23553,7 +23603,7 @@ function isRecord6(value) {
23553
23603
  }
23554
23604
  function isPublicEventPricing(value) {
23555
23605
  if (!isRecord6(value) || typeof value.kind !== "string") return false;
23556
- return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "ticket_types" || value.kind === "course" || value.kind === "external";
23606
+ 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";
23557
23607
  }
23558
23608
  function isPublicEventTicketType(value) {
23559
23609
  return isRecord6(value) && typeof value.id === "string";
@@ -24066,7 +24116,9 @@ function getSelectableCoursePricingOptionsForNow(course, now) {
24066
24116
  return left.name.localeCompare(right.name);
24067
24117
  });
24068
24118
  }
24069
- function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {}) {
24119
+ function resolveCoursePricingState(course, selectedPricingOptionId, selectedSlidingAmountOrNowInput, nowInput = {}) {
24120
+ const selectedSlidingAmount = typeof selectedSlidingAmountOrNowInput === "number" || selectedSlidingAmountOrNowInput === null ? selectedSlidingAmountOrNowInput : null;
24121
+ const effectiveNowInput = typeof selectedSlidingAmountOrNowInput === "object" && selectedSlidingAmountOrNowInput !== null ? selectedSlidingAmountOrNowInput : nowInput;
24070
24122
  const currency = course.pricing?.currency ?? course.currency;
24071
24123
  if (!course.pricing) {
24072
24124
  const legacyAmount = course.priceCents ?? 0;
@@ -24076,6 +24128,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
24076
24128
  selectableOptions: [],
24077
24129
  requiresSelection: false,
24078
24130
  selectedOption: null,
24131
+ slidingScale: null,
24132
+ selectedSlidingAmount: null,
24079
24133
  selectedPriceAmount: legacyAmount,
24080
24134
  selectedRequiresPayment: legacyAmount > 0,
24081
24135
  hasPaidSelection: legacyAmount > 0,
@@ -24090,6 +24144,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
24090
24144
  selectableOptions: [],
24091
24145
  requiresSelection: false,
24092
24146
  selectedOption: null,
24147
+ slidingScale: null,
24148
+ selectedSlidingAmount: null,
24093
24149
  selectedPriceAmount: 0,
24094
24150
  selectedRequiresPayment: false,
24095
24151
  hasPaidSelection: false,
@@ -24103,6 +24159,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
24103
24159
  selectableOptions: [],
24104
24160
  requiresSelection: false,
24105
24161
  selectedOption: null,
24162
+ slidingScale: null,
24163
+ selectedSlidingAmount: null,
24106
24164
  selectedPriceAmount: amount,
24107
24165
  selectedRequiresPayment: amount > 0,
24108
24166
  hasPaidSelection: amount > 0,
@@ -24112,7 +24170,7 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
24112
24170
  case "tiered": {
24113
24171
  const selectableOptions = getSelectableCoursePricingOptionsForNow(
24114
24172
  course,
24115
- resolvePlanningDateInput(nowInput)
24173
+ resolvePlanningDateInput(effectiveNowInput)
24116
24174
  );
24117
24175
  const selectedOption = (selectedPricingOptionId ? selectableOptions.find((option) => option.id === selectedPricingOptionId) : null) ?? (selectableOptions.length === 1 ? selectableOptions[0] ?? null : null);
24118
24176
  const lowestAmount = selectableOptions.reduce((lowest, option) => {
@@ -24128,12 +24186,51 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
24128
24186
  selectableOptions,
24129
24187
  requiresSelection: selectableOptions.length > 1,
24130
24188
  selectedOption,
24189
+ slidingScale: null,
24190
+ selectedSlidingAmount: null,
24131
24191
  selectedPriceAmount: selectedOption?.priceAmount ?? null,
24132
24192
  selectedRequiresPayment: (selectedOption?.priceAmount ?? 0) > 0,
24133
24193
  hasPaidSelection: selectableOptions.some((option) => option.priceAmount > 0),
24134
24194
  hasFreeSelection: selectableOptions.some((option) => option.priceAmount === 0)
24135
24195
  };
24136
24196
  }
24197
+ case "sliding_scale": {
24198
+ const minAmount = course.pricing.slidingMinAmount ?? null;
24199
+ const maxAmount = course.pricing.slidingMaxAmount ?? null;
24200
+ if (minAmount === null || maxAmount === null || minAmount < 0 || maxAmount < minAmount) {
24201
+ return {
24202
+ currency,
24203
+ display: { kind: "unavailable", currency },
24204
+ selectableOptions: [],
24205
+ requiresSelection: true,
24206
+ selectedOption: null,
24207
+ slidingScale: null,
24208
+ selectedSlidingAmount: null,
24209
+ selectedPriceAmount: null,
24210
+ selectedRequiresPayment: false,
24211
+ hasPaidSelection: false,
24212
+ hasFreeSelection: false
24213
+ };
24214
+ }
24215
+ const normalizedSelectedAmount = typeof selectedSlidingAmount === "number" && Number.isFinite(selectedSlidingAmount) ? selectedSlidingAmount : null;
24216
+ return {
24217
+ currency,
24218
+ display: { kind: "range", minAmount, maxAmount, currency },
24219
+ selectableOptions: [],
24220
+ requiresSelection: normalizedSelectedAmount === null,
24221
+ selectedOption: null,
24222
+ slidingScale: {
24223
+ minAmount,
24224
+ maxAmount,
24225
+ suggestedAmount: course.pricing.slidingSuggestedAmount ?? null
24226
+ },
24227
+ selectedSlidingAmount: normalizedSelectedAmount,
24228
+ selectedPriceAmount: normalizedSelectedAmount,
24229
+ selectedRequiresPayment: (normalizedSelectedAmount ?? 0) > 0,
24230
+ hasPaidSelection: maxAmount > 0,
24231
+ hasFreeSelection: minAmount === 0
24232
+ };
24233
+ }
24137
24234
  }
24138
24235
  }
24139
24236
  function formatCoursePriceDisplay(display, locale) {
@@ -24607,7 +24704,7 @@ function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
24607
24704
  if (view === "week") {
24608
24705
  const weekStart = days[0];
24609
24706
  const weekEnd = days[6];
24610
- if (!weekStart || !weekEnd) {
24707
+ if (weekStart === void 0 || weekEnd === void 0) {
24611
24708
  return `${monthNames[viewDate.getMonth()] ?? ""} ${viewDate.getFullYear()}`;
24612
24709
  }
24613
24710
  const startMonth = monthNames[weekStart.getMonth()] ?? "";
@@ -24739,7 +24836,9 @@ function getLocalDateKey(isoTimestamp, timeZone) {
24739
24836
  const year = parts.find((part) => part.type === "year")?.value;
24740
24837
  const month = parts.find((part) => part.type === "month")?.value;
24741
24838
  const day = parts.find((part) => part.type === "day")?.value;
24742
- if (!year || !month || !day) return null;
24839
+ if (year === void 0 || year.length === 0) return null;
24840
+ if (month === void 0 || month.length === 0) return null;
24841
+ if (day === void 0 || day.length === 0) return null;
24743
24842
  return `${year}-${month}-${day}`;
24744
24843
  }
24745
24844
  function isValidTimeZone2(timeZone) {
@@ -24996,7 +25095,7 @@ function enumerateDateKeys(startDateKey, endDateKey) {
24996
25095
  }
24997
25096
  function parseDateKey(dateKey) {
24998
25097
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
24999
- if (!match) {
25098
+ if (match === null) {
25000
25099
  throw new Error(`Invalid timetable date key: ${dateKey}`);
25001
25100
  }
25002
25101
  const [, year, month, day] = match;
@@ -25032,7 +25131,7 @@ function groupItemsByDate(items, accessors) {
25032
25131
  const dateKey = getLocalDateKey(startsAt, timeZone);
25033
25132
  if (dateKey == null) continue;
25034
25133
  const existing = grouped.get(dateKey);
25035
- if (existing) {
25134
+ if (existing !== void 0) {
25036
25135
  existing.push(item);
25037
25136
  } else {
25038
25137
  grouped.set(dateKey, [item]);
@@ -29379,6 +29478,7 @@ var init_booking_form_reserved_fields = __esm({
29379
29478
  "selectedDate",
29380
29479
  "selectedSlot",
29381
29480
  "discountCode",
29481
+ "selectedSlidingAmountInput",
29382
29482
  "selectedCollectionOptionKind",
29383
29483
  "selectedAppointmentPackageId"
29384
29484
  ];
@@ -29664,6 +29764,32 @@ function readOptionalString(value) {
29664
29764
  function readOptionalNumber(value) {
29665
29765
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
29666
29766
  }
29767
+ function getBookingServiceSlidingScale(service) {
29768
+ const pricing = service?.pricing;
29769
+ if (!pricing || pricing.pricingStrategy !== "sliding_scale" || pricing.slidingMinAmount === null || pricing.slidingMinAmount === void 0 || pricing.slidingMaxAmount === null || pricing.slidingMaxAmount === void 0) {
29770
+ return null;
29771
+ }
29772
+ return {
29773
+ minAmount: pricing.slidingMinAmount,
29774
+ maxAmount: pricing.slidingMaxAmount,
29775
+ suggestedAmount: pricing.slidingSuggestedAmount ?? null,
29776
+ currency: pricing.currency
29777
+ };
29778
+ }
29779
+ function parseBookingCurrencyInputToCents(value) {
29780
+ const trimmed = value?.trim() ?? "";
29781
+ if (!trimmed) return null;
29782
+ if (!/^(?:\d+(?:\.\d{0,2})?|\.\d{1,2})$/.test(trimmed)) return null;
29783
+ const parsed = Number(trimmed);
29784
+ if (!Number.isFinite(parsed) || parsed < 0) return null;
29785
+ return Math.round(parsed * 100);
29786
+ }
29787
+ function formatBookingCentsAsCurrencyInput(amount) {
29788
+ return amount % 100 === 0 ? String(amount / 100) : (amount / 100).toFixed(2);
29789
+ }
29790
+ function isBookingServiceSlidingAmountInRange(slidingScale, selectedAmount) {
29791
+ return selectedAmount !== null && selectedAmount >= slidingScale.minAmount && selectedAmount <= slidingScale.maxAmount;
29792
+ }
29667
29793
  function readStringArray(value) {
29668
29794
  if (!Array.isArray(value)) return void 0;
29669
29795
  const strings = value.filter((item) => typeof item === "string");
@@ -29852,6 +29978,20 @@ function prepareBookingSubmission(input) {
29852
29978
  };
29853
29979
  }
29854
29980
  const paymentSelectionMode = getBookingServicePaymentSelectionMode(selectedService);
29981
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
29982
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
29983
+ input.data.selectedSlidingAmountInput
29984
+ );
29985
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(slidingScale, selectedSlidingAmount)) {
29986
+ return {
29987
+ ok: false,
29988
+ error: {
29989
+ tag: selectedSlidingAmount === null ? "sliding-amount-required" : "sliding-amount-out-of-range",
29990
+ serviceId,
29991
+ serviceTitle: selectedService.title || serviceId
29992
+ }
29993
+ };
29994
+ }
29855
29995
  if (paymentSelectionMode === "choice_required" && !toSelectedCollectionOptionKind(input.data.selectedCollectionOptionKind)) {
29856
29996
  return {
29857
29997
  ok: false,
@@ -29879,6 +30019,7 @@ function prepareBookingSubmission(input) {
29879
30019
  ...common,
29880
30020
  kind: "standard",
29881
30021
  ...discountCode ? { discountCode } : {},
30022
+ ...slidingScale && selectedSlidingAmount !== null ? { selectedSlidingAmount } : {},
29882
30023
  ...selectedCollectionOptionKind ? { selectedCollectionOptionKind } : {},
29883
30024
  altchaSolution: input.altchaSolution ?? null
29884
30025
  }
@@ -29892,6 +30033,10 @@ function formatBookingSubmissionPreparationError(error) {
29892
30033
  return `Service not found: ${error.serviceId}`;
29893
30034
  case "payment-option-required":
29894
30035
  return `Please choose how you want to pay for "${error.serviceTitle}".`;
30036
+ case "sliding-amount-required":
30037
+ return `Please choose an amount for "${error.serviceTitle}".`;
30038
+ case "sliding-amount-out-of-range":
30039
+ return `Please choose an amount within the sliding scale for "${error.serviceTitle}".`;
29895
30040
  case "payment-option-unsupported":
29896
30041
  return `Service "${error.serviceTitle}" has a payment option that is not supported yet.`;
29897
30042
  case "service-missing-duration":
@@ -30042,6 +30187,7 @@ var init_booking_form_interactive = __esm({
30042
30187
  title: z66.string(),
30043
30188
  description: z66.string().nullable().optional().transform((value) => value ?? void 0),
30044
30189
  durationMinutes: z66.number().optional(),
30190
+ pricing: z66.custom().optional(),
30045
30191
  priceAmount: z66.number().nullable().optional(),
30046
30192
  priceCurrency: z66.string().nullable().optional(),
30047
30193
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
@@ -34960,12 +35106,13 @@ var init_carousel_client = __esm({
34960
35106
  ]
34961
35107
  );
34962
35108
  const resolvedSlides = resolveSlidesToShowMobile(normalized.slidesToShow);
35109
+ const canAutoplay = normalized.autoplay && slides.length > 1;
34963
35110
  const cssVars = React12.useMemo(
34964
35111
  () => getCarouselSlidesToShowCssVars(normalized.slidesToShow),
34965
35112
  [normalized.slidesToShow]
34966
35113
  );
34967
35114
  const plugins = React12.useMemo(() => {
34968
- if (!normalized.autoplay) return void 0;
35115
+ if (!canAutoplay) return void 0;
34969
35116
  return [
34970
35117
  Autoplay({
34971
35118
  delay: normalized.autoplayDelay,
@@ -34975,7 +35122,7 @@ var init_carousel_client = __esm({
34975
35122
  // Continue after manual navigation
34976
35123
  })
34977
35124
  ];
34978
- }, [normalized.autoplay, normalized.autoplayDelay]);
35125
+ }, [canAutoplay, normalized.autoplayDelay]);
34979
35126
  const [viewportRef, api] = useEmblaCarousel(
34980
35127
  {
34981
35128
  align: "start",
@@ -35014,7 +35161,7 @@ var init_carousel_client = __esm({
35014
35161
  };
35015
35162
  }, [api]);
35016
35163
  React12.useEffect(() => {
35017
- if (!api || !normalized.autoplay) return;
35164
+ if (!api || !canAutoplay) return;
35018
35165
  const autoplayPlugin = api.plugins()?.autoplay;
35019
35166
  if (!autoplayPlugin) return;
35020
35167
  const handleVisibility = () => {
@@ -35023,7 +35170,7 @@ var init_carousel_client = __esm({
35023
35170
  };
35024
35171
  document.addEventListener("visibilitychange", handleVisibility);
35025
35172
  return () => document.removeEventListener("visibilitychange", handleVisibility);
35026
- }, [api, normalized.autoplay]);
35173
+ }, [api, canAutoplay]);
35027
35174
  const inner = /* @__PURE__ */ jsxs53(Fragment13, { children: [
35028
35175
  /* @__PURE__ */ jsx68("div", { className: "carousel-frame rb-overflow-hidden rb-w-full", children: /* @__PURE__ */ jsx68(
35029
35176
  "div",
@@ -35074,7 +35221,7 @@ var init_carousel_client = __esm({
35074
35221
  "data-show-controls": normalized.showControls ? "true" : "false",
35075
35222
  "data-show-dots": normalized.showDots ? "true" : "false",
35076
35223
  "data-arrow-visibility": normalized.arrowVisibility,
35077
- "data-autoplay": normalized.autoplay ? "true" : "false",
35224
+ "data-autoplay": canAutoplay ? "true" : "false",
35078
35225
  "data-loop": normalized.loop ? "true" : "false",
35079
35226
  children: inner
35080
35227
  }
@@ -36077,6 +36224,7 @@ function createBlockApi(config) {
36077
36224
  body: {
36078
36225
  courseId: data.courseId,
36079
36226
  ...data.pricingOptionId ? { pricingOptionId: data.pricingOptionId } : {},
36227
+ ...data.selectedSlidingAmount !== void 0 ? { selectedSlidingAmount: data.selectedSlidingAmount } : {},
36080
36228
  email: data.email,
36081
36229
  name: data.name,
36082
36230
  phone: data.phone,
@@ -39257,22 +39405,22 @@ function AltchaWidget({
39257
39405
  if (typeof window === "undefined") return;
39258
39406
  let widget = null;
39259
39407
  Promise.resolve().then(() => (init_altcha(), altcha_exports)).then(() => {
39260
- if (!containerRef.current) return;
39408
+ if (containerRef.current === null) return;
39261
39409
  widget = document.createElement("altcha-widget");
39262
39410
  widget.setAttribute("challengeurl", challengeUrl);
39263
- if (auto) {
39411
+ if (auto === true) {
39264
39412
  widget.setAttribute("auto", "onload");
39265
39413
  }
39266
- if (hidelogo) {
39414
+ if (hidelogo === true) {
39267
39415
  widget.setAttribute("hidelogo", "true");
39268
39416
  }
39269
39417
  widget.setAttribute("hidefooter", "true");
39270
- if (className) {
39418
+ if (className !== void 0 && className.length > 0) {
39271
39419
  widget.className = className;
39272
39420
  }
39273
39421
  widget.addEventListener("statechange", ((event) => {
39274
39422
  const { state, payload } = event.detail;
39275
- if (state === "verified" && payload) {
39423
+ if (state === "verified" && payload !== void 0 && payload.length > 0) {
39276
39424
  onSolutionChange?.(payload);
39277
39425
  } else if (state === "error" || state === "expired") {
39278
39426
  onSolutionChange?.(null);
@@ -39283,7 +39431,7 @@ function AltchaWidget({
39283
39431
  console.error("Failed to load ALTCHA widget:", err3);
39284
39432
  });
39285
39433
  return () => {
39286
- if (widget && containerRef.current) {
39434
+ if (widget !== null && containerRef.current !== null) {
39287
39435
  containerRef.current.removeChild(widget);
39288
39436
  }
39289
39437
  };
@@ -39329,15 +39477,15 @@ var init_dist2 = __esm({
39329
39477
  // ../altcha/src/index.ts
39330
39478
  function getCmsApiUrl2() {
39331
39479
  const env = process.env.RIVERBANK_ENV;
39332
- if (env) {
39480
+ if (env !== void 0 && env.length > 0) {
39333
39481
  const envKey = `RIVERBANK_${env.toUpperCase()}_DASHBOARD_URL`;
39334
39482
  const dashboardUrl2 = process.env[envKey];
39335
- if (dashboardUrl2) {
39483
+ if (dashboardUrl2 !== void 0 && dashboardUrl2.length > 0) {
39336
39484
  return `${dashboardUrl2.replace(/\/$/, "")}/api`;
39337
39485
  }
39338
39486
  }
39339
39487
  const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;
39340
- if (dashboardUrl) {
39488
+ if (dashboardUrl !== void 0 && dashboardUrl.length > 0) {
39341
39489
  return `${dashboardUrl.replace(/\/$/, "")}/api`;
39342
39490
  }
39343
39491
  throw new Error("Dashboard URL not configured. Set NEXT_PUBLIC_DASHBOARD_URL");
@@ -40277,6 +40425,7 @@ function useBookingSubmission(_siteId) {
40277
40425
  customFields: data.customFields,
40278
40426
  timezone: data.timezone,
40279
40427
  discountCode: data.discountCode,
40428
+ selectedSlidingAmount: data.selectedSlidingAmount,
40280
40429
  selectedCollectionOptionKind: data.selectedCollectionOptionKind,
40281
40430
  altchaSolution: data.altchaSolution ?? null,
40282
40431
  successUrl: data.successUrl,
@@ -40805,6 +40954,9 @@ function createInitialBookingFlowReducerState(input) {
40805
40954
  fieldValues: collectInitialFieldValues(initialData),
40806
40955
  touchedFields: {},
40807
40956
  discountCode: toOptionalString(initialData.discountCode) ?? "",
40957
+ selectedSlidingAmountInput: toOptionalString(
40958
+ initialData.selectedSlidingAmountInput
40959
+ ) ?? "",
40808
40960
  selectedCollectionOptionKind: toSelectedCollectionOptionKind(
40809
40961
  initialData.selectedCollectionOptionKind
40810
40962
  ),
@@ -40857,6 +41009,7 @@ function bookingFlowReducer(state, event) {
40857
41009
  selectedDate: void 0,
40858
41010
  selectedSlot: null,
40859
41011
  selectedAppointmentPackageId: void 0,
41012
+ selectedSlidingAmountInput: "",
40860
41013
  selectedCollectionOptionKind: void 0,
40861
41014
  validationErrors: clearBookingFormError(state.validationErrors)
40862
41015
  };
@@ -40910,6 +41063,13 @@ function bookingFlowReducer(state, event) {
40910
41063
  selectedAppointmentPackageId: void 0,
40911
41064
  validationErrors: clearBookingFormError(state.validationErrors)
40912
41065
  };
41066
+ case "sliding-amount-changed":
41067
+ return {
41068
+ ...state,
41069
+ selectedSlidingAmountInput: event.value,
41070
+ selectedAppointmentPackageId: void 0,
41071
+ validationErrors: clearBookingFormError(state.validationErrors)
41072
+ };
40913
41073
  case "appointment-package-selected":
40914
41074
  return {
40915
41075
  ...state,
@@ -41012,6 +41172,7 @@ function toBookingFormData(state) {
41012
41172
  selectedDate: state.selectedDate,
41013
41173
  selectedSlot: state.selectedSlot?.startAt,
41014
41174
  discountCode: state.discountCode || void 0,
41175
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput || void 0,
41015
41176
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
41016
41177
  selectedAppointmentPackageId: state.selectedAppointmentPackageId
41017
41178
  };
@@ -41029,7 +41190,8 @@ function buildBookingStepResolution(input) {
41029
41190
  input.form.settings?.serviceIds && input.form.settings.serviceIds.length > 1
41030
41191
  );
41031
41192
  const selectedService = paymentResolution.service;
41032
- const needsPayment = paymentResolution.mode !== "hidden" || Boolean(
41193
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
41194
+ const needsPayment = Boolean(selectedServiceSlidingScale) || paymentResolution.mode !== "hidden" || Boolean(
41033
41195
  input.enableAppointmentPackages && selectedService && (selectedService.priceAmount ?? 0) > 0
41034
41196
  );
41035
41197
  const hasFields = Boolean(input.form.schema?.fields.length);
@@ -41073,6 +41235,18 @@ function validateBookingStep(input) {
41073
41235
  services: input.services,
41074
41236
  defaultServiceId: input.defaultServiceId
41075
41237
  });
41238
+ const slidingScale = getBookingServiceSlidingScale(resolution.service);
41239
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(
41240
+ slidingScale,
41241
+ parseBookingCurrencyInputToCents(input.data.selectedSlidingAmountInput)
41242
+ )) {
41243
+ return {
41244
+ ok: false,
41245
+ errors: createFormBookingValidationError(
41246
+ "Please choose an amount within the sliding scale"
41247
+ )
41248
+ };
41249
+ }
41076
41250
  if (resolution.mode === "hidden" || resolution.mode === "disclosure_only") {
41077
41251
  return { ok: true };
41078
41252
  }
@@ -41471,6 +41645,7 @@ var init_useHeadlessBookingFlow_state = __esm({
41471
41645
  "../blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.ts"() {
41472
41646
  "use strict";
41473
41647
  init_booking_form_headless_state();
41648
+ init_booking_form_state();
41474
41649
  }
41475
41650
  });
41476
41651
 
@@ -41510,11 +41685,27 @@ function useHeadlessBookingFlow(options) {
41510
41685
  const selectedServiceId = state.selectedServiceId ?? defaultServiceId;
41511
41686
  const selectedResourceId = state.selectedResourceId ?? defaultResourceId;
41512
41687
  const selectedService = options.services.find((service) => service.id === selectedServiceId) ?? null;
41688
+ const lastSlidingServiceIdRef = useRef9(null);
41689
+ useEffect10(() => {
41690
+ const selectedId = selectedService?.id ?? null;
41691
+ if (lastSlidingServiceIdRef.current === selectedId) return;
41692
+ lastSlidingServiceIdRef.current = selectedId;
41693
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
41694
+ dispatch({
41695
+ type: "sliding-amount-changed",
41696
+ value: slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatBookingCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
41697
+ });
41698
+ }, [selectedService]);
41513
41699
  const hasPortalBearerAuth = (api.config.authOverrides?.portal ?? api.config.auth).type === "bearer";
41700
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
41701
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
41702
+ state.selectedSlidingAmountInput
41703
+ );
41704
+ const selectedAppointmentPriceAmount = selectedServiceSlidingScale && selectedSlidingAmount !== null ? selectedSlidingAmount : selectedService?.priceAmount ?? 0;
41514
41705
  const canLoadAppointmentPackages = Boolean(
41515
- options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && (selectedService.priceAmount ?? 0) > 0
41706
+ options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && !selectedServiceSlidingScale && (selectedService.priceAmount ?? 0) > 0
41516
41707
  );
41517
- const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
41708
+ const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || selectedServiceSlidingScale || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
41518
41709
  const data = useMemo8(() => toBookingFormData(state), [state]);
41519
41710
  const availabilityDatesQuery = useMemo8(
41520
41711
  () => buildBookingAvailabilityQuery({
@@ -41744,13 +41935,13 @@ function useHeadlessBookingFlow(options) {
41744
41935
  state.selectedSlot
41745
41936
  ]);
41746
41937
  const appointmentDiscountContext = useMemo8(
41747
- () => selectedService && (selectedService.priceAmount ?? 0) > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
41748
- [selectedService?.id, selectedService?.priceAmount]
41938
+ () => selectedService && selectedAppointmentPriceAmount > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
41939
+ [selectedAppointmentPriceAmount, selectedService?.id]
41749
41940
  );
41750
41941
  const discountUi = useDiscountCodeUi({
41751
41942
  api,
41752
41943
  discountContext: appointmentDiscountContext,
41753
- subtotalCents: selectedService?.priceAmount ?? 0
41944
+ subtotalCents: selectedAppointmentPriceAmount
41754
41945
  });
41755
41946
  const { submit, clearFeedback } = useBookingSubmission(options.siteId);
41756
41947
  const submittingRef = useRef9(false);
@@ -41893,6 +42084,7 @@ function useHeadlessBookingFlow(options) {
41893
42084
  type: "appointment-package-selected",
41894
42085
  packageId: packageId ? unsafeAsCustomerPassId(packageId) : null
41895
42086
  }),
42087
+ setSlidingAmountInput: (value) => dispatch({ type: "sliding-amount-changed", value }),
41896
42088
  setDiscountCode: (code) => {
41897
42089
  dispatch({ type: "discount-code-changed", code });
41898
42090
  latestRef.current.setDiscountCode(code);
@@ -41963,11 +42155,11 @@ function useHeadlessBookingFlow(options) {
41963
42155
  () => selectedService && paymentResolution.mode !== "unsupported" ? buildBookingPaymentSelectionViewModel({
41964
42156
  serviceTitle: selectedService.title,
41965
42157
  paymentTerms: selectedService.paymentTerms,
41966
- priceAmount: selectedService.priceAmount,
42158
+ priceAmount: selectedAppointmentPriceAmount,
41967
42159
  priceCurrency: selectedService.priceCurrency,
41968
42160
  bookingStartAt: state.selectedSlot?.startAt
41969
42161
  }) : null,
41970
- [paymentResolution.mode, selectedService, state.selectedSlot?.startAt]
42162
+ [paymentResolution.mode, selectedAppointmentPriceAmount, selectedService, state.selectedSlot?.startAt]
41971
42163
  );
41972
42164
  const appointmentPackageItems = appointmentPackages.tag === "loaded" ? appointmentPackages.value : [];
41973
42165
  const selectedAppointmentPackage = appointmentPackageItems.find(
@@ -41996,6 +42188,9 @@ function useHeadlessBookingFlow(options) {
41996
42188
  selection: null,
41997
42189
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
41998
42190
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
42191
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
42192
+ selectedSlidingAmount,
42193
+ slidingScale: selectedServiceSlidingScale,
41999
42194
  appointmentPackages: appointmentPackagesModel,
42000
42195
  errorMessage: paymentResolution.errorMessage ?? "This service cannot be booked right now."
42001
42196
  } : {
@@ -42004,6 +42199,9 @@ function useHeadlessBookingFlow(options) {
42004
42199
  selection: paymentSelection,
42005
42200
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
42006
42201
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
42202
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
42203
+ selectedSlidingAmount,
42204
+ slidingScale: selectedServiceSlidingScale,
42007
42205
  appointmentPackages: appointmentPackagesModel
42008
42206
  };
42009
42207
  return {
@@ -42618,6 +42816,9 @@ var init_FieldValidationMessage = __esm({
42618
42816
 
42619
42817
  // ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
42620
42818
  import { Fragment as Fragment15, jsx as jsx78, jsxs as jsxs59 } from "react/jsx-runtime";
42819
+ function formatBookingAmount(amount, currency) {
42820
+ return formatCurrencyAmount(amount, currency, { decimals: "auto" });
42821
+ }
42621
42822
  function HeadlessBookingDefaultForm({
42622
42823
  booking,
42623
42824
  onSubmit
@@ -42894,6 +43095,7 @@ function HeadlessBookingPaymentSelectionStep({
42894
43095
  }
42895
43096
  const packageOptions = booking.payment.appointmentPackages.items;
42896
43097
  const hasPackageOptions = packageOptions.length > 0;
43098
+ const slidingScale = booking.payment.slidingScale;
42897
43099
  return /* @__PURE__ */ jsxs59("div", { className: "ab-stack", children: [
42898
43100
  /* @__PURE__ */ jsxs59("div", { className: "ab-info-box", children: [
42899
43101
  /* @__PURE__ */ jsx78("h3", { className: "ab-subtitle", children: viewModel.sectionTitle }),
@@ -42904,6 +43106,28 @@ function HeadlessBookingPaymentSelectionStep({
42904
43106
  booking.payment.appointmentPackages.state.message,
42905
43107
  " You can continue with the normal payment option."
42906
43108
  ] }) : null,
43109
+ slidingScale ? /* @__PURE__ */ jsxs59("div", { className: "rb-space-y-2", children: [
43110
+ /* @__PURE__ */ jsx78("label", { htmlFor: "appointment-sliding-amount", className: "form-label", children: "Choose your price" }),
43111
+ /* @__PURE__ */ jsxs59("p", { className: "rb-caption status-muted", children: [
43112
+ formatBookingAmount(slidingScale.minAmount, slidingScale.currency),
43113
+ " to ",
43114
+ formatBookingAmount(slidingScale.maxAmount, slidingScale.currency),
43115
+ slidingScale.suggestedAmount !== null ? `, suggested ${formatBookingAmount(slidingScale.suggestedAmount, slidingScale.currency)}` : ""
43116
+ ] }),
43117
+ /* @__PURE__ */ jsx78(
43118
+ "input",
43119
+ {
43120
+ id: "appointment-sliding-amount",
43121
+ type: "number",
43122
+ className: "form-input",
43123
+ min: slidingScale.minAmount / 100,
43124
+ max: slidingScale.maxAmount / 100,
43125
+ step: "0.01",
43126
+ value: booking.payment.selectedSlidingAmountInput,
43127
+ onChange: (event) => booking.actions.setSlidingAmountInput(event.target.value)
43128
+ }
43129
+ )
43130
+ ] }) : null,
42907
43131
  /* @__PURE__ */ jsxs59("div", { className: "ab-stack", role: "list", "aria-label": "Payment options", children: [
42908
43132
  packageOptions.map((appointmentPackage) => {
42909
43133
  const isSelected = booking.payment.selectedAppointmentPackageId === appointmentPackage.customerPassId;
@@ -44750,6 +44974,7 @@ function useEligibility(params) {
44750
44974
  occurrenceId: params.occurrenceId,
44751
44975
  attendanceMode: params.attendanceMode ?? void 0,
44752
44976
  ticketTypeId: params.ticketTypeId ?? void 0,
44977
+ selectedSlidingAmount: params.selectedSlidingAmount ?? void 0,
44753
44978
  email: params.email.trim().toLowerCase(),
44754
44979
  ticketsCount: params.ticketsCount
44755
44980
  });
@@ -44770,7 +44995,7 @@ function useEligibility(params) {
44770
44995
  return () => {
44771
44996
  cancelled = true;
44772
44997
  };
44773
- }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.email, params.ticketsCount]);
44998
+ }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.selectedSlidingAmount, params.email, params.ticketsCount]);
44774
44999
  return {
44775
45000
  state,
44776
45001
  eligibility: state.eligibility,
@@ -45121,6 +45346,41 @@ function TicketCounterLimit(props2) {
45121
45346
  ] }) : null
45122
45347
  ] });
45123
45348
  }
45349
+ function formatCurrencyInputAmount(amount) {
45350
+ const major = amount / 100;
45351
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
45352
+ }
45353
+ function SlidingScaleAmountField(props2) {
45354
+ const minLabel = formatPrice(props2.slidingScale.minAmount, props2.currency);
45355
+ const maxLabel = formatPrice(props2.slidingScale.maxAmount, props2.currency);
45356
+ const suggestedLabel = props2.slidingScale.suggestedAmount !== null ? formatPrice(props2.slidingScale.suggestedAmount, props2.currency) : null;
45357
+ return /* @__PURE__ */ jsxs66("div", { className: "er-field", children: [
45358
+ /* @__PURE__ */ jsx93("label", { htmlFor: "er-sliding-amount", className: "er-label", children: "Amount" }),
45359
+ /* @__PURE__ */ jsx93(
45360
+ "input",
45361
+ {
45362
+ id: "er-sliding-amount",
45363
+ type: "number",
45364
+ min: formatCurrencyInputAmount(props2.slidingScale.minAmount),
45365
+ max: formatCurrencyInputAmount(props2.slidingScale.maxAmount),
45366
+ step: "0.01",
45367
+ inputMode: "decimal",
45368
+ className: "er-input",
45369
+ value: props2.value,
45370
+ onChange: (event) => props2.onChange?.(event.target.value)
45371
+ }
45372
+ ),
45373
+ /* @__PURE__ */ jsxs66("p", { className: "er-text er-text--muted", children: [
45374
+ "Choose between ",
45375
+ minLabel,
45376
+ " and ",
45377
+ maxLabel,
45378
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
45379
+ "."
45380
+ ] }),
45381
+ props2.message ? /* @__PURE__ */ jsx93("p", { className: "er-text er-text--muted", children: props2.message }) : null
45382
+ ] });
45383
+ }
45124
45384
  function getTicketTypeCounterAvailabilityLabel(input) {
45125
45385
  if (input.selectedRemainingCapacity !== null) {
45126
45386
  return `${input.selectedRemainingCapacity} remaining for this ticket type`;
@@ -45192,6 +45452,10 @@ var init_TicketSelectionStep = __esm({
45192
45452
  maxTickets,
45193
45453
  selectedTicketTypeId = null,
45194
45454
  onTicketTypeChange,
45455
+ slidingScale = null,
45456
+ selectedSlidingAmountInput = "",
45457
+ slidingScaleSelectionMessage = null,
45458
+ onSlidingAmountChange,
45195
45459
  onTicketsChange
45196
45460
  }) => {
45197
45461
  const deliveryMode = event?.deliveryMode ?? "in_person";
@@ -45307,6 +45571,16 @@ var init_TicketSelectionStep = __esm({
45307
45571
  ] })
45308
45572
  ] })
45309
45573
  ] }) : /* @__PURE__ */ jsxs66(Fragment18, { children: [
45574
+ slidingScale ? /* @__PURE__ */ jsx93(
45575
+ SlidingScaleAmountField,
45576
+ {
45577
+ slidingScale,
45578
+ currency: eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2,
45579
+ value: selectedSlidingAmountInput,
45580
+ message: slidingScaleSelectionMessage,
45581
+ onChange: onSlidingAmountChange
45582
+ }
45583
+ ) : null,
45310
45584
  /* @__PURE__ */ jsx93("h3", { className: "er-title", children: canJoinWaitlist ? "How many waitlist spots?" : "How many tickets?" }),
45311
45585
  /* @__PURE__ */ jsxs66("div", { className: "er-ticket-counter-group", children: [
45312
45586
  /* @__PURE__ */ jsx93(
@@ -46321,6 +46595,10 @@ function EventRegistrationWizardContent(props2) {
46321
46595
  maxTickets: wizard.maxTicketsNum,
46322
46596
  selectedTicketTypeId: wizard.selectedTicketTypeId,
46323
46597
  onTicketTypeChange: wizard.setSelectedTicketTypeId,
46598
+ slidingScale: wizard.selectedEventSlidingScale,
46599
+ selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
46600
+ slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
46601
+ onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
46324
46602
  onTicketsChange: wizard.setTicketsCount
46325
46603
  }
46326
46604
  )
@@ -46537,7 +46815,7 @@ function EventRegistrationSummaryBar(props2) {
46537
46815
  const summaryTimeZone = wizard.occurrenceContext?.timeZone ?? summaryEvent?.timeZone ?? null;
46538
46816
  const occurrenceOverrideTitle = wizard.occurrenceContext?.overrides?.title;
46539
46817
  const summaryTitle = occurrenceOverrideTitle ?? summaryEvent?.title ?? "";
46540
- const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
46818
+ const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (wizard.selectedEventSlidingScale ? wizard.selectedSlidingAmount : null) ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
46541
46819
  const summaryCurrency = (summaryEvent ? publicEventPricingCurrency(summaryEvent.pricing) : null) ?? DEFAULT_EVENT_CURRENCY2;
46542
46820
  const teamMemberNames = (summaryEvent?.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
46543
46821
  const subtotalCents = Math.max(0, summaryPrice * wizard.ticketsCount);
@@ -46988,6 +47266,7 @@ function useEventRegistrationSubmit(options) {
46988
47266
  occurrenceId: submitOccurrenceId,
46989
47267
  attendanceMode: options.selectedAttendanceMode ?? void 0,
46990
47268
  ticketTypeId: options.selectedTicketTypeId ?? void 0,
47269
+ selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
46991
47270
  email: options.formData.email.trim().toLowerCase(),
46992
47271
  name: options.formData.name.trim(),
46993
47272
  ticketsCount: options.ticketsCount,
@@ -47097,6 +47376,35 @@ var init_useEventRegistrationSubmit = __esm({
47097
47376
 
47098
47377
  // ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.ts
47099
47378
  import { useCallback as useCallback8, useEffect as useEffect17, useMemo as useMemo12, useReducer as useReducer10, useState as useState9 } from "react";
47379
+ function parseCurrencyInputToCents(value) {
47380
+ const trimmed = value.trim();
47381
+ if (!trimmed) return null;
47382
+ const amount = Number(trimmed);
47383
+ if (!Number.isFinite(amount) || amount < 0) return null;
47384
+ return Math.round(amount * 100);
47385
+ }
47386
+ function formatCentsAsCurrencyInput(amount) {
47387
+ const major = amount / 100;
47388
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
47389
+ }
47390
+ function getSlidingScaleForEvent(event) {
47391
+ const pricing = event ? resolveRuntimeEventPricing(event) : null;
47392
+ return pricing?.kind === "sliding_scale" ? {
47393
+ minAmount: pricing.minAmount,
47394
+ maxAmount: pricing.maxAmount,
47395
+ suggestedAmount: pricing.suggestedAmount
47396
+ } : null;
47397
+ }
47398
+ function getSlidingScaleSelectionMessage(input) {
47399
+ if (!input.slidingScale) return null;
47400
+ if (input.selectedAmount === null) {
47401
+ return "Choose an amount to continue.";
47402
+ }
47403
+ if (input.selectedAmount < input.slidingScale.minAmount || input.selectedAmount > input.slidingScale.maxAmount) {
47404
+ return "Choose an amount within the sliding scale.";
47405
+ }
47406
+ return null;
47407
+ }
47100
47408
  function useEventRegistrationWizard(options) {
47101
47409
  const api = useBlockApi();
47102
47410
  const registrationEvents = useMemo12(
@@ -47141,6 +47449,10 @@ function useEventRegistrationWizard(options) {
47141
47449
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
47142
47450
  );
47143
47451
  const [selectedTicketTypeId, setSelectedTicketTypeId] = useState9(null);
47452
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState9(() => {
47453
+ const slidingScale = getSlidingScaleForEvent(preSelectedEvent ?? null);
47454
+ return slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : "";
47455
+ });
47144
47456
  const [registrationResult, setRegistrationResult] = useState9(null);
47145
47457
  const [errorMessage, setErrorMessage] = useState9("");
47146
47458
  const [altchaSolution, setAltchaSolution] = useState9(null);
@@ -47177,6 +47489,7 @@ function useEventRegistrationWizard(options) {
47177
47489
  occurrenceId,
47178
47490
  attendanceMode: selectedAttendanceMode,
47179
47491
  ticketTypeId: selectedTicketTypeId,
47492
+ selectedSlidingAmount: parseCurrencyInputToCents(selectedSlidingAmountInput),
47180
47493
  email: formData.email,
47181
47494
  ticketsCount
47182
47495
  });
@@ -47312,6 +47625,18 @@ function useEventRegistrationWizard(options) {
47312
47625
  }),
47313
47626
  [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility, selectedTicketTypeId]
47314
47627
  );
47628
+ const selectedSlidingAmount = useMemo12(
47629
+ () => parseCurrencyInputToCents(selectedSlidingAmountInput),
47630
+ [selectedSlidingAmountInput]
47631
+ );
47632
+ const selectedEventSlidingScale = useMemo12(
47633
+ () => getSlidingScaleForEvent(selectedEvent ?? preSelectedEvent),
47634
+ [selectedEvent, preSelectedEvent]
47635
+ );
47636
+ const slidingScaleSelectionMessage = getSlidingScaleSelectionMessage({
47637
+ slidingScale: selectedEventSlidingScale,
47638
+ selectedAmount: selectedSlidingAmount
47639
+ });
47315
47640
  const ticketTypeSelectionRequired = useMemo12(() => {
47316
47641
  const event = selectedEvent ?? preSelectedEvent;
47317
47642
  if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
@@ -47380,12 +47705,24 @@ function useEventRegistrationWizard(options) {
47380
47705
  setGuestSectionShake(false);
47381
47706
  };
47382
47707
  const eventRequiresPayment = useMemo12(
47383
- () => resolveEventRequiresPayment({
47708
+ () => {
47709
+ if (selectedEventSlidingScale) {
47710
+ return (selectedSlidingAmount ?? 0) > 0;
47711
+ }
47712
+ return resolveEventRequiresPayment({
47713
+ eligibility,
47714
+ selectedTicketType,
47715
+ event: selectedEvent ?? preSelectedEvent
47716
+ });
47717
+ },
47718
+ [
47384
47719
  eligibility,
47385
47720
  selectedTicketType,
47386
- event: selectedEvent ?? preSelectedEvent
47387
- }),
47388
- [eligibility, selectedTicketType, selectedEvent, preSelectedEvent]
47721
+ selectedEvent,
47722
+ preSelectedEvent,
47723
+ selectedEventSlidingScale,
47724
+ selectedSlidingAmount
47725
+ ]
47389
47726
  );
47390
47727
  const handleDetailsComplete = async () => {
47391
47728
  if (ticketTypeSelectionRequired && !hasValidSelectedTicketType) {
@@ -47394,6 +47731,11 @@ function useEventRegistrationWizard(options) {
47394
47731
  setStep("error");
47395
47732
  return { ok: false, reason: "blocked", message };
47396
47733
  }
47734
+ if (slidingScaleSelectionMessage) {
47735
+ setErrorMessage(slidingScaleSelectionMessage);
47736
+ setStep("error");
47737
+ return { ok: false, reason: "blocked", message: slidingScaleSelectionMessage };
47738
+ }
47397
47739
  if (!validateForm()) {
47398
47740
  setGuestSectionShake(true);
47399
47741
  return {
@@ -47478,9 +47820,18 @@ function useEventRegistrationWizard(options) {
47478
47820
  const discountSubtotalCents = useMemo12(() => {
47479
47821
  if (discountContext?.type !== "event") return void 0;
47480
47822
  const event = selectedEvent ?? preSelectedEvent;
47481
- const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
47823
+ const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (selectedEventSlidingScale ? selectedSlidingAmount : null) ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
47482
47824
  return Math.max(0, eventPriceCents * ticketsCount);
47483
- }, [discountContext, selectedTicketType, eligibility, selectedEvent, preSelectedEvent, ticketsCount]);
47825
+ }, [
47826
+ discountContext,
47827
+ selectedTicketType,
47828
+ eligibility,
47829
+ selectedEvent,
47830
+ preSelectedEvent,
47831
+ selectedEventSlidingScale,
47832
+ selectedSlidingAmount,
47833
+ ticketsCount
47834
+ ]);
47484
47835
  const discountUi = useDiscountCodeUi({ api, discountContext, subtotalCents: discountSubtotalCents });
47485
47836
  const selectionMethod = paymentSelection.method;
47486
47837
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
@@ -47489,6 +47840,7 @@ function useEventRegistrationWizard(options) {
47489
47840
  registrationTarget,
47490
47841
  selectedAttendanceMode,
47491
47842
  selectedTicketTypeId,
47843
+ selectedSlidingAmount,
47492
47844
  ticketsCount,
47493
47845
  formData,
47494
47846
  altchaSolution,
@@ -47503,6 +47855,10 @@ function useEventRegistrationWizard(options) {
47503
47855
  });
47504
47856
  const handleEventSelect = (event) => {
47505
47857
  setSelectedEvent(event);
47858
+ const slidingScale = getSlidingScaleForEvent(event);
47859
+ setSelectedSlidingAmountInput(
47860
+ slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
47861
+ );
47506
47862
  setSelectedAttendanceMode(
47507
47863
  (event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person"
47508
47864
  );
@@ -47512,6 +47868,7 @@ function useEventRegistrationWizard(options) {
47512
47868
  const handleRetry = () => {
47513
47869
  dispatchFlow({ type: "reset", hasPreSelectedContext });
47514
47870
  setSelectedTicketTypeId(null);
47871
+ setSelectedSlidingAmountInput("");
47515
47872
  setSelectedAttendanceMode(
47516
47873
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
47517
47874
  );
@@ -47548,6 +47905,11 @@ function useEventRegistrationWizard(options) {
47548
47905
  setSelectedAttendanceMode,
47549
47906
  selectedTicketTypeId,
47550
47907
  setSelectedTicketTypeId,
47908
+ selectedSlidingAmountInput,
47909
+ setSelectedSlidingAmountInput,
47910
+ selectedSlidingAmount,
47911
+ selectedEventSlidingScale,
47912
+ slidingScaleSelectionMessage,
47551
47913
  selectedTicketType,
47552
47914
  visibleTicketTypes,
47553
47915
  ticketTypeSelectionRequired,
@@ -51866,6 +52228,44 @@ function CoursePricingSelector({
51866
52228
  )
51867
52229
  ] });
51868
52230
  }
52231
+ function CourseSlidingScaleInput({
52232
+ pricing,
52233
+ value,
52234
+ onChange
52235
+ }) {
52236
+ const slidingScale = pricing.slidingScale;
52237
+ if (!slidingScale) {
52238
+ return null;
52239
+ }
52240
+ const minLabel = formatCoursePriceAmount(slidingScale.minAmount, pricing.currency);
52241
+ const maxLabel = formatCoursePriceAmount(slidingScale.maxAmount, pricing.currency);
52242
+ const suggestedLabel = slidingScale.suggestedAmount !== null ? formatCoursePriceAmount(slidingScale.suggestedAmount, pricing.currency) : null;
52243
+ return /* @__PURE__ */ jsxs97("div", { className: "cr-form__field", children: [
52244
+ /* @__PURE__ */ jsx140("label", { htmlFor: "cr-sliding-amount", className: "cr-form__label", children: "Amount" }),
52245
+ /* @__PURE__ */ jsx140(
52246
+ "input",
52247
+ {
52248
+ id: "cr-sliding-amount",
52249
+ type: "number",
52250
+ min: slidingScale.minAmount / 100,
52251
+ max: slidingScale.maxAmount / 100,
52252
+ step: "0.01",
52253
+ inputMode: "decimal",
52254
+ className: "cr-form__input",
52255
+ value,
52256
+ onChange: (event) => onChange(event.target.value)
52257
+ }
52258
+ ),
52259
+ /* @__PURE__ */ jsxs97("p", { className: "cr-text cr-text--muted", children: [
52260
+ "Choose between ",
52261
+ minLabel,
52262
+ " and ",
52263
+ maxLabel,
52264
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
52265
+ "."
52266
+ ] })
52267
+ ] });
52268
+ }
51869
52269
  function EnrollmentForm({ formData, errors, onChange, isEmailLocked, isLoading, onLogout }) {
51870
52270
  const nameErrorId = "cr-name-error";
51871
52271
  const emailErrorId = "cr-email-error";
@@ -51961,6 +52361,17 @@ var init_components2 = __esm({
51961
52361
 
51962
52362
  // ../blocks/src/system/runtime/nodes/course-registration/useCourseRegistrationWizard.ts
51963
52363
  import { useEffect as useEffect27, useMemo as useMemo21, useState as useState20 } from "react";
52364
+ function parseCurrencyInputToCents2(value) {
52365
+ const trimmed = value.trim();
52366
+ if (!trimmed) return null;
52367
+ const amount = Number(trimmed);
52368
+ if (!Number.isFinite(amount) || amount < 0) return null;
52369
+ return Math.round(amount * 100);
52370
+ }
52371
+ function formatCentsAsCurrencyInput2(amount) {
52372
+ const major = amount / 100;
52373
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
52374
+ }
51964
52375
  function shouldFetchCourseEligibility(course, hasPaidSelection, portalAuthState, hasWaitlistClaimToken) {
51965
52376
  if (!course || portalAuthState === "checking") return false;
51966
52377
  if (hasWaitlistClaimToken) return false;
@@ -51978,12 +52389,27 @@ function getSessionIdFromUrl() {
51978
52389
  }
51979
52390
  function getMissingCoursePricingSelectionMessage(pricing) {
51980
52391
  if (pricing?.requiresSelection && !pricing.selectedOption) {
52392
+ if (pricing.slidingScale) {
52393
+ return "Choose an amount to continue.";
52394
+ }
51981
52395
  return "Choose a pricing option to continue.";
51982
52396
  }
52397
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount < pricing.slidingScale.minAmount) {
52398
+ return "Choose an amount within the sliding scale.";
52399
+ }
52400
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount > pricing.slidingScale.maxAmount) {
52401
+ return "Choose an amount within the sliding scale.";
52402
+ }
51983
52403
  return null;
51984
52404
  }
51985
52405
  function buildCoursePricingOptionPayload(pricing) {
51986
- return pricing?.selectedOption ? { pricingOptionId: pricing.selectedOption.id } : {};
52406
+ if (pricing?.selectedOption) {
52407
+ return { pricingOptionId: pricing.selectedOption.id };
52408
+ }
52409
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null) {
52410
+ return { selectedSlidingAmount: pricing.selectedSlidingAmount };
52411
+ }
52412
+ return {};
51987
52413
  }
51988
52414
  function resolveHasCoursePayment(pricing) {
51989
52415
  if (!pricing) {
@@ -52024,6 +52450,7 @@ function useCourseRegistrationWizard(props2) {
52024
52450
  scopedCourses.length === 1 ? scopedCourses[0] ?? null : null
52025
52451
  );
52026
52452
  const [selectedPricingOptionId, setSelectedPricingOptionId] = useState20(null);
52453
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState20("");
52027
52454
  const [formData, setFormData] = useState20({
52028
52455
  name: "",
52029
52456
  email: "",
@@ -52045,8 +52472,12 @@ function useCourseRegistrationWizard(props2) {
52045
52472
  const [bookingMode, setBookingMode] = useState20(null);
52046
52473
  const waitlistClaim = useWaitlistClaim();
52047
52474
  const selectedCoursePricing = useMemo21(
52048
- () => selectedCourse ? resolveCoursePricingState(selectedCourse, selectedPricingOptionId) : null,
52049
- [selectedCourse, selectedPricingOptionId]
52475
+ () => selectedCourse ? resolveCoursePricingState(
52476
+ selectedCourse,
52477
+ selectedPricingOptionId,
52478
+ parseCurrencyInputToCents2(selectedSlidingAmountInput)
52479
+ ) : null,
52480
+ [selectedCourse, selectedPricingOptionId, selectedSlidingAmountInput]
52050
52481
  );
52051
52482
  const missingPricingSelectionMessage = getMissingCoursePricingSelectionMessage(
52052
52483
  selectedCoursePricing
@@ -52119,9 +52550,13 @@ function useCourseRegistrationWizard(props2) {
52119
52550
  useEffect27(() => {
52120
52551
  if (!selectedCourse) {
52121
52552
  setSelectedPricingOptionId(null);
52553
+ setSelectedSlidingAmountInput("");
52122
52554
  return;
52123
52555
  }
52124
52556
  const defaultPricing = resolveCoursePricingState(selectedCourse, null);
52557
+ setSelectedSlidingAmountInput(
52558
+ defaultPricing.slidingScale?.suggestedAmount !== null && defaultPricing.slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput2(defaultPricing.slidingScale.suggestedAmount) : ""
52559
+ );
52125
52560
  setSelectedPricingOptionId((previous) => {
52126
52561
  if (previous && defaultPricing.selectableOptions.some((option) => option.id === previous)) {
52127
52562
  return previous;
@@ -52496,6 +52931,8 @@ function useCourseRegistrationWizard(props2) {
52496
52931
  selectedCourse,
52497
52932
  selectedPricingOptionId,
52498
52933
  setSelectedPricingOptionId,
52934
+ selectedSlidingAmountInput,
52935
+ setSelectedSlidingAmountInput,
52499
52936
  selectedCoursePricing,
52500
52937
  setSelectedCourse,
52501
52938
  formData,
@@ -52561,6 +52998,19 @@ var init_useCourseRegistrationWizard = __esm({
52561
52998
 
52562
52999
  // ../blocks/src/system/runtime/nodes/course-registration/CourseRegistrationWizard.tsx
52563
53000
  import { Fragment as Fragment31, jsx as jsx141, jsxs as jsxs98 } from "react/jsx-runtime";
53001
+ function getCourseSlidingScaleMessage(pricing) {
53002
+ if (!pricing.slidingScale) {
53003
+ return null;
53004
+ }
53005
+ const selectedAmount = pricing.selectedSlidingAmount;
53006
+ if (selectedAmount === null) {
53007
+ return "Choose an amount to continue.";
53008
+ }
53009
+ if (selectedAmount < pricing.slidingScale.minAmount || selectedAmount > pricing.slidingScale.maxAmount) {
53010
+ return "Choose an amount within the sliding scale.";
53011
+ }
53012
+ return null;
53013
+ }
52564
53014
  function CourseRegistrationWizard(props2) {
52565
53015
  const {
52566
53016
  showSessions = true,
@@ -52582,6 +53032,8 @@ function CourseRegistrationWizard(props2) {
52582
53032
  selectedCourse,
52583
53033
  selectedPricingOptionId,
52584
53034
  setSelectedPricingOptionId,
53035
+ selectedSlidingAmountInput,
53036
+ setSelectedSlidingAmountInput,
52585
53037
  selectedCoursePricing,
52586
53038
  formData,
52587
53039
  setFormData,
@@ -52656,7 +53108,8 @@ function CourseRegistrationWizard(props2) {
52656
53108
  const canShowDiscountUi = !discountsAvailableLoading && paymentChoice === "checkout" && discountsAvailable && manualCodeEntryAllowed;
52657
53109
  const courseSoldOut = waitlistClaim.claim === null && courseEligibility?.isFull === true && courseEligibility.canJoinWaitlist !== true;
52658
53110
  const detailsSubmitLabel = courseEligibility?.canJoinWaitlist ? "Join waitlist" : buttonText;
52659
- const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption;
53111
+ const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption && !selectedCoursePricing.slidingScale;
53112
+ const slidingScaleMessage = selectedCoursePricing ? getCourseSlidingScaleMessage(selectedCoursePricing) : null;
52660
53113
  const pricingUnavailable = selectedCoursePricing?.display.kind === "unavailable";
52661
53114
  const selectedCourseWithPricing = selectedCourse && selectedCoursePricing ? {
52662
53115
  course: selectedCourse,
@@ -52749,6 +53202,14 @@ function CourseRegistrationWizard(props2) {
52749
53202
  onSelect: setSelectedPricingOptionId
52750
53203
  }
52751
53204
  ),
53205
+ /* @__PURE__ */ jsx141(
53206
+ CourseSlidingScaleInput,
53207
+ {
53208
+ pricing: selectedCourseWithPricing.pricing,
53209
+ value: selectedSlidingAmountInput,
53210
+ onChange: setSelectedSlidingAmountInput
53211
+ }
53212
+ ),
52752
53213
  showSpamProtection && /* @__PURE__ */ jsx141("div", { className: "cr-altcha", children: /* @__PURE__ */ jsx141(
52753
53214
  AltchaWidget,
52754
53215
  {
@@ -52786,7 +53247,7 @@ function CourseRegistrationWizard(props2) {
52786
53247
  {
52787
53248
  type: "submit",
52788
53249
  className: buttonClass,
52789
- disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || pricingUnavailable,
53250
+ disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || slidingScaleMessage !== null || pricingUnavailable,
52790
53251
  children: eligibilityLoading ? "Checking availability\u2026" : courseSoldOut ? "Sold out" : detailsSubmitLabel
52791
53252
  }
52792
53253
  )
@@ -52796,6 +53257,7 @@ function CourseRegistrationWizard(props2) {
52796
53257
  ),
52797
53258
  courseSoldOut && /* @__PURE__ */ jsx141("p", { className: "cr-text cr-text--muted", children: "This course is fully booked and there is no waitlist available right now." }),
52798
53259
  pricingSelectionMissing && /* @__PURE__ */ jsx141("p", { className: "cr-text cr-text--muted", children: "Choose a pricing option to continue." }),
53260
+ slidingScaleMessage && /* @__PURE__ */ jsx141("p", { className: "cr-text cr-text--muted", children: slidingScaleMessage }),
52799
53261
  pricingUnavailable && /* @__PURE__ */ jsx141("p", { className: "cr-text cr-text--muted", children: "Pricing is not available for this course right now." }),
52800
53262
  eligibilityError && /* @__PURE__ */ jsx141("p", { className: "cr-text cr-text--error", children: eligibilityError })
52801
53263
  ] })
@@ -62949,6 +63411,21 @@ function fieldPlanToZod(plan, options = {}) {
62949
63411
  }
62950
63412
  });
62951
63413
  }
63414
+ function fieldPresenceForPlan(plan) {
63415
+ return plan.required ? { kind: "required" } : { kind: "optional" };
63416
+ }
63417
+ function fieldPlanToObjectPropertyZod(plan, options = {}) {
63418
+ const schema = fieldPlanToZod(plan, options);
63419
+ const presence = fieldPresenceForPlan(plan);
63420
+ switch (presence.kind) {
63421
+ case "required":
63422
+ return schema;
63423
+ case "optional":
63424
+ return schema.optional();
63425
+ default:
63426
+ return assertNever4(presence);
63427
+ }
63428
+ }
62952
63429
  function addFieldIssueToZodContext(zodCtx, validationIssue, rootPath) {
62953
63430
  const message = fieldIssueToMessage(validationIssue);
62954
63431
  const path = relativeZodIssuePath(validationIssue.path, rootPath);
@@ -63161,7 +63638,7 @@ function createManifestValidator(manifestOrFields, options = {}) {
63161
63638
  function deriveContentZodFromPlans(plans, options) {
63162
63639
  const shape = {};
63163
63640
  for (const plan of plans) {
63164
- shape[plan.fieldId] = fieldPlanToZod(plan, options);
63641
+ shape[plan.fieldId] = fieldPlanToObjectPropertyZod(plan, options);
63165
63642
  }
63166
63643
  return z7.object(shape).catchall(z7.unknown()).passthrough();
63167
63644
  }
@@ -75254,11 +75731,11 @@ function extractRouteUpdatedAt(candidate) {
75254
75731
  }
75255
75732
  function resolveRouteTargetForStage(routeIdValue, routes, stageInput) {
75256
75733
  const routeId = typeof routeIdValue === "string" ? routeIdValue : null;
75257
- if (!routeId || !routes) return null;
75734
+ if (routeId === null || routeId.length === 0 || routes === void 0) return null;
75258
75735
  const routeEntry = routes[routeId];
75259
75736
  if (!isRouteVisibleInStage(routeEntry, stageInput)) return null;
75260
75737
  const href = extractRouteHref(routeEntry, stageInput);
75261
- if (!href) return null;
75738
+ if (href === null) return null;
75262
75739
  return {
75263
75740
  routeId,
75264
75741
  href,
@@ -97083,6 +97560,7 @@ var CANONICAL_REFUND_CONFLICT_CODES = [
97083
97560
  ];
97084
97561
  var CANONICAL_REFUND_PLANNER_ERROR_CODES = [
97085
97562
  ...CANONICAL_REFUND_CONFLICT_CODES,
97563
+ "invalid_amount",
97086
97564
  "amount_exceeds_remaining",
97087
97565
  "pass_revoke_requires_full_refund",
97088
97566
  "refund_failed"
@@ -97109,6 +97587,7 @@ var CUSTOMER_MEMBERSHIP_PAYMENT_REFUND_FAILED_CODES = [
97109
97587
  // ../api/src/bookingRefundOutcomes.ts
97110
97588
  var STANDARD_LINE_ITEM_REFUND_INVALID_CODES = [
97111
97589
  "missing_amount",
97590
+ "invalid_amount",
97112
97591
  "amount_exceeds_remaining",
97113
97592
  "credit_refunds_disabled",
97114
97593
  "missing_credit_customer",
@@ -97123,6 +97602,7 @@ var EVENT_ATTENDEE_REFUND_INVALID_CODES = [
97123
97602
  var MANAGED_PRODUCT_REFUND_INVALID_CODES = [
97124
97603
  "credit_refunds_disabled",
97125
97604
  "not_refundable",
97605
+ "invalid_amount",
97126
97606
  "amount_exceeds_remaining"
97127
97607
  ];
97128
97608
  var CUSTOMER_PASS_REFUND_INVALID_CODES = [
@@ -99095,7 +99575,7 @@ var SimpleCache = class {
99095
99575
  };
99096
99576
 
99097
99577
  // src/version.ts
99098
- var SDK_VERSION = "0.90.1";
99578
+ var SDK_VERSION = "0.92.0";
99099
99579
 
99100
99580
  // src/client/error.ts
99101
99581
  var RiverbankApiError = class _RiverbankApiError extends Error {