@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
@@ -117,12 +117,13 @@ var init_result = __esm({
117
117
  });
118
118
 
119
119
  // ../core/src/branded.ts
120
- var unsafeAsMediaAssetId, unsafeAsStorageBucketName, unsafeAsPositiveByteCount, unsafeAsPositiveSeconds, unsafeAsFormId, unsafeAsPassId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, unsafeAsAppointmentResourceId;
120
+ var unsafeAsMediaAssetId, unsafeAsStorageBucketName, unsafeAsStoragePath, unsafeAsPositiveByteCount, unsafeAsPositiveSeconds, unsafeAsFormId, unsafeAsPassId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, unsafeAsAppointmentResourceId;
121
121
  var init_branded = __esm({
122
122
  "../core/src/branded.ts"() {
123
123
  "use strict";
124
124
  unsafeAsMediaAssetId = (id) => id;
125
125
  unsafeAsStorageBucketName = (value) => value;
126
+ unsafeAsStoragePath = (value) => value;
126
127
  unsafeAsPositiveByteCount = (value) => value;
127
128
  unsafeAsPositiveSeconds = (value) => value;
128
129
  unsafeAsFormId = (id) => id;
@@ -268,6 +269,7 @@ function publicEventPricingType(pricing) {
268
269
  return "free";
269
270
  case "single_paid":
270
271
  case "ticket_types":
272
+ case "sliding_scale":
271
273
  case "course":
272
274
  return isFreePublicEventPriceDisplay(pricing.display) ? "free" : "paid";
273
275
  case "external":
@@ -280,6 +282,7 @@ function publicEventPricingCurrency(pricing) {
280
282
  case "free_registration":
281
283
  case "single_paid":
282
284
  case "ticket_types":
285
+ case "sliding_scale":
283
286
  return pricing.currency;
284
287
  case "course":
285
288
  return pricing.display.display.currency;
@@ -297,6 +300,8 @@ function publicEventPricingMode(pricing) {
297
300
  return "single";
298
301
  case "ticket_types":
299
302
  return "ticket_types";
303
+ case "sliding_scale":
304
+ return "sliding_scale";
300
305
  case "drop_in":
301
306
  case "course":
302
307
  case "external":
@@ -330,6 +335,8 @@ function formatCoursePriceDisplayLabel(display, options) {
330
335
  return `Course (${formatCurrencyAmount(display.amount, display.currency, options)})`;
331
336
  case "from":
332
337
  return `Course (From ${formatCurrencyAmount(display.amount, display.currency, options)})`;
338
+ case "range":
339
+ return `Course (${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)})`;
333
340
  default:
334
341
  return assertNever(display);
335
342
  }
@@ -473,10 +480,11 @@ function omitZeroAmountFutureRows(rows) {
473
480
  if (rows.length <= 1) {
474
481
  return rows;
475
482
  }
476
- const [firstRow, ...futureRows] = rows;
477
- if (!firstRow) {
483
+ const firstRow = rows[0];
484
+ if (firstRow === void 0) {
478
485
  return [];
479
486
  }
487
+ const futureRows = rows.slice(1);
480
488
  return [
481
489
  firstRow,
482
490
  ...futureRows.filter((row) => row.amountCents > 0)
@@ -521,11 +529,11 @@ function isValidTimeZone(timeZone) {
521
529
  }
522
530
  function getSafeTimeZone(timeZone) {
523
531
  const normalized = typeof timeZone === "string" ? timeZone.trim() : "";
524
- return normalized && isValidTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
532
+ return normalized.length > 0 && isValidTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
525
533
  }
526
534
  function parseStrictBookingTimeZone(timeZone) {
527
535
  const normalized = timeZone.trim();
528
- if (!normalized || !isValidTimeZone(normalized)) {
536
+ if (normalized.length === 0 || !isValidTimeZone(normalized)) {
529
537
  return null;
530
538
  }
531
539
  return normalized;
@@ -611,7 +619,7 @@ function parseScheduledBookingDateTime(input) {
611
619
  try {
612
620
  const startsAt = toUtcIsoDateTimeString(input.startsAt);
613
621
  const timeZone = parseStrictBookingTimeZone(input.timeZone);
614
- if (!timeZone) {
622
+ if (timeZone === null) {
615
623
  return {
616
624
  ok: false,
617
625
  field: "timeZone",
@@ -750,7 +758,7 @@ function getBookingLocalDateKey(input) {
750
758
  }
751
759
  function parseBookingLocalDate(value) {
752
760
  const match = LOCAL_DATE_PATTERN.exec(value.trim());
753
- if (!match?.groups) {
761
+ if (match === null || match.groups === void 0) {
754
762
  throw new Error(`Invalid booking date format: "${value}" (expected YYYY-MM-DD)`);
755
763
  }
756
764
  const year = Number(match.groups.year);
@@ -809,10 +817,10 @@ function normalizeLocationAddressText(value) {
809
817
  return null;
810
818
  }
811
819
  const trimmed = value.trim();
812
- return trimmed ? trimmed : null;
820
+ return trimmed.length > 0 ? trimmed : null;
813
821
  }
814
822
  function hasLocationCoordinates(value) {
815
- return typeof value?.lat === "number" && typeof value?.lng === "number";
823
+ return value != null && typeof value.lat === "number" && typeof value.lng === "number";
816
824
  }
817
825
  function toLocationCoordinates(value) {
818
826
  if (!hasLocationCoordinates(value)) {
@@ -829,6 +837,13 @@ var init_location = __esm({
829
837
  }
830
838
  });
831
839
 
840
+ // ../core/src/string.ts
841
+ var init_string = __esm({
842
+ "../core/src/string.ts"() {
843
+ "use strict";
844
+ }
845
+ });
846
+
832
847
  // ../core/src/business-address.ts
833
848
  var init_business_address = __esm({
834
849
  "../core/src/business-address.ts"() {
@@ -969,13 +984,6 @@ var init_slug = __esm({
969
984
  }
970
985
  });
971
986
 
972
- // ../core/src/string.ts
973
- var init_string = __esm({
974
- "../core/src/string.ts"() {
975
- "use strict";
976
- }
977
- });
978
-
979
987
  // ../core/src/uuid.ts
980
988
  var init_uuid = __esm({
981
989
  "../core/src/uuid.ts"() {
@@ -1073,9 +1081,20 @@ var init_src = __esm({
1073
1081
  });
1074
1082
 
1075
1083
  // ../media-core/src/fileSafety.ts
1084
+ function containsUnsafePathSegmentCharacter(value) {
1085
+ return value.includes("/") || value.includes("\\") || CONTROL_CHARACTER_PATTERN.test(value);
1086
+ }
1087
+ function isReservedPathSegment(value) {
1088
+ return value === "." || value === "..";
1089
+ }
1090
+ function isUnsafePathSegment(segment) {
1091
+ return segment.trim().length === 0 || segment !== segment.trim() || isReservedPathSegment(segment) || containsUnsafePathSegmentCharacter(segment);
1092
+ }
1093
+ var CONTROL_CHARACTER_PATTERN;
1076
1094
  var init_fileSafety = __esm({
1077
1095
  "../media-core/src/fileSafety.ts"() {
1078
1096
  "use strict";
1097
+ CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
1079
1098
  }
1080
1099
  });
1081
1100
 
@@ -1115,11 +1134,21 @@ var init_fileDomain = __esm({
1115
1134
  });
1116
1135
 
1117
1136
  // ../media-core/src/fileStorage.ts
1137
+ function parseStoragePath(value) {
1138
+ return joinStoragePathSegments(value.split("/"));
1139
+ }
1140
+ function joinStoragePathSegments(segments) {
1141
+ if (segments.some(isUnsafePathSegment)) {
1142
+ return err("unsafe-path-segment");
1143
+ }
1144
+ return ok(unsafeAsStoragePath(segments.join("/")));
1145
+ }
1118
1146
  var FILE_STORAGE_BUCKETS, PUBLIC_SITE_FILE_STORAGE_BUCKETS, PRIVATE_SITE_FILE_BUCKET_MIME_TYPES, TEMP_UPLOAD_BUCKET_MIME_TYPES;
1119
1147
  var init_fileStorage = __esm({
1120
1148
  "../media-core/src/fileStorage.ts"() {
1121
1149
  "use strict";
1122
1150
  init_src();
1151
+ init_fileSafety();
1123
1152
  FILE_STORAGE_BUCKETS = {
1124
1153
  publicSite: unsafeAsStorageBucketName("media"),
1125
1154
  privateSite: unsafeAsStorageBucketName("site-files-private"),
@@ -1305,14 +1334,18 @@ var init_uploadVerificationContract = __esm({
1305
1334
  });
1306
1335
 
1307
1336
  // ../media-core/src/videoPrep.ts
1337
+ function unsafeAsPositiveVideoDurationSeconds(value) {
1338
+ return value;
1339
+ }
1308
1340
  var VIDEO_PREP_LIMITS;
1309
1341
  var init_videoPrep = __esm({
1310
1342
  "../media-core/src/videoPrep.ts"() {
1311
1343
  "use strict";
1344
+ init_src();
1312
1345
  VIDEO_PREP_LIMITS = {
1313
- maxRawByteCount: 500 * 1024 * 1024,
1314
- inlineContentMaxDurationSeconds: 60,
1315
- backgroundVideoMaxDurationSeconds: 30,
1346
+ maxRawByteCount: unsafeAsPositiveByteCount(500 * 1024 * 1024),
1347
+ inlineContentMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(60),
1348
+ backgroundVideoMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(30),
1316
1349
  targetMaxDimensionPixels: 1080
1317
1350
  };
1318
1351
  }
@@ -1383,10 +1416,10 @@ var init_identifiers = __esm({
1383
1416
 
1384
1417
  // ../media-core/src/url.ts
1385
1418
  function isRelativeDirectImageStoragePath(storagePath) {
1386
- return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix));
1419
+ return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix)) && parseStoragePath(storagePath).ok;
1387
1420
  }
1388
1421
  function isSiteScopedDirectImageStoragePath(storagePath) {
1389
- return storagePath.startsWith("sites/");
1422
+ return storagePath.startsWith("sites/") && parseStoragePath(storagePath).ok;
1390
1423
  }
1391
1424
  function encodeStoragePath(storagePath) {
1392
1425
  return storagePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
@@ -1467,6 +1500,7 @@ var DEFAULT_BASE_PATH, RELATIVE_STORAGE_PATH_PREFIXES, formatNumber, ORDER, getI
1467
1500
  var init_url = __esm({
1468
1501
  "../media-core/src/url.ts"() {
1469
1502
  "use strict";
1503
+ init_fileStorage();
1470
1504
  DEFAULT_BASE_PATH = "/api/img";
1471
1505
  RELATIVE_STORAGE_PATH_PREFIXES = [
1472
1506
  "originals/",
@@ -2050,9 +2084,12 @@ var init_schemas = __esm({
2050
2084
  });
2051
2085
  coursePricingSchema = z5.object({
2052
2086
  bookingRequirement: z5.enum(["none", "required"]),
2053
- pricingStrategy: z5.enum(["free", "single", "tiered"]),
2087
+ pricingStrategy: z5.enum(["free", "single", "tiered", "sliding_scale"]),
2054
2088
  currency: z5.string(),
2055
2089
  singlePriceAmount: z5.number().nullable(),
2090
+ slidingMinAmount: z5.number().nullable().optional(),
2091
+ slidingMaxAmount: z5.number().nullable().optional(),
2092
+ slidingSuggestedAmount: z5.number().nullable().optional(),
2056
2093
  paymentTerms: blockBookingPaymentTermsSchema,
2057
2094
  options: z5.array(coursePricingOptionSchema)
2058
2095
  });
@@ -2217,6 +2254,19 @@ var init_schemas2 = __esm({
2217
2254
  ticketTypes: z6.array(publicEventTicketTypeSchema),
2218
2255
  display: publicEventTicketTypePriceDisplaySchema
2219
2256
  }),
2257
+ z6.object({
2258
+ kind: z6.literal("sliding_scale"),
2259
+ minAmount: z6.number(),
2260
+ maxAmount: z6.number(),
2261
+ suggestedAmount: z6.number().nullable(),
2262
+ currency: z6.string(),
2263
+ display: z6.object({
2264
+ kind: z6.literal("range"),
2265
+ minAmount: z6.number(),
2266
+ maxAmount: z6.number(),
2267
+ currency: z6.string()
2268
+ })
2269
+ }),
2220
2270
  z6.object({
2221
2271
  kind: z6.literal("course"),
2222
2272
  display: z6.object({ kind: z6.literal("course"), display: publicCoursePriceDisplaySchema })
@@ -23337,6 +23387,7 @@ var init_booking_form_reserved_fields = __esm({
23337
23387
  "selectedDate",
23338
23388
  "selectedSlot",
23339
23389
  "discountCode",
23390
+ "selectedSlidingAmountInput",
23340
23391
  "selectedCollectionOptionKind",
23341
23392
  "selectedAppointmentPackageId"
23342
23393
  ];
@@ -23622,6 +23673,32 @@ function readOptionalString(value) {
23622
23673
  function readOptionalNumber(value) {
23623
23674
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
23624
23675
  }
23676
+ function getBookingServiceSlidingScale(service) {
23677
+ const pricing = service?.pricing;
23678
+ if (!pricing || pricing.pricingStrategy !== "sliding_scale" || pricing.slidingMinAmount === null || pricing.slidingMinAmount === void 0 || pricing.slidingMaxAmount === null || pricing.slidingMaxAmount === void 0) {
23679
+ return null;
23680
+ }
23681
+ return {
23682
+ minAmount: pricing.slidingMinAmount,
23683
+ maxAmount: pricing.slidingMaxAmount,
23684
+ suggestedAmount: pricing.slidingSuggestedAmount ?? null,
23685
+ currency: pricing.currency
23686
+ };
23687
+ }
23688
+ function parseBookingCurrencyInputToCents(value) {
23689
+ const trimmed = value?.trim() ?? "";
23690
+ if (!trimmed) return null;
23691
+ if (!/^(?:\d+(?:\.\d{0,2})?|\.\d{1,2})$/.test(trimmed)) return null;
23692
+ const parsed = Number(trimmed);
23693
+ if (!Number.isFinite(parsed) || parsed < 0) return null;
23694
+ return Math.round(parsed * 100);
23695
+ }
23696
+ function formatBookingCentsAsCurrencyInput(amount) {
23697
+ return amount % 100 === 0 ? String(amount / 100) : (amount / 100).toFixed(2);
23698
+ }
23699
+ function isBookingServiceSlidingAmountInRange(slidingScale, selectedAmount) {
23700
+ return selectedAmount !== null && selectedAmount >= slidingScale.minAmount && selectedAmount <= slidingScale.maxAmount;
23701
+ }
23625
23702
  function readStringArray(value) {
23626
23703
  if (!Array.isArray(value)) return void 0;
23627
23704
  const strings = value.filter((item) => typeof item === "string");
@@ -23810,6 +23887,20 @@ function prepareBookingSubmission(input) {
23810
23887
  };
23811
23888
  }
23812
23889
  const paymentSelectionMode = getBookingServicePaymentSelectionMode(selectedService);
23890
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
23891
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
23892
+ input.data.selectedSlidingAmountInput
23893
+ );
23894
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(slidingScale, selectedSlidingAmount)) {
23895
+ return {
23896
+ ok: false,
23897
+ error: {
23898
+ tag: selectedSlidingAmount === null ? "sliding-amount-required" : "sliding-amount-out-of-range",
23899
+ serviceId,
23900
+ serviceTitle: selectedService.title || serviceId
23901
+ }
23902
+ };
23903
+ }
23813
23904
  if (paymentSelectionMode === "choice_required" && !toSelectedCollectionOptionKind(input.data.selectedCollectionOptionKind)) {
23814
23905
  return {
23815
23906
  ok: false,
@@ -23837,6 +23928,7 @@ function prepareBookingSubmission(input) {
23837
23928
  ...common,
23838
23929
  kind: "standard",
23839
23930
  ...discountCode ? { discountCode } : {},
23931
+ ...slidingScale && selectedSlidingAmount !== null ? { selectedSlidingAmount } : {},
23840
23932
  ...selectedCollectionOptionKind ? { selectedCollectionOptionKind } : {},
23841
23933
  altchaSolution: input.altchaSolution ?? null
23842
23934
  }
@@ -23850,6 +23942,10 @@ function formatBookingSubmissionPreparationError(error) {
23850
23942
  return `Service not found: ${error.serviceId}`;
23851
23943
  case "payment-option-required":
23852
23944
  return `Please choose how you want to pay for "${error.serviceTitle}".`;
23945
+ case "sliding-amount-required":
23946
+ return `Please choose an amount for "${error.serviceTitle}".`;
23947
+ case "sliding-amount-out-of-range":
23948
+ return `Please choose an amount within the sliding scale for "${error.serviceTitle}".`;
23853
23949
  case "payment-option-unsupported":
23854
23950
  return `Service "${error.serviceTitle}" has a payment option that is not supported yet.`;
23855
23951
  case "service-missing-duration":
@@ -24000,6 +24096,7 @@ var init_booking_form_interactive = __esm({
24000
24096
  title: z36.string(),
24001
24097
  description: z36.string().nullable().optional().transform((value) => value ?? void 0),
24002
24098
  durationMinutes: z36.number().optional(),
24099
+ pricing: z36.custom().optional(),
24003
24100
  priceAmount: z36.number().nullable().optional(),
24004
24101
  priceCurrency: z36.string().nullable().optional(),
24005
24102
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
@@ -24439,7 +24536,7 @@ function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
24439
24536
  if (view === "week") {
24440
24537
  const weekStart = days[0];
24441
24538
  const weekEnd = days[6];
24442
- if (!weekStart || !weekEnd) {
24539
+ if (weekStart === void 0 || weekEnd === void 0) {
24443
24540
  return `${monthNames[viewDate.getMonth()] ?? ""} ${viewDate.getFullYear()}`;
24444
24541
  }
24445
24542
  const startMonth = monthNames[weekStart.getMonth()] ?? "";
@@ -24571,7 +24668,9 @@ function getLocalDateKey(isoTimestamp, timeZone) {
24571
24668
  const year = parts.find((part) => part.type === "year")?.value;
24572
24669
  const month = parts.find((part) => part.type === "month")?.value;
24573
24670
  const day = parts.find((part) => part.type === "day")?.value;
24574
- if (!year || !month || !day) return null;
24671
+ if (year === void 0 || year.length === 0) return null;
24672
+ if (month === void 0 || month.length === 0) return null;
24673
+ if (day === void 0 || day.length === 0) return null;
24575
24674
  return `${year}-${month}-${day}`;
24576
24675
  }
24577
24676
  function isValidTimeZone2(timeZone) {
@@ -24828,7 +24927,7 @@ function enumerateDateKeys(startDateKey, endDateKey) {
24828
24927
  }
24829
24928
  function parseDateKey(dateKey) {
24830
24929
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
24831
- if (!match) {
24930
+ if (match === null) {
24832
24931
  throw new Error(`Invalid timetable date key: ${dateKey}`);
24833
24932
  }
24834
24933
  const [, year, month, day] = match;
@@ -24864,7 +24963,7 @@ function groupItemsByDate(items, accessors) {
24864
24963
  const dateKey = getLocalDateKey(startsAt, timeZone);
24865
24964
  if (dateKey == null) continue;
24866
24965
  const existing = grouped.get(dateKey);
24867
- if (existing) {
24966
+ if (existing !== void 0) {
24868
24967
  existing.push(item);
24869
24968
  } else {
24870
24969
  grouped.set(dateKey, [item]);
@@ -24986,7 +25085,7 @@ function isRecord6(value) {
24986
25085
  }
24987
25086
  function isPublicEventPricing(value) {
24988
25087
  if (!isRecord6(value) || typeof value.kind !== "string") return false;
24989
- return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "ticket_types" || value.kind === "course" || value.kind === "external";
25088
+ 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";
24990
25089
  }
24991
25090
  function isPublicEventTicketType(value) {
24992
25091
  return isRecord6(value) && typeof value.id === "string";
@@ -33590,12 +33689,13 @@ var init_carousel_client = __esm({
33590
33689
  ]
33591
33690
  );
33592
33691
  const resolvedSlides = resolveSlidesToShowMobile(normalized.slidesToShow);
33692
+ const canAutoplay = normalized.autoplay && slides.length > 1;
33593
33693
  const cssVars = React5.useMemo(
33594
33694
  () => getCarouselSlidesToShowCssVars(normalized.slidesToShow),
33595
33695
  [normalized.slidesToShow]
33596
33696
  );
33597
33697
  const plugins = React5.useMemo(() => {
33598
- if (!normalized.autoplay) return void 0;
33698
+ if (!canAutoplay) return void 0;
33599
33699
  return [
33600
33700
  Autoplay({
33601
33701
  delay: normalized.autoplayDelay,
@@ -33605,7 +33705,7 @@ var init_carousel_client = __esm({
33605
33705
  // Continue after manual navigation
33606
33706
  })
33607
33707
  ];
33608
- }, [normalized.autoplay, normalized.autoplayDelay]);
33708
+ }, [canAutoplay, normalized.autoplayDelay]);
33609
33709
  const [viewportRef, api] = useEmblaCarousel(
33610
33710
  {
33611
33711
  align: "start",
@@ -33644,7 +33744,7 @@ var init_carousel_client = __esm({
33644
33744
  };
33645
33745
  }, [api]);
33646
33746
  React5.useEffect(() => {
33647
- if (!api || !normalized.autoplay) return;
33747
+ if (!api || !canAutoplay) return;
33648
33748
  const autoplayPlugin = api.plugins()?.autoplay;
33649
33749
  if (!autoplayPlugin) return;
33650
33750
  const handleVisibility = () => {
@@ -33653,7 +33753,7 @@ var init_carousel_client = __esm({
33653
33753
  };
33654
33754
  document.addEventListener("visibilitychange", handleVisibility);
33655
33755
  return () => document.removeEventListener("visibilitychange", handleVisibility);
33656
- }, [api, normalized.autoplay]);
33756
+ }, [api, canAutoplay]);
33657
33757
  const inner = /* @__PURE__ */ jsxs13(Fragment3, { children: [
33658
33758
  /* @__PURE__ */ jsx15("div", { className: "carousel-frame rb-overflow-hidden rb-w-full", children: /* @__PURE__ */ jsx15(
33659
33759
  "div",
@@ -33704,7 +33804,7 @@ var init_carousel_client = __esm({
33704
33804
  "data-show-controls": normalized.showControls ? "true" : "false",
33705
33805
  "data-show-dots": normalized.showDots ? "true" : "false",
33706
33806
  "data-arrow-visibility": normalized.arrowVisibility,
33707
- "data-autoplay": normalized.autoplay ? "true" : "false",
33807
+ "data-autoplay": canAutoplay ? "true" : "false",
33708
33808
  "data-loop": normalized.loop ? "true" : "false",
33709
33809
  children: inner
33710
33810
  }
@@ -34707,6 +34807,7 @@ function createBlockApi(config) {
34707
34807
  body: {
34708
34808
  courseId: data.courseId,
34709
34809
  ...data.pricingOptionId ? { pricingOptionId: data.pricingOptionId } : {},
34810
+ ...data.selectedSlidingAmount !== void 0 ? { selectedSlidingAmount: data.selectedSlidingAmount } : {},
34710
34811
  email: data.email,
34711
34812
  name: data.name,
34712
34813
  phone: data.phone,
@@ -37887,22 +37988,22 @@ function AltchaWidget({
37887
37988
  if (typeof window === "undefined") return;
37888
37989
  let widget = null;
37889
37990
  Promise.resolve().then(() => (init_altcha(), altcha_exports)).then(() => {
37890
- if (!containerRef.current) return;
37991
+ if (containerRef.current === null) return;
37891
37992
  widget = document.createElement("altcha-widget");
37892
37993
  widget.setAttribute("challengeurl", challengeUrl);
37893
- if (auto) {
37994
+ if (auto === true) {
37894
37995
  widget.setAttribute("auto", "onload");
37895
37996
  }
37896
- if (hidelogo) {
37997
+ if (hidelogo === true) {
37897
37998
  widget.setAttribute("hidelogo", "true");
37898
37999
  }
37899
38000
  widget.setAttribute("hidefooter", "true");
37900
- if (className) {
38001
+ if (className !== void 0 && className.length > 0) {
37901
38002
  widget.className = className;
37902
38003
  }
37903
38004
  widget.addEventListener("statechange", ((event) => {
37904
38005
  const { state, payload } = event.detail;
37905
- if (state === "verified" && payload) {
38006
+ if (state === "verified" && payload !== void 0 && payload.length > 0) {
37906
38007
  onSolutionChange?.(payload);
37907
38008
  } else if (state === "error" || state === "expired") {
37908
38009
  onSolutionChange?.(null);
@@ -37913,7 +38014,7 @@ function AltchaWidget({
37913
38014
  console.error("Failed to load ALTCHA widget:", err4);
37914
38015
  });
37915
38016
  return () => {
37916
- if (widget && containerRef.current) {
38017
+ if (widget !== null && containerRef.current !== null) {
37917
38018
  containerRef.current.removeChild(widget);
37918
38019
  }
37919
38020
  };
@@ -37959,15 +38060,15 @@ var init_dist2 = __esm({
37959
38060
  // ../altcha/src/index.ts
37960
38061
  function getCmsApiUrl() {
37961
38062
  const env = process.env.RIVERBANK_ENV;
37962
- if (env) {
38063
+ if (env !== void 0 && env.length > 0) {
37963
38064
  const envKey = `RIVERBANK_${env.toUpperCase()}_DASHBOARD_URL`;
37964
38065
  const dashboardUrl2 = process.env[envKey];
37965
- if (dashboardUrl2) {
38066
+ if (dashboardUrl2 !== void 0 && dashboardUrl2.length > 0) {
37966
38067
  return `${dashboardUrl2.replace(/\/$/, "")}/api`;
37967
38068
  }
37968
38069
  }
37969
38070
  const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;
37970
- if (dashboardUrl) {
38071
+ if (dashboardUrl !== void 0 && dashboardUrl.length > 0) {
37971
38072
  return `${dashboardUrl.replace(/\/$/, "")}/api`;
37972
38073
  }
37973
38074
  throw new Error("Dashboard URL not configured. Set NEXT_PUBLIC_DASHBOARD_URL");
@@ -38944,6 +39045,7 @@ function useBookingSubmission(_siteId) {
38944
39045
  customFields: data.customFields,
38945
39046
  timezone: data.timezone,
38946
39047
  discountCode: data.discountCode,
39048
+ selectedSlidingAmount: data.selectedSlidingAmount,
38947
39049
  selectedCollectionOptionKind: data.selectedCollectionOptionKind,
38948
39050
  altchaSolution: data.altchaSolution ?? null,
38949
39051
  successUrl: data.successUrl,
@@ -39472,6 +39574,9 @@ function createInitialBookingFlowReducerState(input) {
39472
39574
  fieldValues: collectInitialFieldValues(initialData),
39473
39575
  touchedFields: {},
39474
39576
  discountCode: toOptionalString(initialData.discountCode) ?? "",
39577
+ selectedSlidingAmountInput: toOptionalString(
39578
+ initialData.selectedSlidingAmountInput
39579
+ ) ?? "",
39475
39580
  selectedCollectionOptionKind: toSelectedCollectionOptionKind(
39476
39581
  initialData.selectedCollectionOptionKind
39477
39582
  ),
@@ -39524,6 +39629,7 @@ function bookingFlowReducer(state, event) {
39524
39629
  selectedDate: void 0,
39525
39630
  selectedSlot: null,
39526
39631
  selectedAppointmentPackageId: void 0,
39632
+ selectedSlidingAmountInput: "",
39527
39633
  selectedCollectionOptionKind: void 0,
39528
39634
  validationErrors: clearBookingFormError(state.validationErrors)
39529
39635
  };
@@ -39577,6 +39683,13 @@ function bookingFlowReducer(state, event) {
39577
39683
  selectedAppointmentPackageId: void 0,
39578
39684
  validationErrors: clearBookingFormError(state.validationErrors)
39579
39685
  };
39686
+ case "sliding-amount-changed":
39687
+ return {
39688
+ ...state,
39689
+ selectedSlidingAmountInput: event.value,
39690
+ selectedAppointmentPackageId: void 0,
39691
+ validationErrors: clearBookingFormError(state.validationErrors)
39692
+ };
39580
39693
  case "appointment-package-selected":
39581
39694
  return {
39582
39695
  ...state,
@@ -39679,6 +39792,7 @@ function toBookingFormData(state) {
39679
39792
  selectedDate: state.selectedDate,
39680
39793
  selectedSlot: state.selectedSlot?.startAt,
39681
39794
  discountCode: state.discountCode || void 0,
39795
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput || void 0,
39682
39796
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
39683
39797
  selectedAppointmentPackageId: state.selectedAppointmentPackageId
39684
39798
  };
@@ -39696,7 +39810,8 @@ function buildBookingStepResolution(input) {
39696
39810
  input.form.settings?.serviceIds && input.form.settings.serviceIds.length > 1
39697
39811
  );
39698
39812
  const selectedService = paymentResolution.service;
39699
- const needsPayment = paymentResolution.mode !== "hidden" || Boolean(
39813
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
39814
+ const needsPayment = Boolean(selectedServiceSlidingScale) || paymentResolution.mode !== "hidden" || Boolean(
39700
39815
  input.enableAppointmentPackages && selectedService && (selectedService.priceAmount ?? 0) > 0
39701
39816
  );
39702
39817
  const hasFields = Boolean(input.form.schema?.fields.length);
@@ -39740,6 +39855,18 @@ function validateBookingStep(input) {
39740
39855
  services: input.services,
39741
39856
  defaultServiceId: input.defaultServiceId
39742
39857
  });
39858
+ const slidingScale = getBookingServiceSlidingScale(resolution.service);
39859
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(
39860
+ slidingScale,
39861
+ parseBookingCurrencyInputToCents(input.data.selectedSlidingAmountInput)
39862
+ )) {
39863
+ return {
39864
+ ok: false,
39865
+ errors: createFormBookingValidationError(
39866
+ "Please choose an amount within the sliding scale"
39867
+ )
39868
+ };
39869
+ }
39743
39870
  if (resolution.mode === "hidden" || resolution.mode === "disclosure_only") {
39744
39871
  return { ok: true };
39745
39872
  }
@@ -40138,6 +40265,7 @@ var init_useHeadlessBookingFlow_state = __esm({
40138
40265
  "../blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.ts"() {
40139
40266
  "use strict";
40140
40267
  init_booking_form_headless_state();
40268
+ init_booking_form_state();
40141
40269
  }
40142
40270
  });
40143
40271
 
@@ -40177,11 +40305,27 @@ function useHeadlessBookingFlow(options) {
40177
40305
  const selectedServiceId = state.selectedServiceId ?? defaultServiceId;
40178
40306
  const selectedResourceId = state.selectedResourceId ?? defaultResourceId;
40179
40307
  const selectedService = options.services.find((service) => service.id === selectedServiceId) ?? null;
40308
+ const lastSlidingServiceIdRef = useRef9(null);
40309
+ useEffect8(() => {
40310
+ const selectedId = selectedService?.id ?? null;
40311
+ if (lastSlidingServiceIdRef.current === selectedId) return;
40312
+ lastSlidingServiceIdRef.current = selectedId;
40313
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
40314
+ dispatch({
40315
+ type: "sliding-amount-changed",
40316
+ value: slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatBookingCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
40317
+ });
40318
+ }, [selectedService]);
40180
40319
  const hasPortalBearerAuth = (api.config.authOverrides?.portal ?? api.config.auth).type === "bearer";
40320
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
40321
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
40322
+ state.selectedSlidingAmountInput
40323
+ );
40324
+ const selectedAppointmentPriceAmount = selectedServiceSlidingScale && selectedSlidingAmount !== null ? selectedSlidingAmount : selectedService?.priceAmount ?? 0;
40181
40325
  const canLoadAppointmentPackages = Boolean(
40182
- options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && (selectedService.priceAmount ?? 0) > 0
40326
+ options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && !selectedServiceSlidingScale && (selectedService.priceAmount ?? 0) > 0
40183
40327
  );
40184
- const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
40328
+ const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || selectedServiceSlidingScale || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
40185
40329
  const data = useMemo6(() => toBookingFormData(state), [state]);
40186
40330
  const availabilityDatesQuery = useMemo6(
40187
40331
  () => buildBookingAvailabilityQuery({
@@ -40411,13 +40555,13 @@ function useHeadlessBookingFlow(options) {
40411
40555
  state.selectedSlot
40412
40556
  ]);
40413
40557
  const appointmentDiscountContext = useMemo6(
40414
- () => selectedService && (selectedService.priceAmount ?? 0) > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
40415
- [selectedService?.id, selectedService?.priceAmount]
40558
+ () => selectedService && selectedAppointmentPriceAmount > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
40559
+ [selectedAppointmentPriceAmount, selectedService?.id]
40416
40560
  );
40417
40561
  const discountUi = useDiscountCodeUi({
40418
40562
  api,
40419
40563
  discountContext: appointmentDiscountContext,
40420
- subtotalCents: selectedService?.priceAmount ?? 0
40564
+ subtotalCents: selectedAppointmentPriceAmount
40421
40565
  });
40422
40566
  const { submit, clearFeedback } = useBookingSubmission(options.siteId);
40423
40567
  const submittingRef = useRef9(false);
@@ -40560,6 +40704,7 @@ function useHeadlessBookingFlow(options) {
40560
40704
  type: "appointment-package-selected",
40561
40705
  packageId: packageId ? unsafeAsCustomerPassId(packageId) : null
40562
40706
  }),
40707
+ setSlidingAmountInput: (value) => dispatch({ type: "sliding-amount-changed", value }),
40563
40708
  setDiscountCode: (code) => {
40564
40709
  dispatch({ type: "discount-code-changed", code });
40565
40710
  latestRef.current.setDiscountCode(code);
@@ -40630,11 +40775,11 @@ function useHeadlessBookingFlow(options) {
40630
40775
  () => selectedService && paymentResolution.mode !== "unsupported" ? buildBookingPaymentSelectionViewModel({
40631
40776
  serviceTitle: selectedService.title,
40632
40777
  paymentTerms: selectedService.paymentTerms,
40633
- priceAmount: selectedService.priceAmount,
40778
+ priceAmount: selectedAppointmentPriceAmount,
40634
40779
  priceCurrency: selectedService.priceCurrency,
40635
40780
  bookingStartAt: state.selectedSlot?.startAt
40636
40781
  }) : null,
40637
- [paymentResolution.mode, selectedService, state.selectedSlot?.startAt]
40782
+ [paymentResolution.mode, selectedAppointmentPriceAmount, selectedService, state.selectedSlot?.startAt]
40638
40783
  );
40639
40784
  const appointmentPackageItems = appointmentPackages.tag === "loaded" ? appointmentPackages.value : [];
40640
40785
  const selectedAppointmentPackage = appointmentPackageItems.find(
@@ -40663,6 +40808,9 @@ function useHeadlessBookingFlow(options) {
40663
40808
  selection: null,
40664
40809
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
40665
40810
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
40811
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
40812
+ selectedSlidingAmount,
40813
+ slidingScale: selectedServiceSlidingScale,
40666
40814
  appointmentPackages: appointmentPackagesModel,
40667
40815
  errorMessage: paymentResolution.errorMessage ?? "This service cannot be booked right now."
40668
40816
  } : {
@@ -40671,6 +40819,9 @@ function useHeadlessBookingFlow(options) {
40671
40819
  selection: paymentSelection,
40672
40820
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
40673
40821
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
40822
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
40823
+ selectedSlidingAmount,
40824
+ slidingScale: selectedServiceSlidingScale,
40674
40825
  appointmentPackages: appointmentPackagesModel
40675
40826
  };
40676
40827
  return {
@@ -41365,6 +41516,9 @@ var init_FieldValidationMessage = __esm({
41365
41516
 
41366
41517
  // ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
41367
41518
  import { Fragment as Fragment5, jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
41519
+ function formatBookingAmount(amount, currency) {
41520
+ return formatCurrencyAmount(amount, currency, { decimals: "auto" });
41521
+ }
41368
41522
  function HeadlessBookingDefaultForm({
41369
41523
  booking,
41370
41524
  onSubmit
@@ -41641,6 +41795,7 @@ function HeadlessBookingPaymentSelectionStep({
41641
41795
  }
41642
41796
  const packageOptions = booking.payment.appointmentPackages.items;
41643
41797
  const hasPackageOptions = packageOptions.length > 0;
41798
+ const slidingScale = booking.payment.slidingScale;
41644
41799
  return /* @__PURE__ */ jsxs21("div", { className: "ab-stack", children: [
41645
41800
  /* @__PURE__ */ jsxs21("div", { className: "ab-info-box", children: [
41646
41801
  /* @__PURE__ */ jsx27("h3", { className: "ab-subtitle", children: viewModel.sectionTitle }),
@@ -41651,6 +41806,28 @@ function HeadlessBookingPaymentSelectionStep({
41651
41806
  booking.payment.appointmentPackages.state.message,
41652
41807
  " You can continue with the normal payment option."
41653
41808
  ] }) : null,
41809
+ slidingScale ? /* @__PURE__ */ jsxs21("div", { className: "rb-space-y-2", children: [
41810
+ /* @__PURE__ */ jsx27("label", { htmlFor: "appointment-sliding-amount", className: "form-label", children: "Choose your price" }),
41811
+ /* @__PURE__ */ jsxs21("p", { className: "rb-caption status-muted", children: [
41812
+ formatBookingAmount(slidingScale.minAmount, slidingScale.currency),
41813
+ " to ",
41814
+ formatBookingAmount(slidingScale.maxAmount, slidingScale.currency),
41815
+ slidingScale.suggestedAmount !== null ? `, suggested ${formatBookingAmount(slidingScale.suggestedAmount, slidingScale.currency)}` : ""
41816
+ ] }),
41817
+ /* @__PURE__ */ jsx27(
41818
+ "input",
41819
+ {
41820
+ id: "appointment-sliding-amount",
41821
+ type: "number",
41822
+ className: "form-input",
41823
+ min: slidingScale.minAmount / 100,
41824
+ max: slidingScale.maxAmount / 100,
41825
+ step: "0.01",
41826
+ value: booking.payment.selectedSlidingAmountInput,
41827
+ onChange: (event) => booking.actions.setSlidingAmountInput(event.target.value)
41828
+ }
41829
+ )
41830
+ ] }) : null,
41654
41831
  /* @__PURE__ */ jsxs21("div", { className: "ab-stack", role: "list", "aria-label": "Payment options", children: [
41655
41832
  packageOptions.map((appointmentPackage) => {
41656
41833
  const isSelected = booking.payment.selectedAppointmentPackageId === appointmentPackage.customerPassId;
@@ -43497,6 +43674,7 @@ function useEligibility(params) {
43497
43674
  occurrenceId: params.occurrenceId,
43498
43675
  attendanceMode: params.attendanceMode ?? void 0,
43499
43676
  ticketTypeId: params.ticketTypeId ?? void 0,
43677
+ selectedSlidingAmount: params.selectedSlidingAmount ?? void 0,
43500
43678
  email: params.email.trim().toLowerCase(),
43501
43679
  ticketsCount: params.ticketsCount
43502
43680
  });
@@ -43517,7 +43695,7 @@ function useEligibility(params) {
43517
43695
  return () => {
43518
43696
  cancelled = true;
43519
43697
  };
43520
- }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.email, params.ticketsCount]);
43698
+ }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.selectedSlidingAmount, params.email, params.ticketsCount]);
43521
43699
  return {
43522
43700
  state,
43523
43701
  eligibility: state.eligibility,
@@ -43868,6 +44046,41 @@ function TicketCounterLimit(props2) {
43868
44046
  ] }) : null
43869
44047
  ] });
43870
44048
  }
44049
+ function formatCurrencyInputAmount(amount) {
44050
+ const major = amount / 100;
44051
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
44052
+ }
44053
+ function SlidingScaleAmountField(props2) {
44054
+ const minLabel = formatPrice(props2.slidingScale.minAmount, props2.currency);
44055
+ const maxLabel = formatPrice(props2.slidingScale.maxAmount, props2.currency);
44056
+ const suggestedLabel = props2.slidingScale.suggestedAmount !== null ? formatPrice(props2.slidingScale.suggestedAmount, props2.currency) : null;
44057
+ return /* @__PURE__ */ jsxs28("div", { className: "er-field", children: [
44058
+ /* @__PURE__ */ jsx42("label", { htmlFor: "er-sliding-amount", className: "er-label", children: "Amount" }),
44059
+ /* @__PURE__ */ jsx42(
44060
+ "input",
44061
+ {
44062
+ id: "er-sliding-amount",
44063
+ type: "number",
44064
+ min: formatCurrencyInputAmount(props2.slidingScale.minAmount),
44065
+ max: formatCurrencyInputAmount(props2.slidingScale.maxAmount),
44066
+ step: "0.01",
44067
+ inputMode: "decimal",
44068
+ className: "er-input",
44069
+ value: props2.value,
44070
+ onChange: (event) => props2.onChange?.(event.target.value)
44071
+ }
44072
+ ),
44073
+ /* @__PURE__ */ jsxs28("p", { className: "er-text er-text--muted", children: [
44074
+ "Choose between ",
44075
+ minLabel,
44076
+ " and ",
44077
+ maxLabel,
44078
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
44079
+ "."
44080
+ ] }),
44081
+ props2.message ? /* @__PURE__ */ jsx42("p", { className: "er-text er-text--muted", children: props2.message }) : null
44082
+ ] });
44083
+ }
43871
44084
  function getTicketTypeCounterAvailabilityLabel(input) {
43872
44085
  if (input.selectedRemainingCapacity !== null) {
43873
44086
  return `${input.selectedRemainingCapacity} remaining for this ticket type`;
@@ -43939,6 +44152,10 @@ var init_TicketSelectionStep = __esm({
43939
44152
  maxTickets,
43940
44153
  selectedTicketTypeId = null,
43941
44154
  onTicketTypeChange,
44155
+ slidingScale = null,
44156
+ selectedSlidingAmountInput = "",
44157
+ slidingScaleSelectionMessage = null,
44158
+ onSlidingAmountChange,
43942
44159
  onTicketsChange
43943
44160
  }) => {
43944
44161
  const deliveryMode = event?.deliveryMode ?? "in_person";
@@ -44054,6 +44271,16 @@ var init_TicketSelectionStep = __esm({
44054
44271
  ] })
44055
44272
  ] })
44056
44273
  ] }) : /* @__PURE__ */ jsxs28(Fragment8, { children: [
44274
+ slidingScale ? /* @__PURE__ */ jsx42(
44275
+ SlidingScaleAmountField,
44276
+ {
44277
+ slidingScale,
44278
+ currency: eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2,
44279
+ value: selectedSlidingAmountInput,
44280
+ message: slidingScaleSelectionMessage,
44281
+ onChange: onSlidingAmountChange
44282
+ }
44283
+ ) : null,
44057
44284
  /* @__PURE__ */ jsx42("h3", { className: "er-title", children: canJoinWaitlist ? "How many waitlist spots?" : "How many tickets?" }),
44058
44285
  /* @__PURE__ */ jsxs28("div", { className: "er-ticket-counter-group", children: [
44059
44286
  /* @__PURE__ */ jsx42(
@@ -45068,6 +45295,10 @@ function EventRegistrationWizardContent(props2) {
45068
45295
  maxTickets: wizard.maxTicketsNum,
45069
45296
  selectedTicketTypeId: wizard.selectedTicketTypeId,
45070
45297
  onTicketTypeChange: wizard.setSelectedTicketTypeId,
45298
+ slidingScale: wizard.selectedEventSlidingScale,
45299
+ selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
45300
+ slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
45301
+ onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
45071
45302
  onTicketsChange: wizard.setTicketsCount
45072
45303
  }
45073
45304
  )
@@ -45284,7 +45515,7 @@ function EventRegistrationSummaryBar(props2) {
45284
45515
  const summaryTimeZone = wizard.occurrenceContext?.timeZone ?? summaryEvent?.timeZone ?? null;
45285
45516
  const occurrenceOverrideTitle = wizard.occurrenceContext?.overrides?.title;
45286
45517
  const summaryTitle = occurrenceOverrideTitle ?? summaryEvent?.title ?? "";
45287
- const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
45518
+ const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (wizard.selectedEventSlidingScale ? wizard.selectedSlidingAmount : null) ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
45288
45519
  const summaryCurrency = (summaryEvent ? publicEventPricingCurrency(summaryEvent.pricing) : null) ?? DEFAULT_EVENT_CURRENCY2;
45289
45520
  const teamMemberNames = (summaryEvent?.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
45290
45521
  const subtotalCents = Math.max(0, summaryPrice * wizard.ticketsCount);
@@ -45735,6 +45966,7 @@ function useEventRegistrationSubmit(options) {
45735
45966
  occurrenceId: submitOccurrenceId,
45736
45967
  attendanceMode: options.selectedAttendanceMode ?? void 0,
45737
45968
  ticketTypeId: options.selectedTicketTypeId ?? void 0,
45969
+ selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
45738
45970
  email: options.formData.email.trim().toLowerCase(),
45739
45971
  name: options.formData.name.trim(),
45740
45972
  ticketsCount: options.ticketsCount,
@@ -45844,6 +46076,35 @@ var init_useEventRegistrationSubmit = __esm({
45844
46076
 
45845
46077
  // ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.ts
45846
46078
  import { useCallback as useCallback8, useEffect as useEffect15, useMemo as useMemo10, useReducer as useReducer10, useState as useState7 } from "react";
46079
+ function parseCurrencyInputToCents(value) {
46080
+ const trimmed = value.trim();
46081
+ if (!trimmed) return null;
46082
+ const amount = Number(trimmed);
46083
+ if (!Number.isFinite(amount) || amount < 0) return null;
46084
+ return Math.round(amount * 100);
46085
+ }
46086
+ function formatCentsAsCurrencyInput(amount) {
46087
+ const major = amount / 100;
46088
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
46089
+ }
46090
+ function getSlidingScaleForEvent(event) {
46091
+ const pricing = event ? resolveRuntimeEventPricing(event) : null;
46092
+ return pricing?.kind === "sliding_scale" ? {
46093
+ minAmount: pricing.minAmount,
46094
+ maxAmount: pricing.maxAmount,
46095
+ suggestedAmount: pricing.suggestedAmount
46096
+ } : null;
46097
+ }
46098
+ function getSlidingScaleSelectionMessage(input) {
46099
+ if (!input.slidingScale) return null;
46100
+ if (input.selectedAmount === null) {
46101
+ return "Choose an amount to continue.";
46102
+ }
46103
+ if (input.selectedAmount < input.slidingScale.minAmount || input.selectedAmount > input.slidingScale.maxAmount) {
46104
+ return "Choose an amount within the sliding scale.";
46105
+ }
46106
+ return null;
46107
+ }
45847
46108
  function useEventRegistrationWizard(options) {
45848
46109
  const api = useBlockApi();
45849
46110
  const registrationEvents = useMemo10(
@@ -45888,6 +46149,10 @@ function useEventRegistrationWizard(options) {
45888
46149
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
45889
46150
  );
45890
46151
  const [selectedTicketTypeId, setSelectedTicketTypeId] = useState7(null);
46152
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState7(() => {
46153
+ const slidingScale = getSlidingScaleForEvent(preSelectedEvent ?? null);
46154
+ return slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : "";
46155
+ });
45891
46156
  const [registrationResult, setRegistrationResult] = useState7(null);
45892
46157
  const [errorMessage, setErrorMessage] = useState7("");
45893
46158
  const [altchaSolution, setAltchaSolution] = useState7(null);
@@ -45924,6 +46189,7 @@ function useEventRegistrationWizard(options) {
45924
46189
  occurrenceId,
45925
46190
  attendanceMode: selectedAttendanceMode,
45926
46191
  ticketTypeId: selectedTicketTypeId,
46192
+ selectedSlidingAmount: parseCurrencyInputToCents(selectedSlidingAmountInput),
45927
46193
  email: formData.email,
45928
46194
  ticketsCount
45929
46195
  });
@@ -46059,6 +46325,18 @@ function useEventRegistrationWizard(options) {
46059
46325
  }),
46060
46326
  [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility, selectedTicketTypeId]
46061
46327
  );
46328
+ const selectedSlidingAmount = useMemo10(
46329
+ () => parseCurrencyInputToCents(selectedSlidingAmountInput),
46330
+ [selectedSlidingAmountInput]
46331
+ );
46332
+ const selectedEventSlidingScale = useMemo10(
46333
+ () => getSlidingScaleForEvent(selectedEvent ?? preSelectedEvent),
46334
+ [selectedEvent, preSelectedEvent]
46335
+ );
46336
+ const slidingScaleSelectionMessage = getSlidingScaleSelectionMessage({
46337
+ slidingScale: selectedEventSlidingScale,
46338
+ selectedAmount: selectedSlidingAmount
46339
+ });
46062
46340
  const ticketTypeSelectionRequired = useMemo10(() => {
46063
46341
  const event = selectedEvent ?? preSelectedEvent;
46064
46342
  if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
@@ -46127,12 +46405,24 @@ function useEventRegistrationWizard(options) {
46127
46405
  setGuestSectionShake(false);
46128
46406
  };
46129
46407
  const eventRequiresPayment = useMemo10(
46130
- () => resolveEventRequiresPayment({
46408
+ () => {
46409
+ if (selectedEventSlidingScale) {
46410
+ return (selectedSlidingAmount ?? 0) > 0;
46411
+ }
46412
+ return resolveEventRequiresPayment({
46413
+ eligibility,
46414
+ selectedTicketType,
46415
+ event: selectedEvent ?? preSelectedEvent
46416
+ });
46417
+ },
46418
+ [
46131
46419
  eligibility,
46132
46420
  selectedTicketType,
46133
- event: selectedEvent ?? preSelectedEvent
46134
- }),
46135
- [eligibility, selectedTicketType, selectedEvent, preSelectedEvent]
46421
+ selectedEvent,
46422
+ preSelectedEvent,
46423
+ selectedEventSlidingScale,
46424
+ selectedSlidingAmount
46425
+ ]
46136
46426
  );
46137
46427
  const handleDetailsComplete = async () => {
46138
46428
  if (ticketTypeSelectionRequired && !hasValidSelectedTicketType) {
@@ -46141,6 +46431,11 @@ function useEventRegistrationWizard(options) {
46141
46431
  setStep("error");
46142
46432
  return { ok: false, reason: "blocked", message };
46143
46433
  }
46434
+ if (slidingScaleSelectionMessage) {
46435
+ setErrorMessage(slidingScaleSelectionMessage);
46436
+ setStep("error");
46437
+ return { ok: false, reason: "blocked", message: slidingScaleSelectionMessage };
46438
+ }
46144
46439
  if (!validateForm()) {
46145
46440
  setGuestSectionShake(true);
46146
46441
  return {
@@ -46225,9 +46520,18 @@ function useEventRegistrationWizard(options) {
46225
46520
  const discountSubtotalCents = useMemo10(() => {
46226
46521
  if (discountContext?.type !== "event") return void 0;
46227
46522
  const event = selectedEvent ?? preSelectedEvent;
46228
- const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
46523
+ const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (selectedEventSlidingScale ? selectedSlidingAmount : null) ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
46229
46524
  return Math.max(0, eventPriceCents * ticketsCount);
46230
- }, [discountContext, selectedTicketType, eligibility, selectedEvent, preSelectedEvent, ticketsCount]);
46525
+ }, [
46526
+ discountContext,
46527
+ selectedTicketType,
46528
+ eligibility,
46529
+ selectedEvent,
46530
+ preSelectedEvent,
46531
+ selectedEventSlidingScale,
46532
+ selectedSlidingAmount,
46533
+ ticketsCount
46534
+ ]);
46231
46535
  const discountUi = useDiscountCodeUi({ api, discountContext, subtotalCents: discountSubtotalCents });
46232
46536
  const selectionMethod = paymentSelection.method;
46233
46537
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
@@ -46236,6 +46540,7 @@ function useEventRegistrationWizard(options) {
46236
46540
  registrationTarget,
46237
46541
  selectedAttendanceMode,
46238
46542
  selectedTicketTypeId,
46543
+ selectedSlidingAmount,
46239
46544
  ticketsCount,
46240
46545
  formData,
46241
46546
  altchaSolution,
@@ -46250,6 +46555,10 @@ function useEventRegistrationWizard(options) {
46250
46555
  });
46251
46556
  const handleEventSelect = (event) => {
46252
46557
  setSelectedEvent(event);
46558
+ const slidingScale = getSlidingScaleForEvent(event);
46559
+ setSelectedSlidingAmountInput(
46560
+ slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
46561
+ );
46253
46562
  setSelectedAttendanceMode(
46254
46563
  (event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person"
46255
46564
  );
@@ -46259,6 +46568,7 @@ function useEventRegistrationWizard(options) {
46259
46568
  const handleRetry = () => {
46260
46569
  dispatchFlow({ type: "reset", hasPreSelectedContext });
46261
46570
  setSelectedTicketTypeId(null);
46571
+ setSelectedSlidingAmountInput("");
46262
46572
  setSelectedAttendanceMode(
46263
46573
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
46264
46574
  );
@@ -46295,6 +46605,11 @@ function useEventRegistrationWizard(options) {
46295
46605
  setSelectedAttendanceMode,
46296
46606
  selectedTicketTypeId,
46297
46607
  setSelectedTicketTypeId,
46608
+ selectedSlidingAmountInput,
46609
+ setSelectedSlidingAmountInput,
46610
+ selectedSlidingAmount,
46611
+ selectedEventSlidingScale,
46612
+ slidingScaleSelectionMessage,
46298
46613
  selectedTicketType,
46299
46614
  visibleTicketTypes,
46300
46615
  ticketTypeSelectionRequired,
@@ -47252,7 +47567,9 @@ function getSelectableCoursePricingOptionsForNow(course, now) {
47252
47567
  return left.name.localeCompare(right.name);
47253
47568
  });
47254
47569
  }
47255
- function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {}) {
47570
+ function resolveCoursePricingState(course, selectedPricingOptionId, selectedSlidingAmountOrNowInput, nowInput = {}) {
47571
+ const selectedSlidingAmount = typeof selectedSlidingAmountOrNowInput === "number" || selectedSlidingAmountOrNowInput === null ? selectedSlidingAmountOrNowInput : null;
47572
+ const effectiveNowInput = typeof selectedSlidingAmountOrNowInput === "object" && selectedSlidingAmountOrNowInput !== null ? selectedSlidingAmountOrNowInput : nowInput;
47256
47573
  const currency = course.pricing?.currency ?? course.currency;
47257
47574
  if (!course.pricing) {
47258
47575
  const legacyAmount = course.priceCents ?? 0;
@@ -47262,6 +47579,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47262
47579
  selectableOptions: [],
47263
47580
  requiresSelection: false,
47264
47581
  selectedOption: null,
47582
+ slidingScale: null,
47583
+ selectedSlidingAmount: null,
47265
47584
  selectedPriceAmount: legacyAmount,
47266
47585
  selectedRequiresPayment: legacyAmount > 0,
47267
47586
  hasPaidSelection: legacyAmount > 0,
@@ -47276,6 +47595,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47276
47595
  selectableOptions: [],
47277
47596
  requiresSelection: false,
47278
47597
  selectedOption: null,
47598
+ slidingScale: null,
47599
+ selectedSlidingAmount: null,
47279
47600
  selectedPriceAmount: 0,
47280
47601
  selectedRequiresPayment: false,
47281
47602
  hasPaidSelection: false,
@@ -47289,6 +47610,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47289
47610
  selectableOptions: [],
47290
47611
  requiresSelection: false,
47291
47612
  selectedOption: null,
47613
+ slidingScale: null,
47614
+ selectedSlidingAmount: null,
47292
47615
  selectedPriceAmount: amount,
47293
47616
  selectedRequiresPayment: amount > 0,
47294
47617
  hasPaidSelection: amount > 0,
@@ -47298,7 +47621,7 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47298
47621
  case "tiered": {
47299
47622
  const selectableOptions = getSelectableCoursePricingOptionsForNow(
47300
47623
  course,
47301
- resolvePlanningDateInput(nowInput)
47624
+ resolvePlanningDateInput(effectiveNowInput)
47302
47625
  );
47303
47626
  const selectedOption = (selectedPricingOptionId ? selectableOptions.find((option) => option.id === selectedPricingOptionId) : null) ?? (selectableOptions.length === 1 ? selectableOptions[0] ?? null : null);
47304
47627
  const lowestAmount = selectableOptions.reduce((lowest, option) => {
@@ -47314,12 +47637,51 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
47314
47637
  selectableOptions,
47315
47638
  requiresSelection: selectableOptions.length > 1,
47316
47639
  selectedOption,
47640
+ slidingScale: null,
47641
+ selectedSlidingAmount: null,
47317
47642
  selectedPriceAmount: selectedOption?.priceAmount ?? null,
47318
47643
  selectedRequiresPayment: (selectedOption?.priceAmount ?? 0) > 0,
47319
47644
  hasPaidSelection: selectableOptions.some((option) => option.priceAmount > 0),
47320
47645
  hasFreeSelection: selectableOptions.some((option) => option.priceAmount === 0)
47321
47646
  };
47322
47647
  }
47648
+ case "sliding_scale": {
47649
+ const minAmount = course.pricing.slidingMinAmount ?? null;
47650
+ const maxAmount = course.pricing.slidingMaxAmount ?? null;
47651
+ if (minAmount === null || maxAmount === null || minAmount < 0 || maxAmount < minAmount) {
47652
+ return {
47653
+ currency,
47654
+ display: { kind: "unavailable", currency },
47655
+ selectableOptions: [],
47656
+ requiresSelection: true,
47657
+ selectedOption: null,
47658
+ slidingScale: null,
47659
+ selectedSlidingAmount: null,
47660
+ selectedPriceAmount: null,
47661
+ selectedRequiresPayment: false,
47662
+ hasPaidSelection: false,
47663
+ hasFreeSelection: false
47664
+ };
47665
+ }
47666
+ const normalizedSelectedAmount = typeof selectedSlidingAmount === "number" && Number.isFinite(selectedSlidingAmount) ? selectedSlidingAmount : null;
47667
+ return {
47668
+ currency,
47669
+ display: { kind: "range", minAmount, maxAmount, currency },
47670
+ selectableOptions: [],
47671
+ requiresSelection: normalizedSelectedAmount === null,
47672
+ selectedOption: null,
47673
+ slidingScale: {
47674
+ minAmount,
47675
+ maxAmount,
47676
+ suggestedAmount: course.pricing.slidingSuggestedAmount ?? null
47677
+ },
47678
+ selectedSlidingAmount: normalizedSelectedAmount,
47679
+ selectedPriceAmount: normalizedSelectedAmount,
47680
+ selectedRequiresPayment: (normalizedSelectedAmount ?? 0) > 0,
47681
+ hasPaidSelection: maxAmount > 0,
47682
+ hasFreeSelection: minAmount === 0
47683
+ };
47684
+ }
47323
47685
  }
47324
47686
  }
47325
47687
  function formatCoursePriceDisplay(display, locale) {
@@ -51511,6 +51873,44 @@ function CoursePricingSelector({
51511
51873
  )
51512
51874
  ] });
51513
51875
  }
51876
+ function CourseSlidingScaleInput({
51877
+ pricing,
51878
+ value,
51879
+ onChange
51880
+ }) {
51881
+ const slidingScale = pricing.slidingScale;
51882
+ if (!slidingScale) {
51883
+ return null;
51884
+ }
51885
+ const minLabel = formatCoursePriceAmount(slidingScale.minAmount, pricing.currency);
51886
+ const maxLabel = formatCoursePriceAmount(slidingScale.maxAmount, pricing.currency);
51887
+ const suggestedLabel = slidingScale.suggestedAmount !== null ? formatCoursePriceAmount(slidingScale.suggestedAmount, pricing.currency) : null;
51888
+ return /* @__PURE__ */ jsxs65("div", { className: "cr-form__field", children: [
51889
+ /* @__PURE__ */ jsx95("label", { htmlFor: "cr-sliding-amount", className: "cr-form__label", children: "Amount" }),
51890
+ /* @__PURE__ */ jsx95(
51891
+ "input",
51892
+ {
51893
+ id: "cr-sliding-amount",
51894
+ type: "number",
51895
+ min: slidingScale.minAmount / 100,
51896
+ max: slidingScale.maxAmount / 100,
51897
+ step: "0.01",
51898
+ inputMode: "decimal",
51899
+ className: "cr-form__input",
51900
+ value,
51901
+ onChange: (event) => onChange(event.target.value)
51902
+ }
51903
+ ),
51904
+ /* @__PURE__ */ jsxs65("p", { className: "cr-text cr-text--muted", children: [
51905
+ "Choose between ",
51906
+ minLabel,
51907
+ " and ",
51908
+ maxLabel,
51909
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
51910
+ "."
51911
+ ] })
51912
+ ] });
51913
+ }
51514
51914
  function EnrollmentForm({ formData, errors, onChange, isEmailLocked, isLoading, onLogout }) {
51515
51915
  const nameErrorId = "cr-name-error";
51516
51916
  const emailErrorId = "cr-email-error";
@@ -51606,6 +52006,17 @@ var init_components2 = __esm({
51606
52006
 
51607
52007
  // ../blocks/src/system/runtime/nodes/course-registration/useCourseRegistrationWizard.ts
51608
52008
  import { useEffect as useEffect25, useMemo as useMemo19, useState as useState18 } from "react";
52009
+ function parseCurrencyInputToCents2(value) {
52010
+ const trimmed = value.trim();
52011
+ if (!trimmed) return null;
52012
+ const amount = Number(trimmed);
52013
+ if (!Number.isFinite(amount) || amount < 0) return null;
52014
+ return Math.round(amount * 100);
52015
+ }
52016
+ function formatCentsAsCurrencyInput2(amount) {
52017
+ const major = amount / 100;
52018
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
52019
+ }
51609
52020
  function shouldFetchCourseEligibility(course, hasPaidSelection, portalAuthState, hasWaitlistClaimToken) {
51610
52021
  if (!course || portalAuthState === "checking") return false;
51611
52022
  if (hasWaitlistClaimToken) return false;
@@ -51623,12 +52034,27 @@ function getSessionIdFromUrl() {
51623
52034
  }
51624
52035
  function getMissingCoursePricingSelectionMessage(pricing) {
51625
52036
  if (pricing?.requiresSelection && !pricing.selectedOption) {
52037
+ if (pricing.slidingScale) {
52038
+ return "Choose an amount to continue.";
52039
+ }
51626
52040
  return "Choose a pricing option to continue.";
51627
52041
  }
52042
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount < pricing.slidingScale.minAmount) {
52043
+ return "Choose an amount within the sliding scale.";
52044
+ }
52045
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount > pricing.slidingScale.maxAmount) {
52046
+ return "Choose an amount within the sliding scale.";
52047
+ }
51628
52048
  return null;
51629
52049
  }
51630
52050
  function buildCoursePricingOptionPayload(pricing) {
51631
- return pricing?.selectedOption ? { pricingOptionId: pricing.selectedOption.id } : {};
52051
+ if (pricing?.selectedOption) {
52052
+ return { pricingOptionId: pricing.selectedOption.id };
52053
+ }
52054
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null) {
52055
+ return { selectedSlidingAmount: pricing.selectedSlidingAmount };
52056
+ }
52057
+ return {};
51632
52058
  }
51633
52059
  function resolveHasCoursePayment(pricing) {
51634
52060
  if (!pricing) {
@@ -51669,6 +52095,7 @@ function useCourseRegistrationWizard(props2) {
51669
52095
  scopedCourses.length === 1 ? scopedCourses[0] ?? null : null
51670
52096
  );
51671
52097
  const [selectedPricingOptionId, setSelectedPricingOptionId] = useState18(null);
52098
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState18("");
51672
52099
  const [formData, setFormData] = useState18({
51673
52100
  name: "",
51674
52101
  email: "",
@@ -51690,8 +52117,12 @@ function useCourseRegistrationWizard(props2) {
51690
52117
  const [bookingMode, setBookingMode] = useState18(null);
51691
52118
  const waitlistClaim = useWaitlistClaim();
51692
52119
  const selectedCoursePricing = useMemo19(
51693
- () => selectedCourse ? resolveCoursePricingState(selectedCourse, selectedPricingOptionId) : null,
51694
- [selectedCourse, selectedPricingOptionId]
52120
+ () => selectedCourse ? resolveCoursePricingState(
52121
+ selectedCourse,
52122
+ selectedPricingOptionId,
52123
+ parseCurrencyInputToCents2(selectedSlidingAmountInput)
52124
+ ) : null,
52125
+ [selectedCourse, selectedPricingOptionId, selectedSlidingAmountInput]
51695
52126
  );
51696
52127
  const missingPricingSelectionMessage = getMissingCoursePricingSelectionMessage(
51697
52128
  selectedCoursePricing
@@ -51764,9 +52195,13 @@ function useCourseRegistrationWizard(props2) {
51764
52195
  useEffect25(() => {
51765
52196
  if (!selectedCourse) {
51766
52197
  setSelectedPricingOptionId(null);
52198
+ setSelectedSlidingAmountInput("");
51767
52199
  return;
51768
52200
  }
51769
52201
  const defaultPricing = resolveCoursePricingState(selectedCourse, null);
52202
+ setSelectedSlidingAmountInput(
52203
+ defaultPricing.slidingScale?.suggestedAmount !== null && defaultPricing.slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput2(defaultPricing.slidingScale.suggestedAmount) : ""
52204
+ );
51770
52205
  setSelectedPricingOptionId((previous) => {
51771
52206
  if (previous && defaultPricing.selectableOptions.some((option) => option.id === previous)) {
51772
52207
  return previous;
@@ -52141,6 +52576,8 @@ function useCourseRegistrationWizard(props2) {
52141
52576
  selectedCourse,
52142
52577
  selectedPricingOptionId,
52143
52578
  setSelectedPricingOptionId,
52579
+ selectedSlidingAmountInput,
52580
+ setSelectedSlidingAmountInput,
52144
52581
  selectedCoursePricing,
52145
52582
  setSelectedCourse,
52146
52583
  formData,
@@ -52206,6 +52643,19 @@ var init_useCourseRegistrationWizard = __esm({
52206
52643
 
52207
52644
  // ../blocks/src/system/runtime/nodes/course-registration/CourseRegistrationWizard.tsx
52208
52645
  import { Fragment as Fragment23, jsx as jsx96, jsxs as jsxs66 } from "react/jsx-runtime";
52646
+ function getCourseSlidingScaleMessage(pricing) {
52647
+ if (!pricing.slidingScale) {
52648
+ return null;
52649
+ }
52650
+ const selectedAmount = pricing.selectedSlidingAmount;
52651
+ if (selectedAmount === null) {
52652
+ return "Choose an amount to continue.";
52653
+ }
52654
+ if (selectedAmount < pricing.slidingScale.minAmount || selectedAmount > pricing.slidingScale.maxAmount) {
52655
+ return "Choose an amount within the sliding scale.";
52656
+ }
52657
+ return null;
52658
+ }
52209
52659
  function CourseRegistrationWizard(props2) {
52210
52660
  const {
52211
52661
  showSessions = true,
@@ -52227,6 +52677,8 @@ function CourseRegistrationWizard(props2) {
52227
52677
  selectedCourse,
52228
52678
  selectedPricingOptionId,
52229
52679
  setSelectedPricingOptionId,
52680
+ selectedSlidingAmountInput,
52681
+ setSelectedSlidingAmountInput,
52230
52682
  selectedCoursePricing,
52231
52683
  formData,
52232
52684
  setFormData,
@@ -52301,7 +52753,8 @@ function CourseRegistrationWizard(props2) {
52301
52753
  const canShowDiscountUi = !discountsAvailableLoading && paymentChoice === "checkout" && discountsAvailable && manualCodeEntryAllowed;
52302
52754
  const courseSoldOut = waitlistClaim.claim === null && courseEligibility?.isFull === true && courseEligibility.canJoinWaitlist !== true;
52303
52755
  const detailsSubmitLabel = courseEligibility?.canJoinWaitlist ? "Join waitlist" : buttonText;
52304
- const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption;
52756
+ const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption && !selectedCoursePricing.slidingScale;
52757
+ const slidingScaleMessage = selectedCoursePricing ? getCourseSlidingScaleMessage(selectedCoursePricing) : null;
52305
52758
  const pricingUnavailable = selectedCoursePricing?.display.kind === "unavailable";
52306
52759
  const selectedCourseWithPricing = selectedCourse && selectedCoursePricing ? {
52307
52760
  course: selectedCourse,
@@ -52394,6 +52847,14 @@ function CourseRegistrationWizard(props2) {
52394
52847
  onSelect: setSelectedPricingOptionId
52395
52848
  }
52396
52849
  ),
52850
+ /* @__PURE__ */ jsx96(
52851
+ CourseSlidingScaleInput,
52852
+ {
52853
+ pricing: selectedCourseWithPricing.pricing,
52854
+ value: selectedSlidingAmountInput,
52855
+ onChange: setSelectedSlidingAmountInput
52856
+ }
52857
+ ),
52397
52858
  showSpamProtection && /* @__PURE__ */ jsx96("div", { className: "cr-altcha", children: /* @__PURE__ */ jsx96(
52398
52859
  AltchaWidget,
52399
52860
  {
@@ -52431,7 +52892,7 @@ function CourseRegistrationWizard(props2) {
52431
52892
  {
52432
52893
  type: "submit",
52433
52894
  className: buttonClass,
52434
- disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || pricingUnavailable,
52895
+ disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || slidingScaleMessage !== null || pricingUnavailable,
52435
52896
  children: eligibilityLoading ? "Checking availability\u2026" : courseSoldOut ? "Sold out" : detailsSubmitLabel
52436
52897
  }
52437
52898
  )
@@ -52441,6 +52902,7 @@ function CourseRegistrationWizard(props2) {
52441
52902
  ),
52442
52903
  courseSoldOut && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--muted", children: "This course is fully booked and there is no waitlist available right now." }),
52443
52904
  pricingSelectionMissing && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--muted", children: "Choose a pricing option to continue." }),
52905
+ slidingScaleMessage && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--muted", children: slidingScaleMessage }),
52444
52906
  pricingUnavailable && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--muted", children: "Pricing is not available for this course right now." }),
52445
52907
  eligibilityError && /* @__PURE__ */ jsx96("p", { className: "cr-text cr-text--error", children: eligibilityError })
52446
52908
  ] })
@@ -56751,6 +57213,21 @@ function fieldPlanToZod(plan, options = {}) {
56751
57213
  }
56752
57214
  });
56753
57215
  }
57216
+ function fieldPresenceForPlan(plan) {
57217
+ return plan.required ? { kind: "required" } : { kind: "optional" };
57218
+ }
57219
+ function fieldPlanToObjectPropertyZod(plan, options = {}) {
57220
+ const schema = fieldPlanToZod(plan, options);
57221
+ const presence = fieldPresenceForPlan(plan);
57222
+ switch (presence.kind) {
57223
+ case "required":
57224
+ return schema;
57225
+ case "optional":
57226
+ return schema.optional();
57227
+ default:
57228
+ return assertNever7(presence);
57229
+ }
57230
+ }
56754
57231
  function addFieldIssueToZodContext(zodCtx, validationIssue, rootPath) {
56755
57232
  const message = fieldIssueToMessage(validationIssue);
56756
57233
  const path = relativeZodIssuePath(validationIssue.path, rootPath);
@@ -56963,7 +57440,7 @@ function createManifestValidator(manifestOrFields, options = {}) {
56963
57440
  function deriveContentZodFromPlans(plans, options) {
56964
57441
  const shape = {};
56965
57442
  for (const plan of plans) {
56966
- shape[plan.fieldId] = fieldPlanToZod(plan, options);
57443
+ shape[plan.fieldId] = fieldPlanToObjectPropertyZod(plan, options);
56967
57444
  }
56968
57445
  return z54.object(shape).catchall(z54.unknown()).passthrough();
56969
57446
  }
@@ -69057,11 +69534,11 @@ function extractRouteUpdatedAt(candidate) {
69057
69534
  }
69058
69535
  function resolveRouteTargetForStage(routeIdValue, routes, stageInput) {
69059
69536
  const routeId = typeof routeIdValue === "string" ? routeIdValue : null;
69060
- if (!routeId || !routes) return null;
69537
+ if (routeId === null || routeId.length === 0 || routes === void 0) return null;
69061
69538
  const routeEntry = routes[routeId];
69062
69539
  if (!isRouteVisibleInStage(routeEntry, stageInput)) return null;
69063
69540
  const href = extractRouteHref(routeEntry, stageInput);
69064
- if (!href) return null;
69541
+ if (href === null) return null;
69065
69542
  return {
69066
69543
  routeId,
69067
69544
  href,
@@ -97874,6 +98351,7 @@ var CANONICAL_REFUND_CONFLICT_CODES = [
97874
98351
  ];
97875
98352
  var CANONICAL_REFUND_PLANNER_ERROR_CODES = [
97876
98353
  ...CANONICAL_REFUND_CONFLICT_CODES,
98354
+ "invalid_amount",
97877
98355
  "amount_exceeds_remaining",
97878
98356
  "pass_revoke_requires_full_refund",
97879
98357
  "refund_failed"
@@ -97900,6 +98378,7 @@ var CUSTOMER_MEMBERSHIP_PAYMENT_REFUND_FAILED_CODES = [
97900
98378
  // ../api/src/bookingRefundOutcomes.ts
97901
98379
  var STANDARD_LINE_ITEM_REFUND_INVALID_CODES = [
97902
98380
  "missing_amount",
98381
+ "invalid_amount",
97903
98382
  "amount_exceeds_remaining",
97904
98383
  "credit_refunds_disabled",
97905
98384
  "missing_credit_customer",
@@ -97914,6 +98393,7 @@ var EVENT_ATTENDEE_REFUND_INVALID_CODES = [
97914
98393
  var MANAGED_PRODUCT_REFUND_INVALID_CODES = [
97915
98394
  "credit_refunds_disabled",
97916
98395
  "not_refundable",
98396
+ "invalid_amount",
97917
98397
  "amount_exceeds_remaining"
97918
98398
  ];
97919
98399
  var CUSTOMER_PASS_REFUND_INVALID_CODES = [