@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
@@ -826,6 +826,7 @@ function createBlockApi(config) {
826
826
  body: {
827
827
  courseId: data.courseId,
828
828
  ...data.pricingOptionId ? { pricingOptionId: data.pricingOptionId } : {},
829
+ ...data.selectedSlidingAmount !== void 0 ? { selectedSlidingAmount: data.selectedSlidingAmount } : {},
829
830
  email: data.email,
830
831
  name: data.name,
831
832
  phone: data.phone,
@@ -2986,12 +2987,13 @@ var init_result = __esm({
2986
2987
  });
2987
2988
 
2988
2989
  // ../core/src/branded.ts
2989
- var unsafeAsMediaAssetId, unsafeAsStorageBucketName, unsafeAsPositiveByteCount, unsafeAsPositiveSeconds, unsafeAsFormId, unsafeAsPassId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, unsafeAsAppointmentResourceId;
2990
+ var unsafeAsMediaAssetId, unsafeAsStorageBucketName, unsafeAsStoragePath, unsafeAsPositiveByteCount, unsafeAsPositiveSeconds, unsafeAsFormId, unsafeAsPassId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, unsafeAsAppointmentResourceId;
2990
2991
  var init_branded = __esm({
2991
2992
  "../core/src/branded.ts"() {
2992
2993
  "use strict";
2993
2994
  unsafeAsMediaAssetId = (id) => id;
2994
2995
  unsafeAsStorageBucketName = (value) => value;
2996
+ unsafeAsStoragePath = (value) => value;
2995
2997
  unsafeAsPositiveByteCount = (value) => value;
2996
2998
  unsafeAsPositiveSeconds = (value) => value;
2997
2999
  unsafeAsFormId = (id) => id;
@@ -3137,6 +3139,7 @@ function publicEventPricingType(pricing) {
3137
3139
  return "free";
3138
3140
  case "single_paid":
3139
3141
  case "ticket_types":
3142
+ case "sliding_scale":
3140
3143
  case "course":
3141
3144
  return isFreePublicEventPriceDisplay(pricing.display) ? "free" : "paid";
3142
3145
  case "external":
@@ -3149,6 +3152,7 @@ function publicEventPricingCurrency(pricing) {
3149
3152
  case "free_registration":
3150
3153
  case "single_paid":
3151
3154
  case "ticket_types":
3155
+ case "sliding_scale":
3152
3156
  return pricing.currency;
3153
3157
  case "course":
3154
3158
  return pricing.display.display.currency;
@@ -3166,6 +3170,8 @@ function publicEventPricingMode(pricing) {
3166
3170
  return "single";
3167
3171
  case "ticket_types":
3168
3172
  return "ticket_types";
3173
+ case "sliding_scale":
3174
+ return "sliding_scale";
3169
3175
  case "drop_in":
3170
3176
  case "course":
3171
3177
  case "external":
@@ -3199,6 +3205,8 @@ function formatCoursePriceDisplayLabel(display, options) {
3199
3205
  return `Course (${formatCurrencyAmount(display.amount, display.currency, options)})`;
3200
3206
  case "from":
3201
3207
  return `Course (From ${formatCurrencyAmount(display.amount, display.currency, options)})`;
3208
+ case "range":
3209
+ return `Course (${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)})`;
3202
3210
  default:
3203
3211
  return assertNever2(display);
3204
3212
  }
@@ -3342,10 +3350,11 @@ function omitZeroAmountFutureRows(rows) {
3342
3350
  if (rows.length <= 1) {
3343
3351
  return rows;
3344
3352
  }
3345
- const [firstRow, ...futureRows] = rows;
3346
- if (!firstRow) {
3353
+ const firstRow = rows[0];
3354
+ if (firstRow === void 0) {
3347
3355
  return [];
3348
3356
  }
3357
+ const futureRows = rows.slice(1);
3349
3358
  return [
3350
3359
  firstRow,
3351
3360
  ...futureRows.filter((row) => row.amountCents > 0)
@@ -3390,11 +3399,11 @@ function isValidTimeZone(timeZone) {
3390
3399
  }
3391
3400
  function getSafeTimeZone(timeZone) {
3392
3401
  const normalized = typeof timeZone === "string" ? timeZone.trim() : "";
3393
- return normalized && isValidTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
3402
+ return normalized.length > 0 && isValidTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
3394
3403
  }
3395
3404
  function parseStrictBookingTimeZone(timeZone) {
3396
3405
  const normalized = timeZone.trim();
3397
- if (!normalized || !isValidTimeZone(normalized)) {
3406
+ if (normalized.length === 0 || !isValidTimeZone(normalized)) {
3398
3407
  return null;
3399
3408
  }
3400
3409
  return normalized;
@@ -3480,7 +3489,7 @@ function parseScheduledBookingDateTime(input) {
3480
3489
  try {
3481
3490
  const startsAt = toUtcIsoDateTimeString(input.startsAt);
3482
3491
  const timeZone = parseStrictBookingTimeZone(input.timeZone);
3483
- if (!timeZone) {
3492
+ if (timeZone === null) {
3484
3493
  return {
3485
3494
  ok: false,
3486
3495
  field: "timeZone",
@@ -3619,7 +3628,7 @@ function getBookingLocalDateKey(input) {
3619
3628
  }
3620
3629
  function parseBookingLocalDate(value) {
3621
3630
  const match = LOCAL_DATE_PATTERN.exec(value.trim());
3622
- if (!match?.groups) {
3631
+ if (match === null || match.groups === void 0) {
3623
3632
  throw new Error(`Invalid booking date format: "${value}" (expected YYYY-MM-DD)`);
3624
3633
  }
3625
3634
  const year = Number(match.groups.year);
@@ -3678,10 +3687,10 @@ function normalizeLocationAddressText(value) {
3678
3687
  return null;
3679
3688
  }
3680
3689
  const trimmed = value.trim();
3681
- return trimmed ? trimmed : null;
3690
+ return trimmed.length > 0 ? trimmed : null;
3682
3691
  }
3683
3692
  function hasLocationCoordinates(value) {
3684
- return typeof value?.lat === "number" && typeof value?.lng === "number";
3693
+ return value != null && typeof value.lat === "number" && typeof value.lng === "number";
3685
3694
  }
3686
3695
  function toLocationCoordinates(value) {
3687
3696
  if (!hasLocationCoordinates(value)) {
@@ -3698,6 +3707,13 @@ var init_location = __esm({
3698
3707
  }
3699
3708
  });
3700
3709
 
3710
+ // ../core/src/string.ts
3711
+ var init_string = __esm({
3712
+ "../core/src/string.ts"() {
3713
+ "use strict";
3714
+ }
3715
+ });
3716
+
3701
3717
  // ../core/src/business-address.ts
3702
3718
  var init_business_address = __esm({
3703
3719
  "../core/src/business-address.ts"() {
@@ -3838,13 +3854,6 @@ var init_slug = __esm({
3838
3854
  }
3839
3855
  });
3840
3856
 
3841
- // ../core/src/string.ts
3842
- var init_string = __esm({
3843
- "../core/src/string.ts"() {
3844
- "use strict";
3845
- }
3846
- });
3847
-
3848
3857
  // ../core/src/uuid.ts
3849
3858
  var init_uuid = __esm({
3850
3859
  "../core/src/uuid.ts"() {
@@ -3949,9 +3958,20 @@ var init_types5 = __esm({
3949
3958
  });
3950
3959
 
3951
3960
  // ../media-core/src/fileSafety.ts
3961
+ function containsUnsafePathSegmentCharacter(value) {
3962
+ return value.includes("/") || value.includes("\\") || CONTROL_CHARACTER_PATTERN.test(value);
3963
+ }
3964
+ function isReservedPathSegment(value) {
3965
+ return value === "." || value === "..";
3966
+ }
3967
+ function isUnsafePathSegment(segment) {
3968
+ return segment.trim().length === 0 || segment !== segment.trim() || isReservedPathSegment(segment) || containsUnsafePathSegmentCharacter(segment);
3969
+ }
3970
+ var CONTROL_CHARACTER_PATTERN;
3952
3971
  var init_fileSafety = __esm({
3953
3972
  "../media-core/src/fileSafety.ts"() {
3954
3973
  "use strict";
3974
+ CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
3955
3975
  }
3956
3976
  });
3957
3977
 
@@ -3991,11 +4011,21 @@ var init_fileDomain = __esm({
3991
4011
  });
3992
4012
 
3993
4013
  // ../media-core/src/fileStorage.ts
4014
+ function parseStoragePath(value) {
4015
+ return joinStoragePathSegments(value.split("/"));
4016
+ }
4017
+ function joinStoragePathSegments(segments) {
4018
+ if (segments.some(isUnsafePathSegment)) {
4019
+ return err("unsafe-path-segment");
4020
+ }
4021
+ return ok(unsafeAsStoragePath(segments.join("/")));
4022
+ }
3994
4023
  var FILE_STORAGE_BUCKETS, PUBLIC_SITE_FILE_STORAGE_BUCKETS, PRIVATE_SITE_FILE_BUCKET_MIME_TYPES, TEMP_UPLOAD_BUCKET_MIME_TYPES;
3995
4024
  var init_fileStorage = __esm({
3996
4025
  "../media-core/src/fileStorage.ts"() {
3997
4026
  "use strict";
3998
4027
  init_src();
4028
+ init_fileSafety();
3999
4029
  FILE_STORAGE_BUCKETS = {
4000
4030
  publicSite: unsafeAsStorageBucketName("media"),
4001
4031
  privateSite: unsafeAsStorageBucketName("site-files-private"),
@@ -4181,14 +4211,18 @@ var init_uploadVerificationContract = __esm({
4181
4211
  });
4182
4212
 
4183
4213
  // ../media-core/src/videoPrep.ts
4214
+ function unsafeAsPositiveVideoDurationSeconds(value) {
4215
+ return value;
4216
+ }
4184
4217
  var VIDEO_PREP_LIMITS;
4185
4218
  var init_videoPrep = __esm({
4186
4219
  "../media-core/src/videoPrep.ts"() {
4187
4220
  "use strict";
4221
+ init_src();
4188
4222
  VIDEO_PREP_LIMITS = {
4189
- maxRawByteCount: 500 * 1024 * 1024,
4190
- inlineContentMaxDurationSeconds: 60,
4191
- backgroundVideoMaxDurationSeconds: 30,
4223
+ maxRawByteCount: unsafeAsPositiveByteCount(500 * 1024 * 1024),
4224
+ inlineContentMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(60),
4225
+ backgroundVideoMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(30),
4192
4226
  targetMaxDimensionPixels: 1080
4193
4227
  };
4194
4228
  }
@@ -4259,10 +4293,10 @@ var init_identifiers = __esm({
4259
4293
 
4260
4294
  // ../media-core/src/url.ts
4261
4295
  function isRelativeDirectImageStoragePath(storagePath) {
4262
- return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix));
4296
+ return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix)) && parseStoragePath(storagePath).ok;
4263
4297
  }
4264
4298
  function isSiteScopedDirectImageStoragePath(storagePath) {
4265
- return storagePath.startsWith("sites/");
4299
+ return storagePath.startsWith("sites/") && parseStoragePath(storagePath).ok;
4266
4300
  }
4267
4301
  function encodeStoragePath(storagePath) {
4268
4302
  return storagePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
@@ -4343,6 +4377,7 @@ var DEFAULT_BASE_PATH, RELATIVE_STORAGE_PATH_PREFIXES, formatNumber, ORDER, getI
4343
4377
  var init_url = __esm({
4344
4378
  "../media-core/src/url.ts"() {
4345
4379
  "use strict";
4380
+ init_fileStorage();
4346
4381
  DEFAULT_BASE_PATH = "/api/img";
4347
4382
  RELATIVE_STORAGE_PATH_PREFIXES = [
4348
4383
  "originals/",
@@ -7097,6 +7132,7 @@ var init_booking_form_reserved_fields = __esm({
7097
7132
  "selectedDate",
7098
7133
  "selectedSlot",
7099
7134
  "discountCode",
7135
+ "selectedSlidingAmountInput",
7100
7136
  "selectedCollectionOptionKind",
7101
7137
  "selectedAppointmentPackageId"
7102
7138
  ];
@@ -7382,6 +7418,32 @@ function readOptionalString(value) {
7382
7418
  function readOptionalNumber(value) {
7383
7419
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
7384
7420
  }
7421
+ function getBookingServiceSlidingScale(service) {
7422
+ const pricing = service?.pricing;
7423
+ if (!pricing || pricing.pricingStrategy !== "sliding_scale" || pricing.slidingMinAmount === null || pricing.slidingMinAmount === void 0 || pricing.slidingMaxAmount === null || pricing.slidingMaxAmount === void 0) {
7424
+ return null;
7425
+ }
7426
+ return {
7427
+ minAmount: pricing.slidingMinAmount,
7428
+ maxAmount: pricing.slidingMaxAmount,
7429
+ suggestedAmount: pricing.slidingSuggestedAmount ?? null,
7430
+ currency: pricing.currency
7431
+ };
7432
+ }
7433
+ function parseBookingCurrencyInputToCents(value) {
7434
+ const trimmed = value?.trim() ?? "";
7435
+ if (!trimmed) return null;
7436
+ if (!/^(?:\d+(?:\.\d{0,2})?|\.\d{1,2})$/.test(trimmed)) return null;
7437
+ const parsed = Number(trimmed);
7438
+ if (!Number.isFinite(parsed) || parsed < 0) return null;
7439
+ return Math.round(parsed * 100);
7440
+ }
7441
+ function formatBookingCentsAsCurrencyInput(amount) {
7442
+ return amount % 100 === 0 ? String(amount / 100) : (amount / 100).toFixed(2);
7443
+ }
7444
+ function isBookingServiceSlidingAmountInRange(slidingScale, selectedAmount) {
7445
+ return selectedAmount !== null && selectedAmount >= slidingScale.minAmount && selectedAmount <= slidingScale.maxAmount;
7446
+ }
7385
7447
  function readStringArray(value) {
7386
7448
  if (!Array.isArray(value)) return void 0;
7387
7449
  const strings = value.filter((item) => typeof item === "string");
@@ -7570,6 +7632,20 @@ function prepareBookingSubmission(input) {
7570
7632
  };
7571
7633
  }
7572
7634
  const paymentSelectionMode = getBookingServicePaymentSelectionMode(selectedService);
7635
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
7636
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
7637
+ input.data.selectedSlidingAmountInput
7638
+ );
7639
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(slidingScale, selectedSlidingAmount)) {
7640
+ return {
7641
+ ok: false,
7642
+ error: {
7643
+ tag: selectedSlidingAmount === null ? "sliding-amount-required" : "sliding-amount-out-of-range",
7644
+ serviceId,
7645
+ serviceTitle: selectedService.title || serviceId
7646
+ }
7647
+ };
7648
+ }
7573
7649
  if (paymentSelectionMode === "choice_required" && !toSelectedCollectionOptionKind(input.data.selectedCollectionOptionKind)) {
7574
7650
  return {
7575
7651
  ok: false,
@@ -7597,6 +7673,7 @@ function prepareBookingSubmission(input) {
7597
7673
  ...common,
7598
7674
  kind: "standard",
7599
7675
  ...discountCode ? { discountCode } : {},
7676
+ ...slidingScale && selectedSlidingAmount !== null ? { selectedSlidingAmount } : {},
7600
7677
  ...selectedCollectionOptionKind ? { selectedCollectionOptionKind } : {},
7601
7678
  altchaSolution: input.altchaSolution ?? null
7602
7679
  }
@@ -7610,6 +7687,10 @@ function formatBookingSubmissionPreparationError(error) {
7610
7687
  return `Service not found: ${error.serviceId}`;
7611
7688
  case "payment-option-required":
7612
7689
  return `Please choose how you want to pay for "${error.serviceTitle}".`;
7690
+ case "sliding-amount-required":
7691
+ return `Please choose an amount for "${error.serviceTitle}".`;
7692
+ case "sliding-amount-out-of-range":
7693
+ return `Please choose an amount within the sliding scale for "${error.serviceTitle}".`;
7613
7694
  case "payment-option-unsupported":
7614
7695
  return `Service "${error.serviceTitle}" has a payment option that is not supported yet.`;
7615
7696
  case "service-missing-duration":
@@ -7830,6 +7911,7 @@ var init_booking_form_interactive = __esm({
7830
7911
  title: z10.string(),
7831
7912
  description: z10.string().nullable().optional().transform((value) => value ?? void 0),
7832
7913
  durationMinutes: z10.number().optional(),
7914
+ pricing: z10.custom().optional(),
7833
7915
  priceAmount: z10.number().nullable().optional(),
7834
7916
  priceCurrency: z10.string().nullable().optional(),
7835
7917
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
@@ -8116,9 +8198,12 @@ var init_schemas = __esm({
8116
8198
  });
8117
8199
  coursePricingSchema = z11.object({
8118
8200
  bookingRequirement: z11.enum(["none", "required"]),
8119
- pricingStrategy: z11.enum(["free", "single", "tiered"]),
8201
+ pricingStrategy: z11.enum(["free", "single", "tiered", "sliding_scale"]),
8120
8202
  currency: z11.string(),
8121
8203
  singlePriceAmount: z11.number().nullable(),
8204
+ slidingMinAmount: z11.number().nullable().optional(),
8205
+ slidingMaxAmount: z11.number().nullable().optional(),
8206
+ slidingSuggestedAmount: z11.number().nullable().optional(),
8122
8207
  paymentTerms: blockBookingPaymentTermsSchema,
8123
8208
  options: z11.array(coursePricingOptionSchema)
8124
8209
  });
@@ -8180,7 +8265,9 @@ function getSelectableCoursePricingOptionsForNow(course, now) {
8180
8265
  return left.name.localeCompare(right.name);
8181
8266
  });
8182
8267
  }
8183
- function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {}) {
8268
+ function resolveCoursePricingState(course, selectedPricingOptionId, selectedSlidingAmountOrNowInput, nowInput = {}) {
8269
+ const selectedSlidingAmount = typeof selectedSlidingAmountOrNowInput === "number" || selectedSlidingAmountOrNowInput === null ? selectedSlidingAmountOrNowInput : null;
8270
+ const effectiveNowInput = typeof selectedSlidingAmountOrNowInput === "object" && selectedSlidingAmountOrNowInput !== null ? selectedSlidingAmountOrNowInput : nowInput;
8184
8271
  const currency = course.pricing?.currency ?? course.currency;
8185
8272
  if (!course.pricing) {
8186
8273
  const legacyAmount = course.priceCents ?? 0;
@@ -8190,6 +8277,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
8190
8277
  selectableOptions: [],
8191
8278
  requiresSelection: false,
8192
8279
  selectedOption: null,
8280
+ slidingScale: null,
8281
+ selectedSlidingAmount: null,
8193
8282
  selectedPriceAmount: legacyAmount,
8194
8283
  selectedRequiresPayment: legacyAmount > 0,
8195
8284
  hasPaidSelection: legacyAmount > 0,
@@ -8204,6 +8293,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
8204
8293
  selectableOptions: [],
8205
8294
  requiresSelection: false,
8206
8295
  selectedOption: null,
8296
+ slidingScale: null,
8297
+ selectedSlidingAmount: null,
8207
8298
  selectedPriceAmount: 0,
8208
8299
  selectedRequiresPayment: false,
8209
8300
  hasPaidSelection: false,
@@ -8217,6 +8308,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
8217
8308
  selectableOptions: [],
8218
8309
  requiresSelection: false,
8219
8310
  selectedOption: null,
8311
+ slidingScale: null,
8312
+ selectedSlidingAmount: null,
8220
8313
  selectedPriceAmount: amount,
8221
8314
  selectedRequiresPayment: amount > 0,
8222
8315
  hasPaidSelection: amount > 0,
@@ -8226,7 +8319,7 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
8226
8319
  case "tiered": {
8227
8320
  const selectableOptions = getSelectableCoursePricingOptionsForNow(
8228
8321
  course,
8229
- resolvePlanningDateInput(nowInput)
8322
+ resolvePlanningDateInput(effectiveNowInput)
8230
8323
  );
8231
8324
  const selectedOption = (selectedPricingOptionId ? selectableOptions.find((option) => option.id === selectedPricingOptionId) : null) ?? (selectableOptions.length === 1 ? selectableOptions[0] ?? null : null);
8232
8325
  const lowestAmount = selectableOptions.reduce((lowest, option) => {
@@ -8242,12 +8335,51 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
8242
8335
  selectableOptions,
8243
8336
  requiresSelection: selectableOptions.length > 1,
8244
8337
  selectedOption,
8338
+ slidingScale: null,
8339
+ selectedSlidingAmount: null,
8245
8340
  selectedPriceAmount: selectedOption?.priceAmount ?? null,
8246
8341
  selectedRequiresPayment: (selectedOption?.priceAmount ?? 0) > 0,
8247
8342
  hasPaidSelection: selectableOptions.some((option) => option.priceAmount > 0),
8248
8343
  hasFreeSelection: selectableOptions.some((option) => option.priceAmount === 0)
8249
8344
  };
8250
8345
  }
8346
+ case "sliding_scale": {
8347
+ const minAmount = course.pricing.slidingMinAmount ?? null;
8348
+ const maxAmount = course.pricing.slidingMaxAmount ?? null;
8349
+ if (minAmount === null || maxAmount === null || minAmount < 0 || maxAmount < minAmount) {
8350
+ return {
8351
+ currency,
8352
+ display: { kind: "unavailable", currency },
8353
+ selectableOptions: [],
8354
+ requiresSelection: true,
8355
+ selectedOption: null,
8356
+ slidingScale: null,
8357
+ selectedSlidingAmount: null,
8358
+ selectedPriceAmount: null,
8359
+ selectedRequiresPayment: false,
8360
+ hasPaidSelection: false,
8361
+ hasFreeSelection: false
8362
+ };
8363
+ }
8364
+ const normalizedSelectedAmount = typeof selectedSlidingAmount === "number" && Number.isFinite(selectedSlidingAmount) ? selectedSlidingAmount : null;
8365
+ return {
8366
+ currency,
8367
+ display: { kind: "range", minAmount, maxAmount, currency },
8368
+ selectableOptions: [],
8369
+ requiresSelection: normalizedSelectedAmount === null,
8370
+ selectedOption: null,
8371
+ slidingScale: {
8372
+ minAmount,
8373
+ maxAmount,
8374
+ suggestedAmount: course.pricing.slidingSuggestedAmount ?? null
8375
+ },
8376
+ selectedSlidingAmount: normalizedSelectedAmount,
8377
+ selectedPriceAmount: normalizedSelectedAmount,
8378
+ selectedRequiresPayment: (normalizedSelectedAmount ?? 0) > 0,
8379
+ hasPaidSelection: maxAmount > 0,
8380
+ hasFreeSelection: minAmount === 0
8381
+ };
8382
+ }
8251
8383
  }
8252
8384
  }
8253
8385
  function formatCoursePriceDisplay(display, locale) {
@@ -8356,7 +8488,7 @@ function isRecord7(value) {
8356
8488
  }
8357
8489
  function isPublicEventPricing(value) {
8358
8490
  if (!isRecord7(value) || typeof value.kind !== "string") return false;
8359
- return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "ticket_types" || value.kind === "course" || value.kind === "external";
8491
+ 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";
8360
8492
  }
8361
8493
  function isPublicEventTicketType(value) {
8362
8494
  return isRecord7(value) && typeof value.id === "string";
@@ -8549,7 +8681,7 @@ function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
8549
8681
  if (view === "week") {
8550
8682
  const weekStart = days[0];
8551
8683
  const weekEnd = days[6];
8552
- if (!weekStart || !weekEnd) {
8684
+ if (weekStart === void 0 || weekEnd === void 0) {
8553
8685
  return `${monthNames[viewDate.getMonth()] ?? ""} ${viewDate.getFullYear()}`;
8554
8686
  }
8555
8687
  const startMonth = monthNames[weekStart.getMonth()] ?? "";
@@ -8681,7 +8813,9 @@ function getLocalDateKey(isoTimestamp, timeZone) {
8681
8813
  const year = parts.find((part) => part.type === "year")?.value;
8682
8814
  const month = parts.find((part) => part.type === "month")?.value;
8683
8815
  const day = parts.find((part) => part.type === "day")?.value;
8684
- if (!year || !month || !day) return null;
8816
+ if (year === void 0 || year.length === 0) return null;
8817
+ if (month === void 0 || month.length === 0) return null;
8818
+ if (day === void 0 || day.length === 0) return null;
8685
8819
  return `${year}-${month}-${day}`;
8686
8820
  }
8687
8821
  function isValidTimeZone2(timeZone) {
@@ -8938,7 +9072,7 @@ function enumerateDateKeys(startDateKey, endDateKey) {
8938
9072
  }
8939
9073
  function parseDateKey(dateKey) {
8940
9074
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
8941
- if (!match) {
9075
+ if (match === null) {
8942
9076
  throw new Error(`Invalid timetable date key: ${dateKey}`);
8943
9077
  }
8944
9078
  const [, year, month, day] = match;
@@ -8974,7 +9108,7 @@ function groupItemsByDate(items, accessors) {
8974
9108
  const dateKey = getLocalDateKey(startsAt, timeZone);
8975
9109
  if (dateKey == null) continue;
8976
9110
  const existing = grouped.get(dateKey);
8977
- if (existing) {
9111
+ if (existing !== void 0) {
8978
9112
  existing.push(item);
8979
9113
  } else {
8980
9114
  grouped.set(dateKey, [item]);
@@ -10385,6 +10519,19 @@ var init_schemas2 = __esm({
10385
10519
  ticketTypes: z14.array(publicEventTicketTypeSchema),
10386
10520
  display: publicEventTicketTypePriceDisplaySchema
10387
10521
  }),
10522
+ z14.object({
10523
+ kind: z14.literal("sliding_scale"),
10524
+ minAmount: z14.number(),
10525
+ maxAmount: z14.number(),
10526
+ suggestedAmount: z14.number().nullable(),
10527
+ currency: z14.string(),
10528
+ display: z14.object({
10529
+ kind: z14.literal("range"),
10530
+ minAmount: z14.number(),
10531
+ maxAmount: z14.number(),
10532
+ currency: z14.string()
10533
+ })
10534
+ }),
10388
10535
  z14.object({
10389
10536
  kind: z14.literal("course"),
10390
10537
  display: z14.object({ kind: z14.literal("course"), display: publicCoursePriceDisplaySchema })
@@ -27661,22 +27808,22 @@ function AltchaWidget({
27661
27808
  if (typeof window === "undefined") return;
27662
27809
  let widget = null;
27663
27810
  Promise.resolve().then(() => (init_altcha(), altcha_exports)).then(() => {
27664
- if (!containerRef.current) return;
27811
+ if (containerRef.current === null) return;
27665
27812
  widget = document.createElement("altcha-widget");
27666
27813
  widget.setAttribute("challengeurl", challengeUrl);
27667
- if (auto) {
27814
+ if (auto === true) {
27668
27815
  widget.setAttribute("auto", "onload");
27669
27816
  }
27670
- if (hidelogo) {
27817
+ if (hidelogo === true) {
27671
27818
  widget.setAttribute("hidelogo", "true");
27672
27819
  }
27673
27820
  widget.setAttribute("hidefooter", "true");
27674
- if (className) {
27821
+ if (className !== void 0 && className.length > 0) {
27675
27822
  widget.className = className;
27676
27823
  }
27677
27824
  widget.addEventListener("statechange", ((event) => {
27678
27825
  const { state, payload } = event.detail;
27679
- if (state === "verified" && payload) {
27826
+ if (state === "verified" && payload !== void 0 && payload.length > 0) {
27680
27827
  onSolutionChange?.(payload);
27681
27828
  } else if (state === "error" || state === "expired") {
27682
27829
  onSolutionChange?.(null);
@@ -27687,7 +27834,7 @@ function AltchaWidget({
27687
27834
  console.error("Failed to load ALTCHA widget:", err3);
27688
27835
  });
27689
27836
  return () => {
27690
- if (widget && containerRef.current) {
27837
+ if (widget !== null && containerRef.current !== null) {
27691
27838
  containerRef.current.removeChild(widget);
27692
27839
  }
27693
27840
  };
@@ -27765,15 +27912,15 @@ var init_dist2 = __esm({
27765
27912
  // ../altcha/src/index.ts
27766
27913
  function getCmsApiUrl() {
27767
27914
  const env = process.env.RIVERBANK_ENV;
27768
- if (env) {
27915
+ if (env !== void 0 && env.length > 0) {
27769
27916
  const envKey = `RIVERBANK_${env.toUpperCase()}_DASHBOARD_URL`;
27770
27917
  const dashboardUrl2 = process.env[envKey];
27771
- if (dashboardUrl2) {
27918
+ if (dashboardUrl2 !== void 0 && dashboardUrl2.length > 0) {
27772
27919
  return `${dashboardUrl2.replace(/\/$/, "")}/api`;
27773
27920
  }
27774
27921
  }
27775
27922
  const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;
27776
- if (dashboardUrl) {
27923
+ if (dashboardUrl !== void 0 && dashboardUrl.length > 0) {
27777
27924
  return `${dashboardUrl.replace(/\/$/, "")}/api`;
27778
27925
  }
27779
27926
  throw new Error("Dashboard URL not configured. Set NEXT_PUBLIC_DASHBOARD_URL");
@@ -28328,6 +28475,7 @@ function useBookingSubmission(_siteId) {
28328
28475
  customFields: data.customFields,
28329
28476
  timezone: data.timezone,
28330
28477
  discountCode: data.discountCode,
28478
+ selectedSlidingAmount: data.selectedSlidingAmount,
28331
28479
  selectedCollectionOptionKind: data.selectedCollectionOptionKind,
28332
28480
  altchaSolution: data.altchaSolution ?? null,
28333
28481
  successUrl: data.successUrl,
@@ -28856,6 +29004,9 @@ function createInitialBookingFlowReducerState(input) {
28856
29004
  fieldValues: collectInitialFieldValues(initialData),
28857
29005
  touchedFields: {},
28858
29006
  discountCode: toOptionalString(initialData.discountCode) ?? "",
29007
+ selectedSlidingAmountInput: toOptionalString(
29008
+ initialData.selectedSlidingAmountInput
29009
+ ) ?? "",
28859
29010
  selectedCollectionOptionKind: toSelectedCollectionOptionKind(
28860
29011
  initialData.selectedCollectionOptionKind
28861
29012
  ),
@@ -28908,6 +29059,7 @@ function bookingFlowReducer(state, event) {
28908
29059
  selectedDate: void 0,
28909
29060
  selectedSlot: null,
28910
29061
  selectedAppointmentPackageId: void 0,
29062
+ selectedSlidingAmountInput: "",
28911
29063
  selectedCollectionOptionKind: void 0,
28912
29064
  validationErrors: clearBookingFormError(state.validationErrors)
28913
29065
  };
@@ -28961,6 +29113,13 @@ function bookingFlowReducer(state, event) {
28961
29113
  selectedAppointmentPackageId: void 0,
28962
29114
  validationErrors: clearBookingFormError(state.validationErrors)
28963
29115
  };
29116
+ case "sliding-amount-changed":
29117
+ return {
29118
+ ...state,
29119
+ selectedSlidingAmountInput: event.value,
29120
+ selectedAppointmentPackageId: void 0,
29121
+ validationErrors: clearBookingFormError(state.validationErrors)
29122
+ };
28964
29123
  case "appointment-package-selected":
28965
29124
  return {
28966
29125
  ...state,
@@ -29063,6 +29222,7 @@ function toBookingFormData(state) {
29063
29222
  selectedDate: state.selectedDate,
29064
29223
  selectedSlot: state.selectedSlot?.startAt,
29065
29224
  discountCode: state.discountCode || void 0,
29225
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput || void 0,
29066
29226
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
29067
29227
  selectedAppointmentPackageId: state.selectedAppointmentPackageId
29068
29228
  };
@@ -29080,7 +29240,8 @@ function buildBookingStepResolution(input) {
29080
29240
  input.form.settings?.serviceIds && input.form.settings.serviceIds.length > 1
29081
29241
  );
29082
29242
  const selectedService = paymentResolution.service;
29083
- const needsPayment = paymentResolution.mode !== "hidden" || Boolean(
29243
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
29244
+ const needsPayment = Boolean(selectedServiceSlidingScale) || paymentResolution.mode !== "hidden" || Boolean(
29084
29245
  input.enableAppointmentPackages && selectedService && (selectedService.priceAmount ?? 0) > 0
29085
29246
  );
29086
29247
  const hasFields = Boolean(input.form.schema?.fields.length);
@@ -29124,6 +29285,18 @@ function validateBookingStep(input) {
29124
29285
  services: input.services,
29125
29286
  defaultServiceId: input.defaultServiceId
29126
29287
  });
29288
+ const slidingScale = getBookingServiceSlidingScale(resolution.service);
29289
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(
29290
+ slidingScale,
29291
+ parseBookingCurrencyInputToCents(input.data.selectedSlidingAmountInput)
29292
+ )) {
29293
+ return {
29294
+ ok: false,
29295
+ errors: createFormBookingValidationError(
29296
+ "Please choose an amount within the sliding scale"
29297
+ )
29298
+ };
29299
+ }
29127
29300
  if (resolution.mode === "hidden" || resolution.mode === "disclosure_only") {
29128
29301
  return { ok: true };
29129
29302
  }
@@ -29522,6 +29695,7 @@ var init_useHeadlessBookingFlow_state = __esm({
29522
29695
  "../blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.ts"() {
29523
29696
  "use strict";
29524
29697
  init_booking_form_headless_state();
29698
+ init_booking_form_state();
29525
29699
  }
29526
29700
  });
29527
29701
 
@@ -29561,11 +29735,27 @@ function useHeadlessBookingFlow(options) {
29561
29735
  const selectedServiceId = state.selectedServiceId ?? defaultServiceId;
29562
29736
  const selectedResourceId = state.selectedResourceId ?? defaultResourceId;
29563
29737
  const selectedService = options.services.find((service) => service.id === selectedServiceId) ?? null;
29738
+ const lastSlidingServiceIdRef = useRef6(null);
29739
+ useEffect6(() => {
29740
+ const selectedId = selectedService?.id ?? null;
29741
+ if (lastSlidingServiceIdRef.current === selectedId) return;
29742
+ lastSlidingServiceIdRef.current = selectedId;
29743
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
29744
+ dispatch({
29745
+ type: "sliding-amount-changed",
29746
+ value: slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatBookingCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
29747
+ });
29748
+ }, [selectedService]);
29564
29749
  const hasPortalBearerAuth = (api.config.authOverrides?.portal ?? api.config.auth).type === "bearer";
29750
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
29751
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
29752
+ state.selectedSlidingAmountInput
29753
+ );
29754
+ const selectedAppointmentPriceAmount = selectedServiceSlidingScale && selectedSlidingAmount !== null ? selectedSlidingAmount : selectedService?.priceAmount ?? 0;
29565
29755
  const canLoadAppointmentPackages = Boolean(
29566
- options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && (selectedService.priceAmount ?? 0) > 0
29756
+ options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && !selectedServiceSlidingScale && (selectedService.priceAmount ?? 0) > 0
29567
29757
  );
29568
- const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
29758
+ const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || selectedServiceSlidingScale || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
29569
29759
  const data = useMemo2(() => toBookingFormData(state), [state]);
29570
29760
  const availabilityDatesQuery = useMemo2(
29571
29761
  () => buildBookingAvailabilityQuery({
@@ -29795,13 +29985,13 @@ function useHeadlessBookingFlow(options) {
29795
29985
  state.selectedSlot
29796
29986
  ]);
29797
29987
  const appointmentDiscountContext = useMemo2(
29798
- () => selectedService && (selectedService.priceAmount ?? 0) > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
29799
- [selectedService?.id, selectedService?.priceAmount]
29988
+ () => selectedService && selectedAppointmentPriceAmount > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
29989
+ [selectedAppointmentPriceAmount, selectedService?.id]
29800
29990
  );
29801
29991
  const discountUi = useDiscountCodeUi({
29802
29992
  api,
29803
29993
  discountContext: appointmentDiscountContext,
29804
- subtotalCents: selectedService?.priceAmount ?? 0
29994
+ subtotalCents: selectedAppointmentPriceAmount
29805
29995
  });
29806
29996
  const { submit, clearFeedback } = useBookingSubmission(options.siteId);
29807
29997
  const submittingRef = useRef6(false);
@@ -29944,6 +30134,7 @@ function useHeadlessBookingFlow(options) {
29944
30134
  type: "appointment-package-selected",
29945
30135
  packageId: packageId ? unsafeAsCustomerPassId(packageId) : null
29946
30136
  }),
30137
+ setSlidingAmountInput: (value) => dispatch({ type: "sliding-amount-changed", value }),
29947
30138
  setDiscountCode: (code) => {
29948
30139
  dispatch({ type: "discount-code-changed", code });
29949
30140
  latestRef.current.setDiscountCode(code);
@@ -30014,11 +30205,11 @@ function useHeadlessBookingFlow(options) {
30014
30205
  () => selectedService && paymentResolution.mode !== "unsupported" ? buildBookingPaymentSelectionViewModel({
30015
30206
  serviceTitle: selectedService.title,
30016
30207
  paymentTerms: selectedService.paymentTerms,
30017
- priceAmount: selectedService.priceAmount,
30208
+ priceAmount: selectedAppointmentPriceAmount,
30018
30209
  priceCurrency: selectedService.priceCurrency,
30019
30210
  bookingStartAt: state.selectedSlot?.startAt
30020
30211
  }) : null,
30021
- [paymentResolution.mode, selectedService, state.selectedSlot?.startAt]
30212
+ [paymentResolution.mode, selectedAppointmentPriceAmount, selectedService, state.selectedSlot?.startAt]
30022
30213
  );
30023
30214
  const appointmentPackageItems = appointmentPackages.tag === "loaded" ? appointmentPackages.value : [];
30024
30215
  const selectedAppointmentPackage = appointmentPackageItems.find(
@@ -30047,6 +30238,9 @@ function useHeadlessBookingFlow(options) {
30047
30238
  selection: null,
30048
30239
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
30049
30240
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
30241
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
30242
+ selectedSlidingAmount,
30243
+ slidingScale: selectedServiceSlidingScale,
30050
30244
  appointmentPackages: appointmentPackagesModel,
30051
30245
  errorMessage: paymentResolution.errorMessage ?? "This service cannot be booked right now."
30052
30246
  } : {
@@ -30055,6 +30249,9 @@ function useHeadlessBookingFlow(options) {
30055
30249
  selection: paymentSelection,
30056
30250
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
30057
30251
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
30252
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
30253
+ selectedSlidingAmount,
30254
+ slidingScale: selectedServiceSlidingScale,
30058
30255
  appointmentPackages: appointmentPackagesModel
30059
30256
  };
30060
30257
  return {
@@ -30669,6 +30866,9 @@ var init_FieldValidationMessage = __esm({
30669
30866
 
30670
30867
  // ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
30671
30868
  import { Fragment as Fragment9, jsx as jsx62, jsxs as jsxs46 } from "react/jsx-runtime";
30869
+ function formatBookingAmount(amount, currency) {
30870
+ return formatCurrencyAmount(amount, currency, { decimals: "auto" });
30871
+ }
30672
30872
  function HeadlessBookingDefaultForm({
30673
30873
  booking,
30674
30874
  onSubmit
@@ -30945,6 +31145,7 @@ function HeadlessBookingPaymentSelectionStep({
30945
31145
  }
30946
31146
  const packageOptions = booking.payment.appointmentPackages.items;
30947
31147
  const hasPackageOptions = packageOptions.length > 0;
31148
+ const slidingScale = booking.payment.slidingScale;
30948
31149
  return /* @__PURE__ */ jsxs46("div", { className: "ab-stack", children: [
30949
31150
  /* @__PURE__ */ jsxs46("div", { className: "ab-info-box", children: [
30950
31151
  /* @__PURE__ */ jsx62("h3", { className: "ab-subtitle", children: viewModel.sectionTitle }),
@@ -30955,6 +31156,28 @@ function HeadlessBookingPaymentSelectionStep({
30955
31156
  booking.payment.appointmentPackages.state.message,
30956
31157
  " You can continue with the normal payment option."
30957
31158
  ] }) : null,
31159
+ slidingScale ? /* @__PURE__ */ jsxs46("div", { className: "rb-space-y-2", children: [
31160
+ /* @__PURE__ */ jsx62("label", { htmlFor: "appointment-sliding-amount", className: "form-label", children: "Choose your price" }),
31161
+ /* @__PURE__ */ jsxs46("p", { className: "rb-caption status-muted", children: [
31162
+ formatBookingAmount(slidingScale.minAmount, slidingScale.currency),
31163
+ " to ",
31164
+ formatBookingAmount(slidingScale.maxAmount, slidingScale.currency),
31165
+ slidingScale.suggestedAmount !== null ? `, suggested ${formatBookingAmount(slidingScale.suggestedAmount, slidingScale.currency)}` : ""
31166
+ ] }),
31167
+ /* @__PURE__ */ jsx62(
31168
+ "input",
31169
+ {
31170
+ id: "appointment-sliding-amount",
31171
+ type: "number",
31172
+ className: "form-input",
31173
+ min: slidingScale.minAmount / 100,
31174
+ max: slidingScale.maxAmount / 100,
31175
+ step: "0.01",
31176
+ value: booking.payment.selectedSlidingAmountInput,
31177
+ onChange: (event) => booking.actions.setSlidingAmountInput(event.target.value)
31178
+ }
31179
+ )
31180
+ ] }) : null,
30958
31181
  /* @__PURE__ */ jsxs46("div", { className: "ab-stack", role: "list", "aria-label": "Payment options", children: [
30959
31182
  packageOptions.map((appointmentPackage) => {
30960
31183
  const isSelected = booking.payment.selectedAppointmentPackageId === appointmentPackage.customerPassId;
@@ -32890,6 +33113,7 @@ function useEligibility(params) {
32890
33113
  occurrenceId: params.occurrenceId,
32891
33114
  attendanceMode: params.attendanceMode ?? void 0,
32892
33115
  ticketTypeId: params.ticketTypeId ?? void 0,
33116
+ selectedSlidingAmount: params.selectedSlidingAmount ?? void 0,
32893
33117
  email: params.email.trim().toLowerCase(),
32894
33118
  ticketsCount: params.ticketsCount
32895
33119
  });
@@ -32910,7 +33134,7 @@ function useEligibility(params) {
32910
33134
  return () => {
32911
33135
  cancelled = true;
32912
33136
  };
32913
- }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.email, params.ticketsCount]);
33137
+ }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.selectedSlidingAmount, params.email, params.ticketsCount]);
32914
33138
  return {
32915
33139
  state,
32916
33140
  eligibility: state.eligibility,
@@ -33261,6 +33485,41 @@ function TicketCounterLimit(props2) {
33261
33485
  ] }) : null
33262
33486
  ] });
33263
33487
  }
33488
+ function formatCurrencyInputAmount(amount) {
33489
+ const major = amount / 100;
33490
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
33491
+ }
33492
+ function SlidingScaleAmountField(props2) {
33493
+ const minLabel = formatPrice(props2.slidingScale.minAmount, props2.currency);
33494
+ const maxLabel = formatPrice(props2.slidingScale.maxAmount, props2.currency);
33495
+ const suggestedLabel = props2.slidingScale.suggestedAmount !== null ? formatPrice(props2.slidingScale.suggestedAmount, props2.currency) : null;
33496
+ return /* @__PURE__ */ jsxs54("div", { className: "er-field", children: [
33497
+ /* @__PURE__ */ jsx77("label", { htmlFor: "er-sliding-amount", className: "er-label", children: "Amount" }),
33498
+ /* @__PURE__ */ jsx77(
33499
+ "input",
33500
+ {
33501
+ id: "er-sliding-amount",
33502
+ type: "number",
33503
+ min: formatCurrencyInputAmount(props2.slidingScale.minAmount),
33504
+ max: formatCurrencyInputAmount(props2.slidingScale.maxAmount),
33505
+ step: "0.01",
33506
+ inputMode: "decimal",
33507
+ className: "er-input",
33508
+ value: props2.value,
33509
+ onChange: (event) => props2.onChange?.(event.target.value)
33510
+ }
33511
+ ),
33512
+ /* @__PURE__ */ jsxs54("p", { className: "er-text er-text--muted", children: [
33513
+ "Choose between ",
33514
+ minLabel,
33515
+ " and ",
33516
+ maxLabel,
33517
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
33518
+ "."
33519
+ ] }),
33520
+ props2.message ? /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: props2.message }) : null
33521
+ ] });
33522
+ }
33264
33523
  function getTicketTypeCounterAvailabilityLabel(input) {
33265
33524
  if (input.selectedRemainingCapacity !== null) {
33266
33525
  return `${input.selectedRemainingCapacity} remaining for this ticket type`;
@@ -33332,6 +33591,10 @@ var init_TicketSelectionStep = __esm({
33332
33591
  maxTickets,
33333
33592
  selectedTicketTypeId = null,
33334
33593
  onTicketTypeChange,
33594
+ slidingScale = null,
33595
+ selectedSlidingAmountInput = "",
33596
+ slidingScaleSelectionMessage = null,
33597
+ onSlidingAmountChange,
33335
33598
  onTicketsChange
33336
33599
  }) => {
33337
33600
  const deliveryMode = event?.deliveryMode ?? "in_person";
@@ -33447,6 +33710,16 @@ var init_TicketSelectionStep = __esm({
33447
33710
  ] })
33448
33711
  ] })
33449
33712
  ] }) : /* @__PURE__ */ jsxs54(Fragment12, { children: [
33713
+ slidingScale ? /* @__PURE__ */ jsx77(
33714
+ SlidingScaleAmountField,
33715
+ {
33716
+ slidingScale,
33717
+ currency: eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2,
33718
+ value: selectedSlidingAmountInput,
33719
+ message: slidingScaleSelectionMessage,
33720
+ onChange: onSlidingAmountChange
33721
+ }
33722
+ ) : null,
33450
33723
  /* @__PURE__ */ jsx77("h3", { className: "er-title", children: canJoinWaitlist ? "How many waitlist spots?" : "How many tickets?" }),
33451
33724
  /* @__PURE__ */ jsxs54("div", { className: "er-ticket-counter-group", children: [
33452
33725
  /* @__PURE__ */ jsx77(
@@ -34823,6 +35096,10 @@ function EventRegistrationWizardContent(props2) {
34823
35096
  maxTickets: wizard.maxTicketsNum,
34824
35097
  selectedTicketTypeId: wizard.selectedTicketTypeId,
34825
35098
  onTicketTypeChange: wizard.setSelectedTicketTypeId,
35099
+ slidingScale: wizard.selectedEventSlidingScale,
35100
+ selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
35101
+ slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
35102
+ onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
34826
35103
  onTicketsChange: wizard.setTicketsCount
34827
35104
  }
34828
35105
  )
@@ -35039,7 +35316,7 @@ function EventRegistrationSummaryBar(props2) {
35039
35316
  const summaryTimeZone = wizard.occurrenceContext?.timeZone ?? summaryEvent?.timeZone ?? null;
35040
35317
  const occurrenceOverrideTitle = wizard.occurrenceContext?.overrides?.title;
35041
35318
  const summaryTitle = occurrenceOverrideTitle ?? summaryEvent?.title ?? "";
35042
- const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
35319
+ const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (wizard.selectedEventSlidingScale ? wizard.selectedSlidingAmount : null) ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
35043
35320
  const summaryCurrency = (summaryEvent ? publicEventPricingCurrency(summaryEvent.pricing) : null) ?? DEFAULT_EVENT_CURRENCY2;
35044
35321
  const teamMemberNames = (summaryEvent?.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
35045
35322
  const subtotalCents = Math.max(0, summaryPrice * wizard.ticketsCount);
@@ -35490,6 +35767,7 @@ function useEventRegistrationSubmit(options) {
35490
35767
  occurrenceId: submitOccurrenceId,
35491
35768
  attendanceMode: options.selectedAttendanceMode ?? void 0,
35492
35769
  ticketTypeId: options.selectedTicketTypeId ?? void 0,
35770
+ selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
35493
35771
  email: options.formData.email.trim().toLowerCase(),
35494
35772
  name: options.formData.name.trim(),
35495
35773
  ticketsCount: options.ticketsCount,
@@ -35599,6 +35877,35 @@ var init_useEventRegistrationSubmit = __esm({
35599
35877
 
35600
35878
  // ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.ts
35601
35879
  import { useCallback as useCallback7, useEffect as useEffect14, useMemo as useMemo7, useReducer as useReducer9, useState as useState4 } from "react";
35880
+ function parseCurrencyInputToCents(value) {
35881
+ const trimmed = value.trim();
35882
+ if (!trimmed) return null;
35883
+ const amount = Number(trimmed);
35884
+ if (!Number.isFinite(amount) || amount < 0) return null;
35885
+ return Math.round(amount * 100);
35886
+ }
35887
+ function formatCentsAsCurrencyInput(amount) {
35888
+ const major = amount / 100;
35889
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
35890
+ }
35891
+ function getSlidingScaleForEvent(event) {
35892
+ const pricing = event ? resolveRuntimeEventPricing(event) : null;
35893
+ return pricing?.kind === "sliding_scale" ? {
35894
+ minAmount: pricing.minAmount,
35895
+ maxAmount: pricing.maxAmount,
35896
+ suggestedAmount: pricing.suggestedAmount
35897
+ } : null;
35898
+ }
35899
+ function getSlidingScaleSelectionMessage(input) {
35900
+ if (!input.slidingScale) return null;
35901
+ if (input.selectedAmount === null) {
35902
+ return "Choose an amount to continue.";
35903
+ }
35904
+ if (input.selectedAmount < input.slidingScale.minAmount || input.selectedAmount > input.slidingScale.maxAmount) {
35905
+ return "Choose an amount within the sliding scale.";
35906
+ }
35907
+ return null;
35908
+ }
35602
35909
  function useEventRegistrationWizard(options) {
35603
35910
  const api = useBlockApi();
35604
35911
  const registrationEvents = useMemo7(
@@ -35643,6 +35950,10 @@ function useEventRegistrationWizard(options) {
35643
35950
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
35644
35951
  );
35645
35952
  const [selectedTicketTypeId, setSelectedTicketTypeId] = useState4(null);
35953
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState4(() => {
35954
+ const slidingScale = getSlidingScaleForEvent(preSelectedEvent ?? null);
35955
+ return slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : "";
35956
+ });
35646
35957
  const [registrationResult, setRegistrationResult] = useState4(null);
35647
35958
  const [errorMessage, setErrorMessage] = useState4("");
35648
35959
  const [altchaSolution, setAltchaSolution] = useState4(null);
@@ -35679,6 +35990,7 @@ function useEventRegistrationWizard(options) {
35679
35990
  occurrenceId,
35680
35991
  attendanceMode: selectedAttendanceMode,
35681
35992
  ticketTypeId: selectedTicketTypeId,
35993
+ selectedSlidingAmount: parseCurrencyInputToCents(selectedSlidingAmountInput),
35682
35994
  email: formData.email,
35683
35995
  ticketsCount
35684
35996
  });
@@ -35814,6 +36126,18 @@ function useEventRegistrationWizard(options) {
35814
36126
  }),
35815
36127
  [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility, selectedTicketTypeId]
35816
36128
  );
36129
+ const selectedSlidingAmount = useMemo7(
36130
+ () => parseCurrencyInputToCents(selectedSlidingAmountInput),
36131
+ [selectedSlidingAmountInput]
36132
+ );
36133
+ const selectedEventSlidingScale = useMemo7(
36134
+ () => getSlidingScaleForEvent(selectedEvent ?? preSelectedEvent),
36135
+ [selectedEvent, preSelectedEvent]
36136
+ );
36137
+ const slidingScaleSelectionMessage = getSlidingScaleSelectionMessage({
36138
+ slidingScale: selectedEventSlidingScale,
36139
+ selectedAmount: selectedSlidingAmount
36140
+ });
35817
36141
  const ticketTypeSelectionRequired = useMemo7(() => {
35818
36142
  const event = selectedEvent ?? preSelectedEvent;
35819
36143
  if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
@@ -35882,12 +36206,24 @@ function useEventRegistrationWizard(options) {
35882
36206
  setGuestSectionShake(false);
35883
36207
  };
35884
36208
  const eventRequiresPayment = useMemo7(
35885
- () => resolveEventRequiresPayment({
36209
+ () => {
36210
+ if (selectedEventSlidingScale) {
36211
+ return (selectedSlidingAmount ?? 0) > 0;
36212
+ }
36213
+ return resolveEventRequiresPayment({
36214
+ eligibility,
36215
+ selectedTicketType,
36216
+ event: selectedEvent ?? preSelectedEvent
36217
+ });
36218
+ },
36219
+ [
35886
36220
  eligibility,
35887
36221
  selectedTicketType,
35888
- event: selectedEvent ?? preSelectedEvent
35889
- }),
35890
- [eligibility, selectedTicketType, selectedEvent, preSelectedEvent]
36222
+ selectedEvent,
36223
+ preSelectedEvent,
36224
+ selectedEventSlidingScale,
36225
+ selectedSlidingAmount
36226
+ ]
35891
36227
  );
35892
36228
  const handleDetailsComplete = async () => {
35893
36229
  if (ticketTypeSelectionRequired && !hasValidSelectedTicketType) {
@@ -35896,6 +36232,11 @@ function useEventRegistrationWizard(options) {
35896
36232
  setStep("error");
35897
36233
  return { ok: false, reason: "blocked", message };
35898
36234
  }
36235
+ if (slidingScaleSelectionMessage) {
36236
+ setErrorMessage(slidingScaleSelectionMessage);
36237
+ setStep("error");
36238
+ return { ok: false, reason: "blocked", message: slidingScaleSelectionMessage };
36239
+ }
35899
36240
  if (!validateForm()) {
35900
36241
  setGuestSectionShake(true);
35901
36242
  return {
@@ -35980,9 +36321,18 @@ function useEventRegistrationWizard(options) {
35980
36321
  const discountSubtotalCents = useMemo7(() => {
35981
36322
  if (discountContext?.type !== "event") return void 0;
35982
36323
  const event = selectedEvent ?? preSelectedEvent;
35983
- const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
36324
+ const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (selectedEventSlidingScale ? selectedSlidingAmount : null) ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
35984
36325
  return Math.max(0, eventPriceCents * ticketsCount);
35985
- }, [discountContext, selectedTicketType, eligibility, selectedEvent, preSelectedEvent, ticketsCount]);
36326
+ }, [
36327
+ discountContext,
36328
+ selectedTicketType,
36329
+ eligibility,
36330
+ selectedEvent,
36331
+ preSelectedEvent,
36332
+ selectedEventSlidingScale,
36333
+ selectedSlidingAmount,
36334
+ ticketsCount
36335
+ ]);
35986
36336
  const discountUi = useDiscountCodeUi({ api, discountContext, subtotalCents: discountSubtotalCents });
35987
36337
  const selectionMethod = paymentSelection.method;
35988
36338
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
@@ -35991,6 +36341,7 @@ function useEventRegistrationWizard(options) {
35991
36341
  registrationTarget,
35992
36342
  selectedAttendanceMode,
35993
36343
  selectedTicketTypeId,
36344
+ selectedSlidingAmount,
35994
36345
  ticketsCount,
35995
36346
  formData,
35996
36347
  altchaSolution,
@@ -36005,6 +36356,10 @@ function useEventRegistrationWizard(options) {
36005
36356
  });
36006
36357
  const handleEventSelect = (event) => {
36007
36358
  setSelectedEvent(event);
36359
+ const slidingScale = getSlidingScaleForEvent(event);
36360
+ setSelectedSlidingAmountInput(
36361
+ slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
36362
+ );
36008
36363
  setSelectedAttendanceMode(
36009
36364
  (event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person"
36010
36365
  );
@@ -36014,6 +36369,7 @@ function useEventRegistrationWizard(options) {
36014
36369
  const handleRetry = () => {
36015
36370
  dispatchFlow({ type: "reset", hasPreSelectedContext });
36016
36371
  setSelectedTicketTypeId(null);
36372
+ setSelectedSlidingAmountInput("");
36017
36373
  setSelectedAttendanceMode(
36018
36374
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
36019
36375
  );
@@ -36050,6 +36406,11 @@ function useEventRegistrationWizard(options) {
36050
36406
  setSelectedAttendanceMode,
36051
36407
  selectedTicketTypeId,
36052
36408
  setSelectedTicketTypeId,
36409
+ selectedSlidingAmountInput,
36410
+ setSelectedSlidingAmountInput,
36411
+ selectedSlidingAmount,
36412
+ selectedEventSlidingScale,
36413
+ slidingScaleSelectionMessage,
36053
36414
  selectedTicketType,
36054
36415
  visibleTicketTypes,
36055
36416
  ticketTypeSelectionRequired,
@@ -37365,6 +37726,44 @@ function CoursePricingSelector({
37365
37726
  )
37366
37727
  ] });
37367
37728
  }
37729
+ function CourseSlidingScaleInput({
37730
+ pricing,
37731
+ value,
37732
+ onChange
37733
+ }) {
37734
+ const slidingScale = pricing.slidingScale;
37735
+ if (!slidingScale) {
37736
+ return null;
37737
+ }
37738
+ const minLabel = formatCoursePriceAmount(slidingScale.minAmount, pricing.currency);
37739
+ const maxLabel = formatCoursePriceAmount(slidingScale.maxAmount, pricing.currency);
37740
+ const suggestedLabel = slidingScale.suggestedAmount !== null ? formatCoursePriceAmount(slidingScale.suggestedAmount, pricing.currency) : null;
37741
+ return /* @__PURE__ */ jsxs75("div", { className: "cr-form__field", children: [
37742
+ /* @__PURE__ */ jsx105("label", { htmlFor: "cr-sliding-amount", className: "cr-form__label", children: "Amount" }),
37743
+ /* @__PURE__ */ jsx105(
37744
+ "input",
37745
+ {
37746
+ id: "cr-sliding-amount",
37747
+ type: "number",
37748
+ min: slidingScale.minAmount / 100,
37749
+ max: slidingScale.maxAmount / 100,
37750
+ step: "0.01",
37751
+ inputMode: "decimal",
37752
+ className: "cr-form__input",
37753
+ value,
37754
+ onChange: (event) => onChange(event.target.value)
37755
+ }
37756
+ ),
37757
+ /* @__PURE__ */ jsxs75("p", { className: "cr-text cr-text--muted", children: [
37758
+ "Choose between ",
37759
+ minLabel,
37760
+ " and ",
37761
+ maxLabel,
37762
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
37763
+ "."
37764
+ ] })
37765
+ ] });
37766
+ }
37368
37767
  function EnrollmentForm({ formData, errors, onChange, isEmailLocked, isLoading, onLogout }) {
37369
37768
  const nameErrorId = "cr-name-error";
37370
37769
  const emailErrorId = "cr-email-error";
@@ -37460,6 +37859,17 @@ var init_components2 = __esm({
37460
37859
 
37461
37860
  // ../blocks/src/system/runtime/nodes/course-registration/useCourseRegistrationWizard.ts
37462
37861
  import { useEffect as useEffect18, useMemo as useMemo9, useState as useState9 } from "react";
37862
+ function parseCurrencyInputToCents2(value) {
37863
+ const trimmed = value.trim();
37864
+ if (!trimmed) return null;
37865
+ const amount = Number(trimmed);
37866
+ if (!Number.isFinite(amount) || amount < 0) return null;
37867
+ return Math.round(amount * 100);
37868
+ }
37869
+ function formatCentsAsCurrencyInput2(amount) {
37870
+ const major = amount / 100;
37871
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
37872
+ }
37463
37873
  function shouldFetchCourseEligibility(course, hasPaidSelection, portalAuthState, hasWaitlistClaimToken) {
37464
37874
  if (!course || portalAuthState === "checking") return false;
37465
37875
  if (hasWaitlistClaimToken) return false;
@@ -37477,12 +37887,27 @@ function getSessionIdFromUrl() {
37477
37887
  }
37478
37888
  function getMissingCoursePricingSelectionMessage(pricing) {
37479
37889
  if (pricing?.requiresSelection && !pricing.selectedOption) {
37890
+ if (pricing.slidingScale) {
37891
+ return "Choose an amount to continue.";
37892
+ }
37480
37893
  return "Choose a pricing option to continue.";
37481
37894
  }
37895
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount < pricing.slidingScale.minAmount) {
37896
+ return "Choose an amount within the sliding scale.";
37897
+ }
37898
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount > pricing.slidingScale.maxAmount) {
37899
+ return "Choose an amount within the sliding scale.";
37900
+ }
37482
37901
  return null;
37483
37902
  }
37484
37903
  function buildCoursePricingOptionPayload(pricing) {
37485
- return pricing?.selectedOption ? { pricingOptionId: pricing.selectedOption.id } : {};
37904
+ if (pricing?.selectedOption) {
37905
+ return { pricingOptionId: pricing.selectedOption.id };
37906
+ }
37907
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null) {
37908
+ return { selectedSlidingAmount: pricing.selectedSlidingAmount };
37909
+ }
37910
+ return {};
37486
37911
  }
37487
37912
  function resolveHasCoursePayment(pricing) {
37488
37913
  if (!pricing) {
@@ -37523,6 +37948,7 @@ function useCourseRegistrationWizard(props2) {
37523
37948
  scopedCourses.length === 1 ? scopedCourses[0] ?? null : null
37524
37949
  );
37525
37950
  const [selectedPricingOptionId, setSelectedPricingOptionId] = useState9(null);
37951
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState9("");
37526
37952
  const [formData, setFormData] = useState9({
37527
37953
  name: "",
37528
37954
  email: "",
@@ -37544,8 +37970,12 @@ function useCourseRegistrationWizard(props2) {
37544
37970
  const [bookingMode, setBookingMode] = useState9(null);
37545
37971
  const waitlistClaim = useWaitlistClaim();
37546
37972
  const selectedCoursePricing = useMemo9(
37547
- () => selectedCourse ? resolveCoursePricingState(selectedCourse, selectedPricingOptionId) : null,
37548
- [selectedCourse, selectedPricingOptionId]
37973
+ () => selectedCourse ? resolveCoursePricingState(
37974
+ selectedCourse,
37975
+ selectedPricingOptionId,
37976
+ parseCurrencyInputToCents2(selectedSlidingAmountInput)
37977
+ ) : null,
37978
+ [selectedCourse, selectedPricingOptionId, selectedSlidingAmountInput]
37549
37979
  );
37550
37980
  const missingPricingSelectionMessage = getMissingCoursePricingSelectionMessage(
37551
37981
  selectedCoursePricing
@@ -37618,9 +38048,13 @@ function useCourseRegistrationWizard(props2) {
37618
38048
  useEffect18(() => {
37619
38049
  if (!selectedCourse) {
37620
38050
  setSelectedPricingOptionId(null);
38051
+ setSelectedSlidingAmountInput("");
37621
38052
  return;
37622
38053
  }
37623
38054
  const defaultPricing = resolveCoursePricingState(selectedCourse, null);
38055
+ setSelectedSlidingAmountInput(
38056
+ defaultPricing.slidingScale?.suggestedAmount !== null && defaultPricing.slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput2(defaultPricing.slidingScale.suggestedAmount) : ""
38057
+ );
37624
38058
  setSelectedPricingOptionId((previous) => {
37625
38059
  if (previous && defaultPricing.selectableOptions.some((option) => option.id === previous)) {
37626
38060
  return previous;
@@ -37995,6 +38429,8 @@ function useCourseRegistrationWizard(props2) {
37995
38429
  selectedCourse,
37996
38430
  selectedPricingOptionId,
37997
38431
  setSelectedPricingOptionId,
38432
+ selectedSlidingAmountInput,
38433
+ setSelectedSlidingAmountInput,
37998
38434
  selectedCoursePricing,
37999
38435
  setSelectedCourse,
38000
38436
  formData,
@@ -38060,6 +38496,19 @@ var init_useCourseRegistrationWizard = __esm({
38060
38496
 
38061
38497
  // ../blocks/src/system/runtime/nodes/course-registration/CourseRegistrationWizard.tsx
38062
38498
  import { Fragment as Fragment23, jsx as jsx106, jsxs as jsxs76 } from "react/jsx-runtime";
38499
+ function getCourseSlidingScaleMessage(pricing) {
38500
+ if (!pricing.slidingScale) {
38501
+ return null;
38502
+ }
38503
+ const selectedAmount = pricing.selectedSlidingAmount;
38504
+ if (selectedAmount === null) {
38505
+ return "Choose an amount to continue.";
38506
+ }
38507
+ if (selectedAmount < pricing.slidingScale.minAmount || selectedAmount > pricing.slidingScale.maxAmount) {
38508
+ return "Choose an amount within the sliding scale.";
38509
+ }
38510
+ return null;
38511
+ }
38063
38512
  function CourseRegistrationWizard(props2) {
38064
38513
  const {
38065
38514
  showSessions = true,
@@ -38081,6 +38530,8 @@ function CourseRegistrationWizard(props2) {
38081
38530
  selectedCourse,
38082
38531
  selectedPricingOptionId,
38083
38532
  setSelectedPricingOptionId,
38533
+ selectedSlidingAmountInput,
38534
+ setSelectedSlidingAmountInput,
38084
38535
  selectedCoursePricing,
38085
38536
  formData,
38086
38537
  setFormData,
@@ -38155,7 +38606,8 @@ function CourseRegistrationWizard(props2) {
38155
38606
  const canShowDiscountUi = !discountsAvailableLoading && paymentChoice === "checkout" && discountsAvailable && manualCodeEntryAllowed;
38156
38607
  const courseSoldOut = waitlistClaim.claim === null && courseEligibility?.isFull === true && courseEligibility.canJoinWaitlist !== true;
38157
38608
  const detailsSubmitLabel = courseEligibility?.canJoinWaitlist ? "Join waitlist" : buttonText;
38158
- const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption;
38609
+ const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption && !selectedCoursePricing.slidingScale;
38610
+ const slidingScaleMessage = selectedCoursePricing ? getCourseSlidingScaleMessage(selectedCoursePricing) : null;
38159
38611
  const pricingUnavailable = selectedCoursePricing?.display.kind === "unavailable";
38160
38612
  const selectedCourseWithPricing = selectedCourse && selectedCoursePricing ? {
38161
38613
  course: selectedCourse,
@@ -38248,6 +38700,14 @@ function CourseRegistrationWizard(props2) {
38248
38700
  onSelect: setSelectedPricingOptionId
38249
38701
  }
38250
38702
  ),
38703
+ /* @__PURE__ */ jsx106(
38704
+ CourseSlidingScaleInput,
38705
+ {
38706
+ pricing: selectedCourseWithPricing.pricing,
38707
+ value: selectedSlidingAmountInput,
38708
+ onChange: setSelectedSlidingAmountInput
38709
+ }
38710
+ ),
38251
38711
  showSpamProtection && /* @__PURE__ */ jsx106("div", { className: "cr-altcha", children: /* @__PURE__ */ jsx106(
38252
38712
  AltchaWidget,
38253
38713
  {
@@ -38285,7 +38745,7 @@ function CourseRegistrationWizard(props2) {
38285
38745
  {
38286
38746
  type: "submit",
38287
38747
  className: buttonClass,
38288
- disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || pricingUnavailable,
38748
+ disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || slidingScaleMessage !== null || pricingUnavailable,
38289
38749
  children: eligibilityLoading ? "Checking availability\u2026" : courseSoldOut ? "Sold out" : detailsSubmitLabel
38290
38750
  }
38291
38751
  )
@@ -38295,6 +38755,7 @@ function CourseRegistrationWizard(props2) {
38295
38755
  ),
38296
38756
  courseSoldOut && /* @__PURE__ */ jsx106("p", { className: "cr-text cr-text--muted", children: "This course is fully booked and there is no waitlist available right now." }),
38297
38757
  pricingSelectionMissing && /* @__PURE__ */ jsx106("p", { className: "cr-text cr-text--muted", children: "Choose a pricing option to continue." }),
38758
+ slidingScaleMessage && /* @__PURE__ */ jsx106("p", { className: "cr-text cr-text--muted", children: slidingScaleMessage }),
38298
38759
  pricingUnavailable && /* @__PURE__ */ jsx106("p", { className: "cr-text cr-text--muted", children: "Pricing is not available for this course right now." }),
38299
38760
  eligibilityError && /* @__PURE__ */ jsx106("p", { className: "cr-text cr-text--error", children: eligibilityError })
38300
38761
  ] })
@@ -41894,12 +42355,13 @@ var init_carousel_client = __esm({
41894
42355
  ]
41895
42356
  );
41896
42357
  const resolvedSlides = resolveSlidesToShowMobile(normalized.slidesToShow);
42358
+ const canAutoplay = normalized.autoplay && slides.length > 1;
41897
42359
  const cssVars = React20.useMemo(
41898
42360
  () => getCarouselSlidesToShowCssVars(normalized.slidesToShow),
41899
42361
  [normalized.slidesToShow]
41900
42362
  );
41901
42363
  const plugins = React20.useMemo(() => {
41902
- if (!normalized.autoplay) return void 0;
42364
+ if (!canAutoplay) return void 0;
41903
42365
  return [
41904
42366
  Autoplay({
41905
42367
  delay: normalized.autoplayDelay,
@@ -41909,7 +42371,7 @@ var init_carousel_client = __esm({
41909
42371
  // Continue after manual navigation
41910
42372
  })
41911
42373
  ];
41912
- }, [normalized.autoplay, normalized.autoplayDelay]);
42374
+ }, [canAutoplay, normalized.autoplayDelay]);
41913
42375
  const [viewportRef, api] = useEmblaCarousel(
41914
42376
  {
41915
42377
  align: "start",
@@ -41948,7 +42410,7 @@ var init_carousel_client = __esm({
41948
42410
  };
41949
42411
  }, [api]);
41950
42412
  React20.useEffect(() => {
41951
- if (!api || !normalized.autoplay) return;
42413
+ if (!api || !canAutoplay) return;
41952
42414
  const autoplayPlugin = api.plugins()?.autoplay;
41953
42415
  if (!autoplayPlugin) return;
41954
42416
  const handleVisibility = () => {
@@ -41957,7 +42419,7 @@ var init_carousel_client = __esm({
41957
42419
  };
41958
42420
  document.addEventListener("visibilitychange", handleVisibility);
41959
42421
  return () => document.removeEventListener("visibilitychange", handleVisibility);
41960
- }, [api, normalized.autoplay]);
42422
+ }, [api, canAutoplay]);
41961
42423
  const inner = /* @__PURE__ */ jsxs81(Fragment25, { children: [
41962
42424
  /* @__PURE__ */ jsx113("div", { className: "carousel-frame rb-overflow-hidden rb-w-full", children: /* @__PURE__ */ jsx113(
41963
42425
  "div",
@@ -42008,7 +42470,7 @@ var init_carousel_client = __esm({
42008
42470
  "data-show-controls": normalized.showControls ? "true" : "false",
42009
42471
  "data-show-dots": normalized.showDots ? "true" : "false",
42010
42472
  "data-arrow-visibility": normalized.arrowVisibility,
42011
- "data-autoplay": normalized.autoplay ? "true" : "false",
42473
+ "data-autoplay": canAutoplay ? "true" : "false",
42012
42474
  "data-loop": normalized.loop ? "true" : "false",
42013
42475
  children: inner
42014
42476
  }