@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
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { ApiRequestError } from './chunk-BOQXMCSP.mjs';
2
+ import { ApiRequestError } from './chunk-ZA2S7H34.mjs';
3
3
  import { __commonJS, __export, __toESM } from './chunk-R7POPVJR.mjs';
4
4
  import * as React61 from 'react';
5
5
  import React61__default, { createContext, forwardRef, useContext, createElement, useRef, useMemo, useEffect, useState, useCallback, useImperativeHandle, useLayoutEffect, memo, createRef, Component as Component$1, useDebugValue, useId as useId$1, useSyncExternalStore, isValidElement, cloneElement, useReducer, startTransition } from 'react';
@@ -129,6 +129,8 @@ function expectParsedBrandedId(result) {
129
129
  }
130
130
  var unsafeAsSiteId = (id) => id;
131
131
  var unsafeAsMediaAssetId = (id) => id;
132
+ var unsafeAsStoragePath = (value) => value;
133
+ var unsafeAsPositiveByteCount = (value) => value;
132
134
  var unsafeAsFormId = (id) => id;
133
135
  var unsafeAsPassId = (id) => id;
134
136
  var unsafeAsCustomerPassId = (id) => id;
@@ -207,6 +209,7 @@ function publicEventPricingType(pricing) {
207
209
  return "free";
208
210
  case "single_paid":
209
211
  case "ticket_types":
212
+ case "sliding_scale":
210
213
  case "course":
211
214
  return isFreePublicEventPriceDisplay(pricing.display) ? "free" : "paid";
212
215
  case "external":
@@ -219,6 +222,7 @@ function publicEventPricingCurrency(pricing) {
219
222
  case "free_registration":
220
223
  case "single_paid":
221
224
  case "ticket_types":
225
+ case "sliding_scale":
222
226
  return pricing.currency;
223
227
  case "course":
224
228
  return pricing.display.display.currency;
@@ -236,6 +240,8 @@ function publicEventPricingMode(pricing) {
236
240
  return "single";
237
241
  case "ticket_types":
238
242
  return "ticket_types";
243
+ case "sliding_scale":
244
+ return "sliding_scale";
239
245
  case "drop_in":
240
246
  case "course":
241
247
  case "external":
@@ -269,6 +275,8 @@ function formatCoursePriceDisplayLabel(display, options) {
269
275
  return `Course (${formatCurrencyAmount(display.amount, display.currency, options)})`;
270
276
  case "from":
271
277
  return `Course (From ${formatCurrencyAmount(display.amount, display.currency, options)})`;
278
+ case "range":
279
+ return `Course (${formatCurrencyAmount(display.minAmount, display.currency, options)} - ${formatCurrencyAmount(display.maxAmount, display.currency, options)})`;
272
280
  default:
273
281
  return assertNever(display);
274
282
  }
@@ -385,10 +393,11 @@ function omitZeroAmountFutureRows(rows) {
385
393
  if (rows.length <= 1) {
386
394
  return rows;
387
395
  }
388
- const [firstRow, ...futureRows] = rows;
389
- if (!firstRow) {
396
+ const firstRow = rows[0];
397
+ if (firstRow === void 0) {
390
398
  return [];
391
399
  }
400
+ const futureRows = rows.slice(1);
392
401
  return [
393
402
  firstRow,
394
403
  ...futureRows.filter((row) => row.amountCents > 0)
@@ -431,11 +440,11 @@ function isValidTimeZone(timeZone) {
431
440
  }
432
441
  function getSafeTimeZone(timeZone) {
433
442
  const normalized = typeof timeZone === "string" ? timeZone.trim() : "";
434
- return normalized && isValidTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
443
+ return normalized.length > 0 && isValidTimeZone(normalized) ? normalized : FALLBACK_TIMEZONE;
435
444
  }
436
445
  function parseStrictBookingTimeZone(timeZone) {
437
446
  const normalized = timeZone.trim();
438
- if (!normalized || !isValidTimeZone(normalized)) {
447
+ if (normalized.length === 0 || !isValidTimeZone(normalized)) {
439
448
  return null;
440
449
  }
441
450
  return normalized;
@@ -522,7 +531,7 @@ function parseScheduledBookingDateTime(input) {
522
531
  try {
523
532
  const startsAt = toUtcIsoDateTimeString(input.startsAt);
524
533
  const timeZone = parseStrictBookingTimeZone(input.timeZone);
525
- if (!timeZone) {
534
+ if (timeZone === null) {
526
535
  return {
527
536
  ok: false,
528
537
  field: "timeZone",
@@ -661,7 +670,7 @@ function getBookingLocalDateKey(input) {
661
670
  }
662
671
  function parseBookingLocalDate(value) {
663
672
  const match2 = LOCAL_DATE_PATTERN.exec(value.trim());
664
- if (!match2?.groups) {
673
+ if (match2 === null || match2.groups === void 0) {
665
674
  throw new Error(`Invalid booking date format: "${value}" (expected YYYY-MM-DD)`);
666
675
  }
667
676
  const year = Number(match2.groups.year);
@@ -695,10 +704,10 @@ function normalizeLocationAddressText(value) {
695
704
  return null;
696
705
  }
697
706
  const trimmed = value.trim();
698
- return trimmed ? trimmed : null;
707
+ return trimmed.length > 0 ? trimmed : null;
699
708
  }
700
709
  function hasLocationCoordinates(value) {
701
- return typeof value?.lat === "number" && typeof value?.lng === "number";
710
+ return value != null && typeof value.lat === "number" && typeof value.lng === "number";
702
711
  }
703
712
  function toLocationCoordinates(value) {
704
713
  if (!hasLocationCoordinates(value)) {
@@ -30395,6 +30404,18 @@ var SVG_MIME_TYPE = "image/svg+xml";
30395
30404
  function isSvgMimeType(mimeType) {
30396
30405
  return mimeType === SVG_MIME_TYPE;
30397
30406
  }
30407
+
30408
+ // ../media-core/src/fileSafety.ts
30409
+ var CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
30410
+ function containsUnsafePathSegmentCharacter(value) {
30411
+ return value.includes("/") || value.includes("\\") || CONTROL_CHARACTER_PATTERN.test(value);
30412
+ }
30413
+ function isReservedPathSegment(value) {
30414
+ return value === "." || value === "..";
30415
+ }
30416
+ function isUnsafePathSegment(segment) {
30417
+ return segment.trim().length === 0 || segment !== segment.trim() || isReservedPathSegment(segment) || containsUnsafePathSegmentCharacter(segment);
30418
+ }
30398
30419
  function classifyKnownMimeType(mimeType) {
30399
30420
  const normalized = normalizeMimeType(mimeType);
30400
30421
  switch (normalized) {
@@ -30461,6 +30482,15 @@ function classifyKnownMimeType(mimeType) {
30461
30482
  function normalizeMimeType(mimeType) {
30462
30483
  return (mimeType ?? "").split(";", 1)[0]?.trim().toLowerCase() ?? "";
30463
30484
  }
30485
+ function parseStoragePath(value) {
30486
+ return joinStoragePathSegments(value.split("/"));
30487
+ }
30488
+ function joinStoragePathSegments(segments) {
30489
+ if (segments.some(isUnsafePathSegment)) {
30490
+ return err("unsafe-path-segment");
30491
+ }
30492
+ return ok(unsafeAsStoragePath(segments.join("/")));
30493
+ }
30464
30494
 
30465
30495
  // ../media-core/src/typeGuards.ts
30466
30496
  function isRecord(value) {
@@ -30472,11 +30502,20 @@ function asRecord(value) {
30472
30502
 
30473
30503
  // ../media-core/src/videoPrep.ts
30474
30504
  var VIDEO_PREP_LIMITS = {
30475
- maxRawByteCount: 500 * 1024 * 1024,
30476
- inlineContentMaxDurationSeconds: 60,
30477
- backgroundVideoMaxDurationSeconds: 30,
30505
+ maxRawByteCount: unsafeAsPositiveByteCount(500 * 1024 * 1024),
30506
+ inlineContentMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(60),
30507
+ backgroundVideoMaxDurationSeconds: unsafeAsPositiveVideoDurationSeconds(30),
30478
30508
  targetMaxDimensionPixels: 1080
30479
30509
  };
30510
+ function unsafeAsPositiveVideoDurationSeconds(value) {
30511
+ return value;
30512
+ }
30513
+ function asPositiveVideoDurationSeconds(value) {
30514
+ if (!Number.isFinite(value) || value <= 0) {
30515
+ return err("not-positive-video-duration-seconds");
30516
+ }
30517
+ return ok(unsafeAsPositiveVideoDurationSeconds(value));
30518
+ }
30480
30519
  function deriveVideoPrepPolicy(intent) {
30481
30520
  switch (intent.tag) {
30482
30521
  case "files-and-media":
@@ -30527,14 +30566,16 @@ function evaluateVideoSlotAssetPolicy(asset, policy) {
30527
30566
  if (!isVideoMimeType(asset.mimeType)) {
30528
30567
  return { tag: "not-video" };
30529
30568
  }
30530
- const durationSeconds = asset.durationSeconds;
30531
- if (!Number.isFinite(durationSeconds) || durationSeconds == null || durationSeconds <= 0) {
30569
+ const durationSeconds = asPositiveVideoDurationSeconds(
30570
+ asset.durationSeconds ?? Number.NaN
30571
+ );
30572
+ if (!durationSeconds.ok) {
30532
30573
  return { tag: "duration-unknown", maxSeconds: policy.maxDurationSeconds };
30533
30574
  }
30534
- if (durationSeconds > policy.maxDurationSeconds) {
30575
+ if (durationSeconds.value > policy.maxDurationSeconds) {
30535
30576
  return {
30536
30577
  tag: "duration-too-long",
30537
- actualSeconds: durationSeconds,
30578
+ actualSeconds: durationSeconds.value,
30538
30579
  maxSeconds: policy.maxDurationSeconds
30539
30580
  };
30540
30581
  }
@@ -30590,10 +30631,10 @@ var RELATIVE_STORAGE_PATH_PREFIXES = [
30590
30631
  "imports/"
30591
30632
  ];
30592
30633
  function isRelativeDirectImageStoragePath(storagePath) {
30593
- return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix));
30634
+ return RELATIVE_STORAGE_PATH_PREFIXES.some((prefix) => storagePath.startsWith(prefix)) && parseStoragePath(storagePath).ok;
30594
30635
  }
30595
30636
  function isSiteScopedDirectImageStoragePath(storagePath) {
30596
- return storagePath.startsWith("sites/");
30637
+ return storagePath.startsWith("sites/") && parseStoragePath(storagePath).ok;
30597
30638
  }
30598
30639
  function encodeStoragePath(storagePath) {
30599
30640
  return storagePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
@@ -33402,6 +33443,21 @@ function fieldPlanToZod(plan, options = {}) {
33402
33443
  }
33403
33444
  });
33404
33445
  }
33446
+ function fieldPresenceForPlan(plan) {
33447
+ return plan.required ? { kind: "required" } : { kind: "optional" };
33448
+ }
33449
+ function fieldPlanToObjectPropertyZod(plan, options = {}) {
33450
+ const schema = fieldPlanToZod(plan, options);
33451
+ const presence = fieldPresenceForPlan(plan);
33452
+ switch (presence.kind) {
33453
+ case "required":
33454
+ return schema;
33455
+ case "optional":
33456
+ return schema.optional();
33457
+ default:
33458
+ return assertNever4(presence);
33459
+ }
33460
+ }
33405
33461
  function addFieldIssueToZodContext(zodCtx, validationIssue, rootPath) {
33406
33462
  const message2 = fieldIssueToMessage(validationIssue);
33407
33463
  const path = relativeZodIssuePath(validationIssue.path, rootPath);
@@ -33668,7 +33724,7 @@ function createManifestValidator(manifestOrFields, options = {}) {
33668
33724
  function deriveContentZodFromPlans(plans, options) {
33669
33725
  const shape = {};
33670
33726
  for (const plan of plans) {
33671
- shape[plan.fieldId] = fieldPlanToZod(plan, options);
33727
+ shape[plan.fieldId] = fieldPlanToObjectPropertyZod(plan, options);
33672
33728
  }
33673
33729
  return z.object(shape).catchall(z.unknown()).passthrough();
33674
33730
  }
@@ -60129,9 +60185,12 @@ var coursePricingOptionSchema = z.object({
60129
60185
  });
60130
60186
  var coursePricingSchema = z.object({
60131
60187
  bookingRequirement: z.enum(["none", "required"]),
60132
- pricingStrategy: z.enum(["free", "single", "tiered"]),
60188
+ pricingStrategy: z.enum(["free", "single", "tiered", "sliding_scale"]),
60133
60189
  currency: z.string(),
60134
60190
  singlePriceAmount: z.number().nullable(),
60191
+ slidingMinAmount: z.number().nullable().optional(),
60192
+ slidingMaxAmount: z.number().nullable().optional(),
60193
+ slidingSuggestedAmount: z.number().nullable().optional(),
60135
60194
  paymentTerms: blockBookingPaymentTermsSchema,
60136
60195
  options: z.array(coursePricingOptionSchema)
60137
60196
  });
@@ -60285,6 +60344,19 @@ var publicEventPricingSchema = z.discriminatedUnion("kind", [
60285
60344
  ticketTypes: z.array(publicEventTicketTypeSchema),
60286
60345
  display: publicEventTicketTypePriceDisplaySchema
60287
60346
  }),
60347
+ z.object({
60348
+ kind: z.literal("sliding_scale"),
60349
+ minAmount: z.number(),
60350
+ maxAmount: z.number(),
60351
+ suggestedAmount: z.number().nullable(),
60352
+ currency: z.string(),
60353
+ display: z.object({
60354
+ kind: z.literal("range"),
60355
+ minAmount: z.number(),
60356
+ maxAmount: z.number(),
60357
+ currency: z.string()
60358
+ })
60359
+ }),
60288
60360
  z.object({
60289
60361
  kind: z.literal("course"),
60290
60362
  display: z.object({ kind: z.literal("course"), display: publicCoursePriceDisplaySchema })
@@ -60693,7 +60765,7 @@ function isRecord7(value) {
60693
60765
  }
60694
60766
  function isPublicEventPricing(value) {
60695
60767
  if (!isRecord7(value) || typeof value.kind !== "string") return false;
60696
- return value.kind === "drop_in" || value.kind === "free_registration" || value.kind === "single_paid" || value.kind === "ticket_types" || value.kind === "course" || value.kind === "external";
60768
+ 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";
60697
60769
  }
60698
60770
  function isPublicEventTicketType(value) {
60699
60771
  return isRecord7(value) && typeof value.id === "string";
@@ -61334,7 +61406,9 @@ function getSelectableCoursePricingOptionsForNow(course, now) {
61334
61406
  return left.name.localeCompare(right.name);
61335
61407
  });
61336
61408
  }
61337
- function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {}) {
61409
+ function resolveCoursePricingState(course, selectedPricingOptionId, selectedSlidingAmountOrNowInput, nowInput = {}) {
61410
+ const selectedSlidingAmount = typeof selectedSlidingAmountOrNowInput === "number" || selectedSlidingAmountOrNowInput === null ? selectedSlidingAmountOrNowInput : null;
61411
+ const effectiveNowInput = typeof selectedSlidingAmountOrNowInput === "object" && selectedSlidingAmountOrNowInput !== null ? selectedSlidingAmountOrNowInput : nowInput;
61338
61412
  const currency = course.pricing?.currency ?? course.currency;
61339
61413
  if (!course.pricing) {
61340
61414
  const legacyAmount = course.priceCents ?? 0;
@@ -61344,6 +61418,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
61344
61418
  selectableOptions: [],
61345
61419
  requiresSelection: false,
61346
61420
  selectedOption: null,
61421
+ slidingScale: null,
61422
+ selectedSlidingAmount: null,
61347
61423
  selectedPriceAmount: legacyAmount,
61348
61424
  selectedRequiresPayment: legacyAmount > 0,
61349
61425
  hasPaidSelection: legacyAmount > 0,
@@ -61358,6 +61434,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
61358
61434
  selectableOptions: [],
61359
61435
  requiresSelection: false,
61360
61436
  selectedOption: null,
61437
+ slidingScale: null,
61438
+ selectedSlidingAmount: null,
61361
61439
  selectedPriceAmount: 0,
61362
61440
  selectedRequiresPayment: false,
61363
61441
  hasPaidSelection: false,
@@ -61371,6 +61449,8 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
61371
61449
  selectableOptions: [],
61372
61450
  requiresSelection: false,
61373
61451
  selectedOption: null,
61452
+ slidingScale: null,
61453
+ selectedSlidingAmount: null,
61374
61454
  selectedPriceAmount: amount,
61375
61455
  selectedRequiresPayment: amount > 0,
61376
61456
  hasPaidSelection: amount > 0,
@@ -61380,7 +61460,7 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
61380
61460
  case "tiered": {
61381
61461
  const selectableOptions = getSelectableCoursePricingOptionsForNow(
61382
61462
  course,
61383
- resolvePlanningDateInput(nowInput)
61463
+ resolvePlanningDateInput(effectiveNowInput)
61384
61464
  );
61385
61465
  const selectedOption = (selectedPricingOptionId ? selectableOptions.find((option) => option.id === selectedPricingOptionId) : null) ?? (selectableOptions.length === 1 ? selectableOptions[0] ?? null : null);
61386
61466
  const lowestAmount = selectableOptions.reduce((lowest, option) => {
@@ -61396,12 +61476,51 @@ function resolveCoursePricingState(course, selectedPricingOptionId, nowInput = {
61396
61476
  selectableOptions,
61397
61477
  requiresSelection: selectableOptions.length > 1,
61398
61478
  selectedOption,
61479
+ slidingScale: null,
61480
+ selectedSlidingAmount: null,
61399
61481
  selectedPriceAmount: selectedOption?.priceAmount ?? null,
61400
61482
  selectedRequiresPayment: (selectedOption?.priceAmount ?? 0) > 0,
61401
61483
  hasPaidSelection: selectableOptions.some((option) => option.priceAmount > 0),
61402
61484
  hasFreeSelection: selectableOptions.some((option) => option.priceAmount === 0)
61403
61485
  };
61404
61486
  }
61487
+ case "sliding_scale": {
61488
+ const minAmount = course.pricing.slidingMinAmount ?? null;
61489
+ const maxAmount = course.pricing.slidingMaxAmount ?? null;
61490
+ if (minAmount === null || maxAmount === null || minAmount < 0 || maxAmount < minAmount) {
61491
+ return {
61492
+ currency,
61493
+ display: { kind: "unavailable", currency },
61494
+ selectableOptions: [],
61495
+ requiresSelection: true,
61496
+ selectedOption: null,
61497
+ slidingScale: null,
61498
+ selectedSlidingAmount: null,
61499
+ selectedPriceAmount: null,
61500
+ selectedRequiresPayment: false,
61501
+ hasPaidSelection: false,
61502
+ hasFreeSelection: false
61503
+ };
61504
+ }
61505
+ const normalizedSelectedAmount = typeof selectedSlidingAmount === "number" && Number.isFinite(selectedSlidingAmount) ? selectedSlidingAmount : null;
61506
+ return {
61507
+ currency,
61508
+ display: { kind: "range", minAmount, maxAmount, currency },
61509
+ selectableOptions: [],
61510
+ requiresSelection: normalizedSelectedAmount === null,
61511
+ selectedOption: null,
61512
+ slidingScale: {
61513
+ minAmount,
61514
+ maxAmount,
61515
+ suggestedAmount: course.pricing.slidingSuggestedAmount ?? null
61516
+ },
61517
+ selectedSlidingAmount: normalizedSelectedAmount,
61518
+ selectedPriceAmount: normalizedSelectedAmount,
61519
+ selectedRequiresPayment: (normalizedSelectedAmount ?? 0) > 0,
61520
+ hasPaidSelection: maxAmount > 0,
61521
+ hasFreeSelection: minAmount === 0
61522
+ };
61523
+ }
61405
61524
  }
61406
61525
  }
61407
61526
  function formatCoursePriceDisplay(display, locale) {
@@ -62335,7 +62454,7 @@ function formatCalendarTitle(viewDate, view, days, monthNames = MONTH_NAMES) {
62335
62454
  if (view === "week") {
62336
62455
  const weekStart = days[0];
62337
62456
  const weekEnd = days[6];
62338
- if (!weekStart || !weekEnd) {
62457
+ if (weekStart === void 0 || weekEnd === void 0) {
62339
62458
  return `${monthNames[viewDate.getMonth()] ?? ""} ${viewDate.getFullYear()}`;
62340
62459
  }
62341
62460
  const startMonth = monthNames[weekStart.getMonth()] ?? "";
@@ -62441,7 +62560,9 @@ function getLocalDateKey(isoTimestamp, timeZone) {
62441
62560
  const year = parts.find((part) => part.type === "year")?.value;
62442
62561
  const month = parts.find((part) => part.type === "month")?.value;
62443
62562
  const day = parts.find((part) => part.type === "day")?.value;
62444
- if (!year || !month || !day) return null;
62563
+ if (year === void 0 || year.length === 0) return null;
62564
+ if (month === void 0 || month.length === 0) return null;
62565
+ if (day === void 0 || day.length === 0) return null;
62445
62566
  return `${year}-${month}-${day}`;
62446
62567
  }
62447
62568
  var validTimeZoneCache = /* @__PURE__ */ new Map();
@@ -62698,7 +62819,7 @@ function enumerateDateKeys(startDateKey, endDateKey) {
62698
62819
  }
62699
62820
  function parseDateKey(dateKey) {
62700
62821
  const match2 = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateKey);
62701
- if (!match2) {
62822
+ if (match2 === null) {
62702
62823
  throw new Error(`Invalid timetable date key: ${dateKey}`);
62703
62824
  }
62704
62825
  const [, year, month, day] = match2;
@@ -62718,7 +62839,7 @@ function groupItemsByDate(items, accessors) {
62718
62839
  const dateKey = getLocalDateKey(startsAt, timeZone);
62719
62840
  if (dateKey == null) continue;
62720
62841
  const existing = grouped.get(dateKey);
62721
- if (existing) {
62842
+ if (existing !== void 0) {
62722
62843
  existing.push(item);
62723
62844
  } else {
62724
62845
  grouped.set(dateKey, [item]);
@@ -71845,11 +71966,11 @@ function extractRouteUpdatedAt(candidate) {
71845
71966
  }
71846
71967
  function resolveRouteTargetForStage(routeIdValue, routes, stageInput) {
71847
71968
  const routeId = typeof routeIdValue === "string" ? routeIdValue : null;
71848
- if (!routeId || !routes) return null;
71969
+ if (routeId === null || routeId.length === 0 || routes === void 0) return null;
71849
71970
  const routeEntry = routes[routeId];
71850
71971
  if (!isRouteVisibleInStage(routeEntry, stageInput)) return null;
71851
71972
  const href = extractRouteHref(routeEntry, stageInput);
71852
- if (!href) return null;
71973
+ if (href === null) return null;
71853
71974
  return {
71854
71975
  routeId,
71855
71976
  href,
@@ -73708,6 +73829,7 @@ var BOOKING_FORM_DATA_RESERVED_KEY_VALUES = [
73708
73829
  "selectedDate",
73709
73830
  "selectedSlot",
73710
73831
  "discountCode",
73832
+ "selectedSlidingAmountInput",
73711
73833
  "selectedCollectionOptionKind",
73712
73834
  "selectedAppointmentPackageId"
73713
73835
  ];
@@ -73991,6 +74113,32 @@ function readOptionalString(value) {
73991
74113
  function readOptionalNumber(value) {
73992
74114
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
73993
74115
  }
74116
+ function getBookingServiceSlidingScale(service) {
74117
+ const pricing = service?.pricing;
74118
+ if (!pricing || pricing.pricingStrategy !== "sliding_scale" || pricing.slidingMinAmount === null || pricing.slidingMinAmount === void 0 || pricing.slidingMaxAmount === null || pricing.slidingMaxAmount === void 0) {
74119
+ return null;
74120
+ }
74121
+ return {
74122
+ minAmount: pricing.slidingMinAmount,
74123
+ maxAmount: pricing.slidingMaxAmount,
74124
+ suggestedAmount: pricing.slidingSuggestedAmount ?? null,
74125
+ currency: pricing.currency
74126
+ };
74127
+ }
74128
+ function parseBookingCurrencyInputToCents(value) {
74129
+ const trimmed = value?.trim() ?? "";
74130
+ if (!trimmed) return null;
74131
+ if (!/^(?:\d+(?:\.\d{0,2})?|\.\d{1,2})$/.test(trimmed)) return null;
74132
+ const parsed = Number(trimmed);
74133
+ if (!Number.isFinite(parsed) || parsed < 0) return null;
74134
+ return Math.round(parsed * 100);
74135
+ }
74136
+ function formatBookingCentsAsCurrencyInput(amount) {
74137
+ return amount % 100 === 0 ? String(amount / 100) : (amount / 100).toFixed(2);
74138
+ }
74139
+ function isBookingServiceSlidingAmountInRange(slidingScale, selectedAmount) {
74140
+ return selectedAmount !== null && selectedAmount >= slidingScale.minAmount && selectedAmount <= slidingScale.maxAmount;
74141
+ }
73994
74142
  function readStringArray(value) {
73995
74143
  if (!Array.isArray(value)) return void 0;
73996
74144
  const strings = value.filter((item) => typeof item === "string");
@@ -74179,6 +74327,20 @@ function prepareBookingSubmission(input) {
74179
74327
  };
74180
74328
  }
74181
74329
  const paymentSelectionMode = getBookingServicePaymentSelectionMode(selectedService);
74330
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
74331
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
74332
+ input.data.selectedSlidingAmountInput
74333
+ );
74334
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(slidingScale, selectedSlidingAmount)) {
74335
+ return {
74336
+ ok: false,
74337
+ error: {
74338
+ tag: selectedSlidingAmount === null ? "sliding-amount-required" : "sliding-amount-out-of-range",
74339
+ serviceId,
74340
+ serviceTitle: selectedService.title || serviceId
74341
+ }
74342
+ };
74343
+ }
74182
74344
  if (paymentSelectionMode === "choice_required" && !toSelectedCollectionOptionKind(input.data.selectedCollectionOptionKind)) {
74183
74345
  return {
74184
74346
  ok: false,
@@ -74206,6 +74368,7 @@ function prepareBookingSubmission(input) {
74206
74368
  ...common,
74207
74369
  kind: "standard",
74208
74370
  ...discountCode ? { discountCode } : {},
74371
+ ...slidingScale && selectedSlidingAmount !== null ? { selectedSlidingAmount } : {},
74209
74372
  ...selectedCollectionOptionKind ? { selectedCollectionOptionKind } : {},
74210
74373
  altchaSolution: input.altchaSolution ?? null
74211
74374
  }
@@ -74219,6 +74382,10 @@ function formatBookingSubmissionPreparationError(error) {
74219
74382
  return `Service not found: ${error.serviceId}`;
74220
74383
  case "payment-option-required":
74221
74384
  return `Please choose how you want to pay for "${error.serviceTitle}".`;
74385
+ case "sliding-amount-required":
74386
+ return `Please choose an amount for "${error.serviceTitle}".`;
74387
+ case "sliding-amount-out-of-range":
74388
+ return `Please choose an amount within the sliding scale for "${error.serviceTitle}".`;
74222
74389
  case "payment-option-unsupported":
74223
74390
  return `Service "${error.serviceTitle}" has a payment option that is not supported yet.`;
74224
74391
  case "service-missing-duration":
@@ -74315,6 +74482,7 @@ var bookingServiceSchema = z.object({
74315
74482
  title: z.string(),
74316
74483
  description: z.string().nullable().optional().transform((value) => value ?? void 0),
74317
74484
  durationMinutes: z.number().optional(),
74485
+ pricing: z.custom().optional(),
74318
74486
  priceAmount: z.number().nullable().optional(),
74319
74487
  priceCurrency: z.string().nullable().optional(),
74320
74488
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
@@ -95604,7 +95772,7 @@ function parsePath(path) {
95604
95772
  for (const raw of parts) {
95605
95773
  if (!raw) continue;
95606
95774
  const baseMatch = raw.match(/^[^\[]+/);
95607
- if (baseMatch) {
95775
+ if (baseMatch !== null) {
95608
95776
  const base2 = baseMatch[1] ?? baseMatch[0];
95609
95777
  if (/^\d+$/.test(base2)) {
95610
95778
  segments.push(Number(base2));
@@ -95623,7 +95791,7 @@ function parsePath(path) {
95623
95791
  segments.push(token);
95624
95792
  }
95625
95793
  }
95626
- if (!baseMatch && !raw.includes("[")) {
95794
+ if (baseMatch === null && !raw.includes("[")) {
95627
95795
  segments.push(raw);
95628
95796
  }
95629
95797
  }
@@ -110834,14 +111002,14 @@ CollapsibleContent.displayName = CONTENT_NAME5;
110834
111002
  var CollapsibleContentImpl = React61.forwardRef((props2, forwardedRef) => {
110835
111003
  const { __scopeCollapsible, present, children, ...contentProps } = props2;
110836
111004
  const context = useCollapsibleContext(CONTENT_NAME5, __scopeCollapsible);
110837
- const [isPresent, setIsPresent] = React61.useState(present);
111005
+ const [isPresent2, setIsPresent] = React61.useState(present);
110838
111006
  const ref = React61.useRef(null);
110839
111007
  const composedRefs = useComposedRefs(forwardedRef, ref);
110840
111008
  const heightRef = React61.useRef(0);
110841
111009
  const height = heightRef.current;
110842
111010
  const widthRef = React61.useRef(0);
110843
111011
  const width = widthRef.current;
110844
- const isOpen = context.open || isPresent;
111012
+ const isOpen = context.open || isPresent2;
110845
111013
  const isMountAnimationPreventedRef = React61.useRef(isOpen);
110846
111014
  const originalStylesRef = React61.useRef(void 0);
110847
111015
  React61.useEffect(() => {
@@ -126532,6 +126700,7 @@ function createBlockApi(config) {
126532
126700
  body: {
126533
126701
  courseId: data.courseId,
126534
126702
  ...data.pricingOptionId ? { pricingOptionId: data.pricingOptionId } : {},
126703
+ ...data.selectedSlidingAmount !== void 0 ? { selectedSlidingAmount: data.selectedSlidingAmount } : {},
126535
126704
  email: data.email,
126536
126705
  name: data.name,
126537
126706
  phone: data.phone,
@@ -126912,22 +127081,22 @@ function AltchaWidget({
126912
127081
  if (typeof window === "undefined") return;
126913
127082
  let widget = null;
126914
127083
  import('./altcha-EKUEQ4RP.mjs').then(() => {
126915
- if (!containerRef.current) return;
127084
+ if (containerRef.current === null) return;
126916
127085
  widget = document.createElement("altcha-widget");
126917
127086
  widget.setAttribute("challengeurl", challengeUrl);
126918
- if (auto) {
127087
+ if (auto === true) {
126919
127088
  widget.setAttribute("auto", "onload");
126920
127089
  }
126921
- if (hidelogo) {
127090
+ if (hidelogo === true) {
126922
127091
  widget.setAttribute("hidelogo", "true");
126923
127092
  }
126924
127093
  widget.setAttribute("hidefooter", "true");
126925
- if (className) {
127094
+ if (className !== void 0 && className.length > 0) {
126926
127095
  widget.className = className;
126927
127096
  }
126928
127097
  widget.addEventListener("statechange", ((event) => {
126929
127098
  const { state, payload } = event.detail;
126930
- if (state === "verified" && payload) {
127099
+ if (state === "verified" && payload !== void 0 && payload.length > 0) {
126931
127100
  onSolutionChange?.(payload);
126932
127101
  } else if (state === "error" || state === "expired") {
126933
127102
  onSolutionChange?.(null);
@@ -126938,7 +127107,7 @@ function AltchaWidget({
126938
127107
  console.error("Failed to load ALTCHA widget:", err4);
126939
127108
  });
126940
127109
  return () => {
126941
- if (widget && containerRef.current) {
127110
+ if (widget !== null && containerRef.current !== null) {
126942
127111
  containerRef.current.removeChild(widget);
126943
127112
  }
126944
127113
  };
@@ -126979,15 +127148,15 @@ new TextEncoder();
126979
127148
  // ../altcha/src/index.ts
126980
127149
  function getCmsApiUrl() {
126981
127150
  const env = process.env.RIVERBANK_ENV;
126982
- if (env) {
127151
+ if (env !== void 0 && env.length > 0) {
126983
127152
  const envKey = `RIVERBANK_${env.toUpperCase()}_DASHBOARD_URL`;
126984
127153
  const dashboardUrl2 = process.env[envKey];
126985
- if (dashboardUrl2) {
127154
+ if (dashboardUrl2 !== void 0 && dashboardUrl2.length > 0) {
126986
127155
  return `${dashboardUrl2.replace(/\/$/, "")}/api`;
126987
127156
  }
126988
127157
  }
126989
127158
  const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;
126990
- if (dashboardUrl) {
127159
+ if (dashboardUrl !== void 0 && dashboardUrl.length > 0) {
126991
127160
  return `${dashboardUrl.replace(/\/$/, "")}/api`;
126992
127161
  }
126993
127162
  throw new Error("Dashboard URL not configured. Set NEXT_PUBLIC_DASHBOARD_URL");
@@ -127474,6 +127643,7 @@ function useBookingSubmission(_siteId) {
127474
127643
  customFields: data.customFields,
127475
127644
  timezone: data.timezone,
127476
127645
  discountCode: data.discountCode,
127646
+ selectedSlidingAmount: data.selectedSlidingAmount,
127477
127647
  selectedCollectionOptionKind: data.selectedCollectionOptionKind,
127478
127648
  altchaSolution: data.altchaSolution ?? null,
127479
127649
  successUrl: data.successUrl,
@@ -127953,6 +128123,9 @@ function createInitialBookingFlowReducerState(input) {
127953
128123
  fieldValues: collectInitialFieldValues(initialData),
127954
128124
  touchedFields: {},
127955
128125
  discountCode: toOptionalString(initialData.discountCode) ?? "",
128126
+ selectedSlidingAmountInput: toOptionalString(
128127
+ initialData.selectedSlidingAmountInput
128128
+ ) ?? "",
127956
128129
  selectedCollectionOptionKind: toSelectedCollectionOptionKind(
127957
128130
  initialData.selectedCollectionOptionKind
127958
128131
  ),
@@ -128005,6 +128178,7 @@ function bookingFlowReducer(state, event) {
128005
128178
  selectedDate: void 0,
128006
128179
  selectedSlot: null,
128007
128180
  selectedAppointmentPackageId: void 0,
128181
+ selectedSlidingAmountInput: "",
128008
128182
  selectedCollectionOptionKind: void 0,
128009
128183
  validationErrors: clearBookingFormError(state.validationErrors)
128010
128184
  };
@@ -128058,6 +128232,13 @@ function bookingFlowReducer(state, event) {
128058
128232
  selectedAppointmentPackageId: void 0,
128059
128233
  validationErrors: clearBookingFormError(state.validationErrors)
128060
128234
  };
128235
+ case "sliding-amount-changed":
128236
+ return {
128237
+ ...state,
128238
+ selectedSlidingAmountInput: event.value,
128239
+ selectedAppointmentPackageId: void 0,
128240
+ validationErrors: clearBookingFormError(state.validationErrors)
128241
+ };
128061
128242
  case "appointment-package-selected":
128062
128243
  return {
128063
128244
  ...state,
@@ -128160,6 +128341,7 @@ function toBookingFormData(state) {
128160
128341
  selectedDate: state.selectedDate,
128161
128342
  selectedSlot: state.selectedSlot?.startAt,
128162
128343
  discountCode: state.discountCode || void 0,
128344
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput || void 0,
128163
128345
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
128164
128346
  selectedAppointmentPackageId: state.selectedAppointmentPackageId
128165
128347
  };
@@ -128177,7 +128359,8 @@ function buildBookingStepResolution(input) {
128177
128359
  input.form.settings?.serviceIds && input.form.settings.serviceIds.length > 1
128178
128360
  );
128179
128361
  const selectedService = paymentResolution.service;
128180
- const needsPayment = paymentResolution.mode !== "hidden" || Boolean(
128362
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
128363
+ const needsPayment = Boolean(selectedServiceSlidingScale) || paymentResolution.mode !== "hidden" || Boolean(
128181
128364
  input.enableAppointmentPackages && selectedService && (selectedService.priceAmount ?? 0) > 0
128182
128365
  );
128183
128366
  const hasFields = Boolean(input.form.schema?.fields.length);
@@ -128221,6 +128404,18 @@ function validateBookingStep(input) {
128221
128404
  services: input.services,
128222
128405
  defaultServiceId: input.defaultServiceId
128223
128406
  });
128407
+ const slidingScale = getBookingServiceSlidingScale(resolution.service);
128408
+ if (slidingScale && !isBookingServiceSlidingAmountInRange(
128409
+ slidingScale,
128410
+ parseBookingCurrencyInputToCents(input.data.selectedSlidingAmountInput)
128411
+ )) {
128412
+ return {
128413
+ ok: false,
128414
+ errors: createFormBookingValidationError(
128415
+ "Please choose an amount within the sliding scale"
128416
+ )
128417
+ };
128418
+ }
128224
128419
  if (resolution.mode === "hidden" || resolution.mode === "disclosure_only") {
128225
128420
  return { ok: true };
128226
128421
  }
@@ -128630,11 +128825,27 @@ function useHeadlessBookingFlow(options) {
128630
128825
  const selectedServiceId = state.selectedServiceId ?? defaultServiceId;
128631
128826
  const selectedResourceId = state.selectedResourceId ?? defaultResourceId;
128632
128827
  const selectedService = options.services.find((service) => service.id === selectedServiceId) ?? null;
128828
+ const lastSlidingServiceIdRef = useRef(null);
128829
+ useEffect(() => {
128830
+ const selectedId = selectedService?.id ?? null;
128831
+ if (lastSlidingServiceIdRef.current === selectedId) return;
128832
+ lastSlidingServiceIdRef.current = selectedId;
128833
+ const slidingScale = getBookingServiceSlidingScale(selectedService);
128834
+ dispatch({
128835
+ type: "sliding-amount-changed",
128836
+ value: slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatBookingCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
128837
+ });
128838
+ }, [selectedService]);
128633
128839
  const hasPortalBearerAuth = (api.config.authOverrides?.portal ?? api.config.auth).type === "bearer";
128840
+ const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
128841
+ const selectedSlidingAmount = parseBookingCurrencyInputToCents(
128842
+ state.selectedSlidingAmountInput
128843
+ );
128844
+ const selectedAppointmentPriceAmount = selectedServiceSlidingScale && selectedSlidingAmount !== null ? selectedSlidingAmount : selectedService?.priceAmount ?? 0;
128634
128845
  const canLoadAppointmentPackages = Boolean(
128635
- options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && (selectedService.priceAmount ?? 0) > 0
128846
+ options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && !selectedServiceSlidingScale && (selectedService.priceAmount ?? 0) > 0
128636
128847
  );
128637
- const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
128848
+ const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || selectedServiceSlidingScale || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
128638
128849
  const data = useMemo(() => toBookingFormData(state), [state]);
128639
128850
  const availabilityDatesQuery = useMemo(
128640
128851
  () => buildBookingAvailabilityQuery({
@@ -128864,13 +129075,13 @@ function useHeadlessBookingFlow(options) {
128864
129075
  state.selectedSlot
128865
129076
  ]);
128866
129077
  const appointmentDiscountContext = useMemo(
128867
- () => selectedService && (selectedService.priceAmount ?? 0) > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
128868
- [selectedService?.id, selectedService?.priceAmount]
129078
+ () => selectedService && selectedAppointmentPriceAmount > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
129079
+ [selectedAppointmentPriceAmount, selectedService?.id]
128869
129080
  );
128870
129081
  const discountUi = useDiscountCodeUi({
128871
129082
  api,
128872
129083
  discountContext: appointmentDiscountContext,
128873
- subtotalCents: selectedService?.priceAmount ?? 0
129084
+ subtotalCents: selectedAppointmentPriceAmount
128874
129085
  });
128875
129086
  const { submit, clearFeedback } = useBookingSubmission(options.siteId);
128876
129087
  const submittingRef = useRef(false);
@@ -129013,6 +129224,7 @@ function useHeadlessBookingFlow(options) {
129013
129224
  type: "appointment-package-selected",
129014
129225
  packageId: packageId ? unsafeAsCustomerPassId(packageId) : null
129015
129226
  }),
129227
+ setSlidingAmountInput: (value) => dispatch({ type: "sliding-amount-changed", value }),
129016
129228
  setDiscountCode: (code) => {
129017
129229
  dispatch({ type: "discount-code-changed", code });
129018
129230
  latestRef.current.setDiscountCode(code);
@@ -129083,11 +129295,11 @@ function useHeadlessBookingFlow(options) {
129083
129295
  () => selectedService && paymentResolution.mode !== "unsupported" ? buildBookingPaymentSelectionViewModel({
129084
129296
  serviceTitle: selectedService.title,
129085
129297
  paymentTerms: selectedService.paymentTerms,
129086
- priceAmount: selectedService.priceAmount,
129298
+ priceAmount: selectedAppointmentPriceAmount,
129087
129299
  priceCurrency: selectedService.priceCurrency,
129088
129300
  bookingStartAt: state.selectedSlot?.startAt
129089
129301
  }) : null,
129090
- [paymentResolution.mode, selectedService, state.selectedSlot?.startAt]
129302
+ [paymentResolution.mode, selectedAppointmentPriceAmount, selectedService, state.selectedSlot?.startAt]
129091
129303
  );
129092
129304
  const appointmentPackageItems = appointmentPackages.tag === "loaded" ? appointmentPackages.value : [];
129093
129305
  const selectedAppointmentPackage = appointmentPackageItems.find(
@@ -129116,6 +129328,9 @@ function useHeadlessBookingFlow(options) {
129116
129328
  selection: null,
129117
129329
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
129118
129330
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
129331
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
129332
+ selectedSlidingAmount,
129333
+ slidingScale: selectedServiceSlidingScale,
129119
129334
  appointmentPackages: appointmentPackagesModel,
129120
129335
  errorMessage: paymentResolution.errorMessage ?? "This service cannot be booked right now."
129121
129336
  } : {
@@ -129124,6 +129339,9 @@ function useHeadlessBookingFlow(options) {
129124
129339
  selection: paymentSelection,
129125
129340
  selectedCollectionOptionKind: state.selectedCollectionOptionKind,
129126
129341
  selectedAppointmentPackageId: state.selectedAppointmentPackageId,
129342
+ selectedSlidingAmountInput: state.selectedSlidingAmountInput,
129343
+ selectedSlidingAmount,
129344
+ slidingScale: selectedServiceSlidingScale,
129127
129345
  appointmentPackages: appointmentPackagesModel
129128
129346
  };
129129
129347
  return {
@@ -129664,6 +129882,9 @@ var paymentOptionLayoutStyle = {
129664
129882
  var summaryStackStyle = {
129665
129883
  gap: "0.125rem"
129666
129884
  };
129885
+ function formatBookingAmount(amount, currency) {
129886
+ return formatCurrencyAmount(amount, currency, { decimals: "auto" });
129887
+ }
129667
129888
  function HeadlessBookingDefaultForm({
129668
129889
  booking,
129669
129890
  onSubmit
@@ -129940,6 +130161,7 @@ function HeadlessBookingPaymentSelectionStep({
129940
130161
  }
129941
130162
  const packageOptions = booking.payment.appointmentPackages.items;
129942
130163
  const hasPackageOptions = packageOptions.length > 0;
130164
+ const slidingScale = booking.payment.slidingScale;
129943
130165
  return /* @__PURE__ */ jsxs("div", { className: "ab-stack", children: [
129944
130166
  /* @__PURE__ */ jsxs("div", { className: "ab-info-box", children: [
129945
130167
  /* @__PURE__ */ jsx("h3", { className: "ab-subtitle", children: viewModel.sectionTitle }),
@@ -129950,6 +130172,28 @@ function HeadlessBookingPaymentSelectionStep({
129950
130172
  booking.payment.appointmentPackages.state.message,
129951
130173
  " You can continue with the normal payment option."
129952
130174
  ] }) : null,
130175
+ slidingScale ? /* @__PURE__ */ jsxs("div", { className: "rb-space-y-2", children: [
130176
+ /* @__PURE__ */ jsx("label", { htmlFor: "appointment-sliding-amount", className: "form-label", children: "Choose your price" }),
130177
+ /* @__PURE__ */ jsxs("p", { className: "rb-caption status-muted", children: [
130178
+ formatBookingAmount(slidingScale.minAmount, slidingScale.currency),
130179
+ " to ",
130180
+ formatBookingAmount(slidingScale.maxAmount, slidingScale.currency),
130181
+ slidingScale.suggestedAmount !== null ? `, suggested ${formatBookingAmount(slidingScale.suggestedAmount, slidingScale.currency)}` : ""
130182
+ ] }),
130183
+ /* @__PURE__ */ jsx(
130184
+ "input",
130185
+ {
130186
+ id: "appointment-sliding-amount",
130187
+ type: "number",
130188
+ className: "form-input",
130189
+ min: slidingScale.minAmount / 100,
130190
+ max: slidingScale.maxAmount / 100,
130191
+ step: "0.01",
130192
+ value: booking.payment.selectedSlidingAmountInput,
130193
+ onChange: (event) => booking.actions.setSlidingAmountInput(event.target.value)
130194
+ }
130195
+ )
130196
+ ] }) : null,
129953
130197
  /* @__PURE__ */ jsxs("div", { className: "ab-stack", role: "list", "aria-label": "Payment options", children: [
129954
130198
  packageOptions.map((appointmentPackage) => {
129955
130199
  const isSelected = booking.payment.selectedAppointmentPackageId === appointmentPackage.customerPassId;
@@ -131587,6 +131831,7 @@ function useEligibility(params) {
131587
131831
  occurrenceId: params.occurrenceId,
131588
131832
  attendanceMode: params.attendanceMode ?? void 0,
131589
131833
  ticketTypeId: params.ticketTypeId ?? void 0,
131834
+ selectedSlidingAmount: params.selectedSlidingAmount ?? void 0,
131590
131835
  email: params.email.trim().toLowerCase(),
131591
131836
  ticketsCount: params.ticketsCount
131592
131837
  });
@@ -131607,7 +131852,7 @@ function useEligibility(params) {
131607
131852
  return () => {
131608
131853
  cancelled = true;
131609
131854
  };
131610
- }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.email, params.ticketsCount]);
131855
+ }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.selectedSlidingAmount, params.email, params.ticketsCount]);
131611
131856
  return {
131612
131857
  state,
131613
131858
  eligibility: state.eligibility,
@@ -131930,6 +132175,41 @@ function TicketCounterLimit(props2) {
131930
132175
  ] }) : null
131931
132176
  ] });
131932
132177
  }
132178
+ function formatCurrencyInputAmount(amount) {
132179
+ const major = amount / 100;
132180
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
132181
+ }
132182
+ function SlidingScaleAmountField(props2) {
132183
+ const minLabel = formatPrice(props2.slidingScale.minAmount, props2.currency);
132184
+ const maxLabel = formatPrice(props2.slidingScale.maxAmount, props2.currency);
132185
+ const suggestedLabel = props2.slidingScale.suggestedAmount !== null ? formatPrice(props2.slidingScale.suggestedAmount, props2.currency) : null;
132186
+ return /* @__PURE__ */ jsxs("div", { className: "er-field", children: [
132187
+ /* @__PURE__ */ jsx("label", { htmlFor: "er-sliding-amount", className: "er-label", children: "Amount" }),
132188
+ /* @__PURE__ */ jsx(
132189
+ "input",
132190
+ {
132191
+ id: "er-sliding-amount",
132192
+ type: "number",
132193
+ min: formatCurrencyInputAmount(props2.slidingScale.minAmount),
132194
+ max: formatCurrencyInputAmount(props2.slidingScale.maxAmount),
132195
+ step: "0.01",
132196
+ inputMode: "decimal",
132197
+ className: "er-input",
132198
+ value: props2.value,
132199
+ onChange: (event) => props2.onChange?.(event.target.value)
132200
+ }
132201
+ ),
132202
+ /* @__PURE__ */ jsxs("p", { className: "er-text er-text--muted", children: [
132203
+ "Choose between ",
132204
+ minLabel,
132205
+ " and ",
132206
+ maxLabel,
132207
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
132208
+ "."
132209
+ ] }),
132210
+ props2.message ? /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: props2.message }) : null
132211
+ ] });
132212
+ }
131933
132213
  function getTicketTypeCounterAvailabilityLabel(input) {
131934
132214
  if (input.selectedRemainingCapacity !== null) {
131935
132215
  return `${input.selectedRemainingCapacity} remaining for this ticket type`;
@@ -131993,6 +132273,10 @@ var TicketSelectionStep = ({
131993
132273
  maxTickets,
131994
132274
  selectedTicketTypeId = null,
131995
132275
  onTicketTypeChange,
132276
+ slidingScale = null,
132277
+ selectedSlidingAmountInput = "",
132278
+ slidingScaleSelectionMessage = null,
132279
+ onSlidingAmountChange,
131996
132280
  onTicketsChange
131997
132281
  }) => {
131998
132282
  const deliveryMode = event?.deliveryMode ?? "in_person";
@@ -132108,6 +132392,16 @@ var TicketSelectionStep = ({
132108
132392
  ] })
132109
132393
  ] })
132110
132394
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
132395
+ slidingScale ? /* @__PURE__ */ jsx(
132396
+ SlidingScaleAmountField,
132397
+ {
132398
+ slidingScale,
132399
+ currency: eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2,
132400
+ value: selectedSlidingAmountInput,
132401
+ message: slidingScaleSelectionMessage,
132402
+ onChange: onSlidingAmountChange
132403
+ }
132404
+ ) : null,
132111
132405
  /* @__PURE__ */ jsx("h3", { className: "er-title", children: canJoinWaitlist ? "How many waitlist spots?" : "How many tickets?" }),
132112
132406
  /* @__PURE__ */ jsxs("div", { className: "er-ticket-counter-group", children: [
132113
132407
  /* @__PURE__ */ jsx(
@@ -132960,6 +133254,10 @@ function EventRegistrationWizardContent(props2) {
132960
133254
  maxTickets: wizard.maxTicketsNum,
132961
133255
  selectedTicketTypeId: wizard.selectedTicketTypeId,
132962
133256
  onTicketTypeChange: wizard.setSelectedTicketTypeId,
133257
+ slidingScale: wizard.selectedEventSlidingScale,
133258
+ selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
133259
+ slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
133260
+ onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
132963
133261
  onTicketsChange: wizard.setTicketsCount
132964
133262
  }
132965
133263
  )
@@ -133153,7 +133451,7 @@ function EventRegistrationSummaryBar(props2) {
133153
133451
  const summaryTimeZone = wizard.occurrenceContext?.timeZone ?? summaryEvent?.timeZone ?? null;
133154
133452
  const occurrenceOverrideTitle = wizard.occurrenceContext?.overrides?.title;
133155
133453
  const summaryTitle = occurrenceOverrideTitle ?? summaryEvent?.title ?? "";
133156
- const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
133454
+ const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (wizard.selectedEventSlidingScale ? wizard.selectedSlidingAmount : null) ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
133157
133455
  const summaryCurrency = (summaryEvent ? publicEventPricingCurrency(summaryEvent.pricing) : null) ?? DEFAULT_EVENT_CURRENCY2;
133158
133456
  const teamMemberNames = (summaryEvent?.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
133159
133457
  const subtotalCents = Math.max(0, summaryPrice * wizard.ticketsCount);
@@ -133553,6 +133851,7 @@ function useEventRegistrationSubmit(options) {
133553
133851
  occurrenceId: submitOccurrenceId,
133554
133852
  attendanceMode: options.selectedAttendanceMode ?? void 0,
133555
133853
  ticketTypeId: options.selectedTicketTypeId ?? void 0,
133854
+ selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
133556
133855
  email: options.formData.email.trim().toLowerCase(),
133557
133856
  name: options.formData.name.trim(),
133558
133857
  ticketsCount: options.ticketsCount,
@@ -133654,6 +133953,35 @@ function useEventRegistrationSubmit(options) {
133654
133953
  }
133655
133954
 
133656
133955
  // ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.ts
133956
+ function parseCurrencyInputToCents(value) {
133957
+ const trimmed = value.trim();
133958
+ if (!trimmed) return null;
133959
+ const amount = Number(trimmed);
133960
+ if (!Number.isFinite(amount) || amount < 0) return null;
133961
+ return Math.round(amount * 100);
133962
+ }
133963
+ function formatCentsAsCurrencyInput(amount) {
133964
+ const major = amount / 100;
133965
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
133966
+ }
133967
+ function getSlidingScaleForEvent(event) {
133968
+ const pricing = event ? resolveRuntimeEventPricing(event) : null;
133969
+ return pricing?.kind === "sliding_scale" ? {
133970
+ minAmount: pricing.minAmount,
133971
+ maxAmount: pricing.maxAmount,
133972
+ suggestedAmount: pricing.suggestedAmount
133973
+ } : null;
133974
+ }
133975
+ function getSlidingScaleSelectionMessage(input) {
133976
+ if (!input.slidingScale) return null;
133977
+ if (input.selectedAmount === null) {
133978
+ return "Choose an amount to continue.";
133979
+ }
133980
+ if (input.selectedAmount < input.slidingScale.minAmount || input.selectedAmount > input.slidingScale.maxAmount) {
133981
+ return "Choose an amount within the sliding scale.";
133982
+ }
133983
+ return null;
133984
+ }
133657
133985
  function useEventRegistrationWizard(options) {
133658
133986
  const api = useBlockApi();
133659
133987
  const registrationEvents = useMemo(
@@ -133698,6 +134026,10 @@ function useEventRegistrationWizard(options) {
133698
134026
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
133699
134027
  );
133700
134028
  const [selectedTicketTypeId, setSelectedTicketTypeId] = useState(null);
134029
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState(() => {
134030
+ const slidingScale = getSlidingScaleForEvent(preSelectedEvent ?? null);
134031
+ return slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : "";
134032
+ });
133701
134033
  const [registrationResult, setRegistrationResult] = useState(null);
133702
134034
  const [errorMessage, setErrorMessage] = useState("");
133703
134035
  const [altchaSolution, setAltchaSolution] = useState(null);
@@ -133734,6 +134066,7 @@ function useEventRegistrationWizard(options) {
133734
134066
  occurrenceId,
133735
134067
  attendanceMode: selectedAttendanceMode,
133736
134068
  ticketTypeId: selectedTicketTypeId,
134069
+ selectedSlidingAmount: parseCurrencyInputToCents(selectedSlidingAmountInput),
133737
134070
  email: formData.email,
133738
134071
  ticketsCount
133739
134072
  });
@@ -133869,6 +134202,18 @@ function useEventRegistrationWizard(options) {
133869
134202
  }),
133870
134203
  [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility, selectedTicketTypeId]
133871
134204
  );
134205
+ const selectedSlidingAmount = useMemo(
134206
+ () => parseCurrencyInputToCents(selectedSlidingAmountInput),
134207
+ [selectedSlidingAmountInput]
134208
+ );
134209
+ const selectedEventSlidingScale = useMemo(
134210
+ () => getSlidingScaleForEvent(selectedEvent ?? preSelectedEvent),
134211
+ [selectedEvent, preSelectedEvent]
134212
+ );
134213
+ const slidingScaleSelectionMessage = getSlidingScaleSelectionMessage({
134214
+ slidingScale: selectedEventSlidingScale,
134215
+ selectedAmount: selectedSlidingAmount
134216
+ });
133872
134217
  const ticketTypeSelectionRequired = useMemo(() => {
133873
134218
  const event = selectedEvent ?? preSelectedEvent;
133874
134219
  if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
@@ -133937,12 +134282,24 @@ function useEventRegistrationWizard(options) {
133937
134282
  setGuestSectionShake(false);
133938
134283
  };
133939
134284
  const eventRequiresPayment = useMemo(
133940
- () => resolveEventRequiresPayment({
134285
+ () => {
134286
+ if (selectedEventSlidingScale) {
134287
+ return (selectedSlidingAmount ?? 0) > 0;
134288
+ }
134289
+ return resolveEventRequiresPayment({
134290
+ eligibility,
134291
+ selectedTicketType,
134292
+ event: selectedEvent ?? preSelectedEvent
134293
+ });
134294
+ },
134295
+ [
133941
134296
  eligibility,
133942
134297
  selectedTicketType,
133943
- event: selectedEvent ?? preSelectedEvent
133944
- }),
133945
- [eligibility, selectedTicketType, selectedEvent, preSelectedEvent]
134298
+ selectedEvent,
134299
+ preSelectedEvent,
134300
+ selectedEventSlidingScale,
134301
+ selectedSlidingAmount
134302
+ ]
133946
134303
  );
133947
134304
  const handleDetailsComplete = async () => {
133948
134305
  if (ticketTypeSelectionRequired && !hasValidSelectedTicketType) {
@@ -133951,6 +134308,11 @@ function useEventRegistrationWizard(options) {
133951
134308
  setStep("error");
133952
134309
  return { ok: false, reason: "blocked", message: message2 };
133953
134310
  }
134311
+ if (slidingScaleSelectionMessage) {
134312
+ setErrorMessage(slidingScaleSelectionMessage);
134313
+ setStep("error");
134314
+ return { ok: false, reason: "blocked", message: slidingScaleSelectionMessage };
134315
+ }
133954
134316
  if (!validateForm()) {
133955
134317
  setGuestSectionShake(true);
133956
134318
  return {
@@ -134035,9 +134397,18 @@ function useEventRegistrationWizard(options) {
134035
134397
  const discountSubtotalCents = useMemo(() => {
134036
134398
  if (discountContext?.type !== "event") return void 0;
134037
134399
  const event = selectedEvent ?? preSelectedEvent;
134038
- const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
134400
+ const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (selectedEventSlidingScale ? selectedSlidingAmount : null) ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
134039
134401
  return Math.max(0, eventPriceCents * ticketsCount);
134040
- }, [discountContext, selectedTicketType, eligibility, selectedEvent, preSelectedEvent, ticketsCount]);
134402
+ }, [
134403
+ discountContext,
134404
+ selectedTicketType,
134405
+ eligibility,
134406
+ selectedEvent,
134407
+ preSelectedEvent,
134408
+ selectedEventSlidingScale,
134409
+ selectedSlidingAmount,
134410
+ ticketsCount
134411
+ ]);
134041
134412
  const discountUi = useDiscountCodeUi({ api, discountContext, subtotalCents: discountSubtotalCents });
134042
134413
  const selectionMethod = paymentSelection.method;
134043
134414
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
@@ -134046,6 +134417,7 @@ function useEventRegistrationWizard(options) {
134046
134417
  registrationTarget,
134047
134418
  selectedAttendanceMode,
134048
134419
  selectedTicketTypeId,
134420
+ selectedSlidingAmount,
134049
134421
  ticketsCount,
134050
134422
  formData,
134051
134423
  altchaSolution,
@@ -134060,6 +134432,10 @@ function useEventRegistrationWizard(options) {
134060
134432
  });
134061
134433
  const handleEventSelect = (event) => {
134062
134434
  setSelectedEvent(event);
134435
+ const slidingScale = getSlidingScaleForEvent(event);
134436
+ setSelectedSlidingAmountInput(
134437
+ slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
134438
+ );
134063
134439
  setSelectedAttendanceMode(
134064
134440
  (event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person"
134065
134441
  );
@@ -134069,6 +134445,7 @@ function useEventRegistrationWizard(options) {
134069
134445
  const handleRetry = () => {
134070
134446
  dispatchFlow({ type: "reset", hasPreSelectedContext });
134071
134447
  setSelectedTicketTypeId(null);
134448
+ setSelectedSlidingAmountInput("");
134072
134449
  setSelectedAttendanceMode(
134073
134450
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
134074
134451
  );
@@ -134105,6 +134482,11 @@ function useEventRegistrationWizard(options) {
134105
134482
  setSelectedAttendanceMode,
134106
134483
  selectedTicketTypeId,
134107
134484
  setSelectedTicketTypeId,
134485
+ selectedSlidingAmountInput,
134486
+ setSelectedSlidingAmountInput,
134487
+ selectedSlidingAmount,
134488
+ selectedEventSlidingScale,
134489
+ slidingScaleSelectionMessage,
134108
134490
  selectedTicketType,
134109
134491
  visibleTicketTypes,
134110
134492
  ticketTypeSelectionRequired,
@@ -135185,6 +135567,44 @@ function CoursePricingSelector({
135185
135567
  )
135186
135568
  ] });
135187
135569
  }
135570
+ function CourseSlidingScaleInput({
135571
+ pricing,
135572
+ value,
135573
+ onChange
135574
+ }) {
135575
+ const slidingScale = pricing.slidingScale;
135576
+ if (!slidingScale) {
135577
+ return null;
135578
+ }
135579
+ const minLabel = formatCoursePriceAmount(slidingScale.minAmount, pricing.currency);
135580
+ const maxLabel = formatCoursePriceAmount(slidingScale.maxAmount, pricing.currency);
135581
+ const suggestedLabel = slidingScale.suggestedAmount !== null ? formatCoursePriceAmount(slidingScale.suggestedAmount, pricing.currency) : null;
135582
+ return /* @__PURE__ */ jsxs("div", { className: "cr-form__field", children: [
135583
+ /* @__PURE__ */ jsx("label", { htmlFor: "cr-sliding-amount", className: "cr-form__label", children: "Amount" }),
135584
+ /* @__PURE__ */ jsx(
135585
+ "input",
135586
+ {
135587
+ id: "cr-sliding-amount",
135588
+ type: "number",
135589
+ min: slidingScale.minAmount / 100,
135590
+ max: slidingScale.maxAmount / 100,
135591
+ step: "0.01",
135592
+ inputMode: "decimal",
135593
+ className: "cr-form__input",
135594
+ value,
135595
+ onChange: (event) => onChange(event.target.value)
135596
+ }
135597
+ ),
135598
+ /* @__PURE__ */ jsxs("p", { className: "cr-text cr-text--muted", children: [
135599
+ "Choose between ",
135600
+ minLabel,
135601
+ " and ",
135602
+ maxLabel,
135603
+ suggestedLabel ? `; suggested ${suggestedLabel}` : "",
135604
+ "."
135605
+ ] })
135606
+ ] });
135607
+ }
135188
135608
  function EnrollmentForm({ formData, errors, onChange, isEmailLocked, isLoading, onLogout }) {
135189
135609
  const nameErrorId = "cr-name-error";
135190
135610
  const emailErrorId = "cr-email-error";
@@ -135267,6 +135687,17 @@ function EnrollmentForm({ formData, errors, onChange, isEmailLocked, isLoading,
135267
135687
  ] })
135268
135688
  ] });
135269
135689
  }
135690
+ function parseCurrencyInputToCents2(value) {
135691
+ const trimmed = value.trim();
135692
+ if (!trimmed) return null;
135693
+ const amount = Number(trimmed);
135694
+ if (!Number.isFinite(amount) || amount < 0) return null;
135695
+ return Math.round(amount * 100);
135696
+ }
135697
+ function formatCentsAsCurrencyInput2(amount) {
135698
+ const major = amount / 100;
135699
+ return Number.isInteger(major) ? String(major) : major.toFixed(2);
135700
+ }
135270
135701
  function shouldFetchCourseEligibility(course, hasPaidSelection, portalAuthState, hasWaitlistClaimToken) {
135271
135702
  if (!course || portalAuthState === "checking") return false;
135272
135703
  if (hasWaitlistClaimToken) return false;
@@ -135284,12 +135715,27 @@ function getSessionIdFromUrl() {
135284
135715
  }
135285
135716
  function getMissingCoursePricingSelectionMessage(pricing) {
135286
135717
  if (pricing?.requiresSelection && !pricing.selectedOption) {
135718
+ if (pricing.slidingScale) {
135719
+ return "Choose an amount to continue.";
135720
+ }
135287
135721
  return "Choose a pricing option to continue.";
135288
135722
  }
135723
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount < pricing.slidingScale.minAmount) {
135724
+ return "Choose an amount within the sliding scale.";
135725
+ }
135726
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null && pricing.selectedSlidingAmount > pricing.slidingScale.maxAmount) {
135727
+ return "Choose an amount within the sliding scale.";
135728
+ }
135289
135729
  return null;
135290
135730
  }
135291
135731
  function buildCoursePricingOptionPayload(pricing) {
135292
- return pricing?.selectedOption ? { pricingOptionId: pricing.selectedOption.id } : {};
135732
+ if (pricing?.selectedOption) {
135733
+ return { pricingOptionId: pricing.selectedOption.id };
135734
+ }
135735
+ if (pricing?.slidingScale && pricing.selectedSlidingAmount !== null) {
135736
+ return { selectedSlidingAmount: pricing.selectedSlidingAmount };
135737
+ }
135738
+ return {};
135293
135739
  }
135294
135740
  function resolveHasCoursePayment(pricing) {
135295
135741
  if (!pricing) {
@@ -135330,6 +135776,7 @@ function useCourseRegistrationWizard(props2) {
135330
135776
  scopedCourses.length === 1 ? scopedCourses[0] ?? null : null
135331
135777
  );
135332
135778
  const [selectedPricingOptionId, setSelectedPricingOptionId] = useState(null);
135779
+ const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState("");
135333
135780
  const [formData, setFormData] = useState({
135334
135781
  name: "",
135335
135782
  email: "",
@@ -135351,8 +135798,12 @@ function useCourseRegistrationWizard(props2) {
135351
135798
  const [bookingMode, setBookingMode] = useState(null);
135352
135799
  const waitlistClaim = useWaitlistClaim();
135353
135800
  const selectedCoursePricing = useMemo(
135354
- () => selectedCourse ? resolveCoursePricingState(selectedCourse, selectedPricingOptionId) : null,
135355
- [selectedCourse, selectedPricingOptionId]
135801
+ () => selectedCourse ? resolveCoursePricingState(
135802
+ selectedCourse,
135803
+ selectedPricingOptionId,
135804
+ parseCurrencyInputToCents2(selectedSlidingAmountInput)
135805
+ ) : null,
135806
+ [selectedCourse, selectedPricingOptionId, selectedSlidingAmountInput]
135356
135807
  );
135357
135808
  const missingPricingSelectionMessage = getMissingCoursePricingSelectionMessage(
135358
135809
  selectedCoursePricing
@@ -135425,9 +135876,13 @@ function useCourseRegistrationWizard(props2) {
135425
135876
  useEffect(() => {
135426
135877
  if (!selectedCourse) {
135427
135878
  setSelectedPricingOptionId(null);
135879
+ setSelectedSlidingAmountInput("");
135428
135880
  return;
135429
135881
  }
135430
135882
  const defaultPricing = resolveCoursePricingState(selectedCourse, null);
135883
+ setSelectedSlidingAmountInput(
135884
+ defaultPricing.slidingScale?.suggestedAmount !== null && defaultPricing.slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput2(defaultPricing.slidingScale.suggestedAmount) : ""
135885
+ );
135431
135886
  setSelectedPricingOptionId((previous) => {
135432
135887
  if (previous && defaultPricing.selectableOptions.some((option) => option.id === previous)) {
135433
135888
  return previous;
@@ -135802,6 +136257,8 @@ function useCourseRegistrationWizard(props2) {
135802
136257
  selectedCourse,
135803
136258
  selectedPricingOptionId,
135804
136259
  setSelectedPricingOptionId,
136260
+ selectedSlidingAmountInput,
136261
+ setSelectedSlidingAmountInput,
135805
136262
  selectedCoursePricing,
135806
136263
  setSelectedCourse,
135807
136264
  formData,
@@ -135849,6 +136306,19 @@ function useCourseRegistrationWizard(props2) {
135849
136306
  handleBookingModeChange
135850
136307
  };
135851
136308
  }
136309
+ function getCourseSlidingScaleMessage(pricing) {
136310
+ if (!pricing.slidingScale) {
136311
+ return null;
136312
+ }
136313
+ const selectedAmount = pricing.selectedSlidingAmount;
136314
+ if (selectedAmount === null) {
136315
+ return "Choose an amount to continue.";
136316
+ }
136317
+ if (selectedAmount < pricing.slidingScale.minAmount || selectedAmount > pricing.slidingScale.maxAmount) {
136318
+ return "Choose an amount within the sliding scale.";
136319
+ }
136320
+ return null;
136321
+ }
135852
136322
  function CourseRegistrationWizard(props2) {
135853
136323
  const {
135854
136324
  showSessions = true,
@@ -135870,6 +136340,8 @@ function CourseRegistrationWizard(props2) {
135870
136340
  selectedCourse,
135871
136341
  selectedPricingOptionId,
135872
136342
  setSelectedPricingOptionId,
136343
+ selectedSlidingAmountInput,
136344
+ setSelectedSlidingAmountInput,
135873
136345
  selectedCoursePricing,
135874
136346
  formData,
135875
136347
  setFormData,
@@ -135940,7 +136412,8 @@ function CourseRegistrationWizard(props2) {
135940
136412
  const canShowDiscountUi = !discountsAvailableLoading && paymentChoice === "checkout" && discountsAvailable && manualCodeEntryAllowed;
135941
136413
  const courseSoldOut = waitlistClaim.claim === null && courseEligibility?.isFull === true && courseEligibility.canJoinWaitlist !== true;
135942
136414
  const detailsSubmitLabel = courseEligibility?.canJoinWaitlist ? "Join waitlist" : buttonText;
135943
- const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption;
136415
+ const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption && !selectedCoursePricing.slidingScale;
136416
+ const slidingScaleMessage = selectedCoursePricing ? getCourseSlidingScaleMessage(selectedCoursePricing) : null;
135944
136417
  const pricingUnavailable = selectedCoursePricing?.display.kind === "unavailable";
135945
136418
  const selectedCourseWithPricing = selectedCourse && selectedCoursePricing ? {
135946
136419
  course: selectedCourse,
@@ -136033,6 +136506,14 @@ function CourseRegistrationWizard(props2) {
136033
136506
  onSelect: setSelectedPricingOptionId
136034
136507
  }
136035
136508
  ),
136509
+ /* @__PURE__ */ jsx(
136510
+ CourseSlidingScaleInput,
136511
+ {
136512
+ pricing: selectedCourseWithPricing.pricing,
136513
+ value: selectedSlidingAmountInput,
136514
+ onChange: setSelectedSlidingAmountInput
136515
+ }
136516
+ ),
136036
136517
  showSpamProtection && /* @__PURE__ */ jsx("div", { className: "cr-altcha", children: /* @__PURE__ */ jsx(
136037
136518
  AltchaWidget,
136038
136519
  {
@@ -136070,7 +136551,7 @@ function CourseRegistrationWizard(props2) {
136070
136551
  {
136071
136552
  type: "submit",
136072
136553
  className: buttonClass,
136073
- disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || pricingUnavailable,
136554
+ disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || slidingScaleMessage !== null || pricingUnavailable,
136074
136555
  children: eligibilityLoading ? "Checking availability\u2026" : courseSoldOut ? "Sold out" : detailsSubmitLabel
136075
136556
  }
136076
136557
  )
@@ -136080,6 +136561,7 @@ function CourseRegistrationWizard(props2) {
136080
136561
  ),
136081
136562
  courseSoldOut && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--muted", children: "This course is fully booked and there is no waitlist available right now." }),
136082
136563
  pricingSelectionMissing && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--muted", children: "Choose a pricing option to continue." }),
136564
+ slidingScaleMessage && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--muted", children: slidingScaleMessage }),
136083
136565
  pricingUnavailable && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--muted", children: "Pricing is not available for this course right now." }),
136084
136566
  eligibilityError && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--error", children: eligibilityError })
136085
136567
  ] })
@@ -137779,12 +138261,13 @@ var CarouselNodeClient = ({
137779
138261
  ]
137780
138262
  );
137781
138263
  const resolvedSlides = resolveSlidesToShowMobile(normalized.slidesToShow);
138264
+ const canAutoplay = normalized.autoplay && slides.length > 1;
137782
138265
  const cssVars = React61__default.useMemo(
137783
138266
  () => getCarouselSlidesToShowCssVars(normalized.slidesToShow),
137784
138267
  [normalized.slidesToShow]
137785
138268
  );
137786
138269
  const plugins = React61__default.useMemo(() => {
137787
- if (!normalized.autoplay) return void 0;
138270
+ if (!canAutoplay) return void 0;
137788
138271
  return [
137789
138272
  Autoplay({
137790
138273
  delay: normalized.autoplayDelay,
@@ -137794,7 +138277,7 @@ var CarouselNodeClient = ({
137794
138277
  // Continue after manual navigation
137795
138278
  })
137796
138279
  ];
137797
- }, [normalized.autoplay, normalized.autoplayDelay]);
138280
+ }, [canAutoplay, normalized.autoplayDelay]);
137798
138281
  const [viewportRef, api] = useEmblaCarousel(
137799
138282
  {
137800
138283
  align: "start",
@@ -137833,7 +138316,7 @@ var CarouselNodeClient = ({
137833
138316
  };
137834
138317
  }, [api]);
137835
138318
  React61__default.useEffect(() => {
137836
- if (!api || !normalized.autoplay) return;
138319
+ if (!api || !canAutoplay) return;
137837
138320
  const autoplayPlugin = api.plugins()?.autoplay;
137838
138321
  if (!autoplayPlugin) return;
137839
138322
  const handleVisibility = () => {
@@ -137842,7 +138325,7 @@ var CarouselNodeClient = ({
137842
138325
  };
137843
138326
  document.addEventListener("visibilitychange", handleVisibility);
137844
138327
  return () => document.removeEventListener("visibilitychange", handleVisibility);
137845
- }, [api, normalized.autoplay]);
138328
+ }, [api, canAutoplay]);
137846
138329
  const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
137847
138330
  /* @__PURE__ */ jsx("div", { className: "carousel-frame rb-overflow-hidden rb-w-full", children: /* @__PURE__ */ jsx(
137848
138331
  "div",
@@ -137893,7 +138376,7 @@ var CarouselNodeClient = ({
137893
138376
  "data-show-controls": normalized.showControls ? "true" : "false",
137894
138377
  "data-show-dots": normalized.showDots ? "true" : "false",
137895
138378
  "data-arrow-visibility": normalized.arrowVisibility,
137896
- "data-autoplay": normalized.autoplay ? "true" : "false",
138379
+ "data-autoplay": canAutoplay ? "true" : "false",
137897
138380
  "data-loop": normalized.loop ? "true" : "false",
137898
138381
  children: inner
137899
138382
  }
@@ -149507,6 +149990,15 @@ function fromArray(items) {
149507
149990
  if (first2 === void 0) return err3("empty_array");
149508
149991
  return ok$1([first2, ...rest]);
149509
149992
  }
149993
+ function isPresent(value) {
149994
+ return value !== null && value !== void 0;
149995
+ }
149996
+ function isNonEmptyString(value) {
149997
+ return typeof value === "string" && value.length > 0;
149998
+ }
149999
+ function hasText(value) {
150000
+ return isNonEmptyString(value);
150001
+ }
149510
150002
  var IMAGE_FILE_FORMATS = [
149511
150003
  "jpeg",
149512
150004
  "png",
@@ -149934,14 +150426,14 @@ function assertRelativeStoragePath(value) {
149934
150426
  }
149935
150427
  var pad = (value) => value.toString().padStart(2, "0");
149936
150428
  var extractExtension = (filename) => {
149937
- if (!filename) return "";
150429
+ if (!hasText(filename)) return "";
149938
150430
  const lastDot = filename.lastIndexOf(".");
149939
150431
  if (lastDot === -1) return "";
149940
150432
  return filename.slice(lastDot).toLowerCase();
149941
150433
  };
149942
150434
  var generateId4 = () => {
149943
150435
  const crypto2 = globalThis.crypto;
149944
- if (crypto2 && typeof crypto2.getRandomValues === "function") {
150436
+ if (isPresent(crypto2) && typeof crypto2.getRandomValues === "function") {
149945
150437
  const array = crypto2.getRandomValues(/* @__PURE__ */ new Uint32Array(4));
149946
150438
  return Array.from(array).map((value) => value.toString(16).padStart(8, "0")).join("-");
149947
150439
  }
@@ -157591,9 +158083,9 @@ var DEFAULT_MESSAGES = {
157591
158083
  function isRecord22(value) {
157592
158084
  return typeof value === "object" && value !== null;
157593
158085
  }
157594
- var normalizeLabels = (labels) => labels && labels.length ? labels.map((label) => label.trim()).filter(Boolean).join(",") : void 0;
158086
+ var normalizeLabels = (labels) => labels !== void 0 && labels.length > 0 ? labels.map((label) => label.trim()).filter((label) => label.length > 0).join(",") : void 0;
157595
158087
  var fileFilterForMediaKinds = (mediaKinds) => {
157596
- if (!mediaKinds || mediaKinds.length !== 1) return void 0;
158088
+ if (mediaKinds === void 0 || mediaKinds.length !== 1) return void 0;
157597
158089
  const [mediaKind] = mediaKinds;
157598
158090
  if (mediaKind === void 0) return void 0;
157599
158091
  switch (mediaKind) {
@@ -157750,7 +158242,7 @@ function createCachedMediaRepo(baseRepo, options) {
157750
158242
  }
157751
158243
  function readCache(key, cache) {
157752
158244
  const entry = cache.get(key);
157753
- if (!entry) return null;
158245
+ if (entry === void 0) return null;
157754
158246
  if (entry.expiresAt <= Date.now()) {
157755
158247
  cache.delete(key);
157756
158248
  return null;
@@ -157772,10 +158264,10 @@ function createCachedMediaRepo(baseRepo, options) {
157772
158264
  async getAsset(assetId) {
157773
158265
  const cacheKey = `asset:${assetId}`;
157774
158266
  const entry = globalAssetCache.get(cacheKey);
157775
- if (entry) if (entry.expiresAt > Date.now()) return entry.data;
158267
+ if (entry !== void 0) if (entry.expiresAt > Date.now()) return entry.data;
157776
158268
  else globalAssetCache.delete(cacheKey);
157777
158269
  const inflightRequest = globalAssetInflightRequests.get(assetId);
157778
- if (inflightRequest) return inflightRequest;
158270
+ if (inflightRequest !== void 0) return inflightRequest;
157779
158271
  const fetchPromise = baseRepo.getAsset(assetId).then((result) => {
157780
158272
  writeCache(cacheKey, result, globalAssetCache);
157781
158273
  globalAssetInflightRequests.delete(assetId);
@@ -157790,7 +158282,7 @@ function createCachedMediaRepo(baseRepo, options) {
157790
158282
  async listAssets(options2) {
157791
158283
  const cacheKey = buildCacheKey(options2);
157792
158284
  const cached = readCache(cacheKey, globalListCache);
157793
- if (cached) return cached;
158285
+ if (cached !== null) return cached;
157794
158286
  const result = await baseRepo.listAssets(options2);
157795
158287
  writeCache(cacheKey, result, globalListCache);
157796
158288
  return result;