@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.
- package/dist/{PreviewEditorSidebar-FNFRVSIM.mjs → PreviewEditorSidebar-3CI5MKV3.mjs} +3 -3
- package/dist/{PreviewEditorUI-3WXJFQXG.mjs → PreviewEditorUI-DKEP636I.mjs} +3 -3
- package/dist/SdkPreviewModeRuntime-BRGRDQFH.mjs +8 -0
- package/dist/_dts/api/src/bookingConfig.d.ts +13 -1
- package/dist/_dts/api/src/bookingOperations.d.ts +1 -0
- package/dist/_dts/api/src/bookingRefundOutcomes.d.ts +6 -6
- package/dist/_dts/api/src/customerMembershipOutcomes.d.ts +4 -4
- package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +6 -0
- package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +32 -0
- package/dist/_dts/blocks/src/system/blocks/courses/shared/pricing.d.ts +12 -1
- package/dist/_dts/blocks/src/system/blocks/courses/shared/schemas.d.ts +12 -0
- package/dist/_dts/blocks/src/system/blocks/courses/shared/types.d.ts +4 -1
- package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +44 -0
- package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +24 -0
- package/dist/_dts/blocks/src/system/runtime/api/types.d.ts +8 -1
- package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.d.ts +8 -1
- package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.state.d.ts +1 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +4 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.interactive.d.ts +40 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.reserved-fields.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +29 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-runtime-sources.d.ts +1 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/course-registration/components.d.ts +6 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/course-registration/useCourseRegistrationWizard.d.ts +8 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/TicketSelectionStep.d.ts +8 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +1 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +1 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +9 -0
- package/dist/_dts/content-model/src/entry-slots/types.d.ts +110 -2
- package/dist/_dts/core/src/branded.d.ts +3 -0
- package/dist/_dts/core/src/event-pricing.d.ts +18 -1
- package/dist/_dts/core/src/money-input.d.ts +34 -4
- package/dist/_dts/core/src/string.d.ts +12 -0
- package/dist/_dts/db/src/generated/supabase/database.types.d.ts +70 -0
- package/dist/_dts/media-core/src/fileAssetCompatibility.d.ts +1 -1
- package/dist/_dts/media-core/src/filePersistence.d.ts +5 -1
- package/dist/_dts/media-core/src/fileStorage.d.ts +1 -0
- package/dist/_dts/media-core/src/videoPrep.d.ts +15 -6
- package/dist/_dts/media-core/src/videoSlotPolicy.d.ts +5 -5
- package/dist/_dts/sdk/src/cli/commands/push/execute/diff.d.ts +2 -0
- package/dist/_dts/sdk/src/cli/commands/push/execute/localMediaReporting.d.ts +23 -0
- package/dist/_dts/sdk/src/cli/commands/push/scopes/commandBackedDatedOfferingScope.d.ts +54 -0
- package/dist/_dts/sdk/src/cli/commands/push/scopes/mediaPrep.d.ts +7 -2
- package/dist/_dts/sdk/src/cli/commands/push/scopes/types.d.ts +6 -0
- package/dist/_dts/sdk/src/cli/media/local-sync.d.ts +35 -0
- package/dist/_dts/sdk/src/cli/sync/validation.d.ts +2 -2
- package/dist/_dts/sdk/src/rendering/islands/types.d.ts +23 -1
- package/dist/_dts/sdk/src/version.d.ts +1 -1
- package/dist/{chunk-SYYJZPKU.mjs → chunk-6D7PLF4M.mjs} +2 -2
- package/dist/{chunk-CR2VX432.mjs → chunk-I6EOKV6B.mjs} +568 -76
- package/dist/{chunk-FA6LVZ7C.mjs → chunk-LERKJM3E.mjs} +7 -7
- package/dist/{chunk-UG7I7S7W.mjs → chunk-U5YM4AWE.mjs} +1 -1
- package/dist/{chunk-H6TT5ZXI.mjs → chunk-UOMTCHZD.mjs} +1 -1
- package/dist/{chunk-BOQXMCSP.mjs → chunk-ZA2S7H34.mjs} +3 -3
- package/dist/cli/index.mjs +755 -414
- package/dist/cli/init-docs/content/workflow-add-block.md +48 -0
- package/dist/cli/init-docs/content/workflow-custom-block.md +10 -0
- package/dist/client/{accordion-4K7VGZ3I.mjs → accordion-BUDH2G3S.mjs} +2 -2
- package/dist/client/{bookingForm-3QXA3CIS.mjs → bookingForm-NQMCELVB.mjs} +90 -15
- package/dist/client/bookings.mjs +259 -32
- package/dist/client/{carousel-BEJKNGQ7.mjs → carousel-ONCEW6IJ.mjs} +3 -3
- package/dist/client/{cart-44BBM6XG.mjs → cart-V2TCK2GI.mjs} +7 -7
- package/dist/client/{checkout-THMHEYKF.mjs → checkout-ZI7SWNCS.mjs} +7 -7
- package/dist/client/{chunk-PEDNDLPB.mjs → chunk-AUMRQ242.mjs} +52 -5
- package/dist/client/{chunk-EJBHK74Y.mjs → chunk-B5UC2D6X.mjs} +7 -6
- package/dist/client/{chunk-DQIRKNZE.mjs → chunk-ERQDHUS2.mjs} +1 -1
- package/dist/client/{chunk-3H5CLFKT.mjs → chunk-GH6IIOOE.mjs} +39 -10
- package/dist/client/{chunk-ZIAF2DA4.mjs → chunk-HQWIJLYN.mjs} +1 -1
- package/dist/client/{chunk-4COPESPO.mjs → chunk-JXY6NHG6.mjs} +6 -6
- package/dist/client/{chunk-JV7ZNAOU.mjs → chunk-KXYEKEV6.mjs} +155 -17
- package/dist/client/{chunk-HIHOQGIP.mjs → chunk-LJCX6V7Q.mjs} +9 -9
- package/dist/client/{chunk-5AJIMWKS.mjs → chunk-N2WJPTF7.mjs} +3 -3
- package/dist/client/{chunk-KNTLBQ3N.mjs → chunk-QQNVQ62W.mjs} +1 -1
- package/dist/client/{chunk-B4H6QMPY.mjs → chunk-R3CZQLXF.mjs} +73 -8
- package/dist/client/{chunk-WHZQDPBZ.mjs → chunk-TNGL2WJF.mjs} +1 -0
- package/dist/client/{chunk-SLY6UDAS.mjs → chunk-ZJDBOLX7.mjs} +3 -3
- package/dist/client/client.mjs +548 -68
- package/dist/client/{courseRegistration-URX7RCCK.mjs → courseRegistration-JPWAQ6KM.mjs} +112 -12
- package/dist/client/{eventCalendar-GTENOQCE.mjs → eventCalendar-QDOCHBBV.mjs} +10 -10
- package/dist/client/{eventCombined-EZVB72MF.mjs → eventCombined-ZS2CXSIE.mjs} +10 -10
- package/dist/client/{eventDetails-CH4BJR5Z.mjs → eventDetails-5LOBMPLN.mjs} +10 -10
- package/dist/client/{eventListing-4BVOI5HS.mjs → eventListing-SBGY4I3L.mjs} +10 -10
- package/dist/client/{eventRegistration-37LPIKIE.mjs → eventRegistration-XGPIU3Z2.mjs} +10 -10
- package/dist/client/{form-FX3Y54YK.mjs → form-R7D4LZWW.mjs} +4 -4
- package/dist/client/{headerSection-UOZZGVYL.mjs → headerSection-RRLYUCGC.mjs} +3 -3
- package/dist/client/hooks.mjs +29 -9
- package/dist/client/{locationMap-VSIPKV35.mjs → locationMap-35ZJMRBZ.mjs} +3 -3
- package/dist/client/{newsletterForm-6FRJKNAF.mjs → newsletterForm-MJ2VCFUT.mjs} +4 -4
- package/dist/client/{productDetail-6AHMLXCO.mjs → productDetail-2O7YQNBX.mjs} +7 -7
- package/dist/client/{productList-M2CYIZVH.mjs → productList-FDP5BH7W.mjs} +7 -7
- package/dist/client/rendering/client.mjs +526 -64
- package/dist/client/rendering/islands.mjs +19 -19
- package/dist/client/rendering.mjs +547 -67
- package/dist/client/{shop-AUOT6GEX.mjs → shop-BGYJ3LDT.mjs} +7 -7
- package/dist/client/spam-protection.mjs +10 -10
- package/dist/preview-next/client/runtime.mjs +5 -5
- package/dist/{sdk-runtime-6GPJNLU6.mjs → sdk-runtime-B3TFXRXZ.mjs} +6 -6
- package/dist/server/components.mjs +179 -18
- package/dist/server/config-validation.mjs +30 -9
- package/dist/server/config.mjs +30 -9
- package/dist/server/data.mjs +29 -9
- package/dist/server/index.mjs +1 -1
- package/dist/server/navigation.mjs +2 -2
- package/dist/server/next.mjs +180 -19
- package/dist/server/page-converter.mjs +24 -7
- package/dist/server/prebuild.mjs +1 -1
- package/dist/server/rendering/server.mjs +179 -18
- package/dist/server/rendering.mjs +179 -18
- package/dist/server/routing.mjs +43 -19
- package/dist/server/server.mjs +30 -10
- package/package.json +1 -1
- package/dist/SdkPreviewModeRuntime-2UCIMKUB.mjs +0 -8
|
@@ -49,6 +49,16 @@ riverbankcms block list home
|
|
|
49
49
|
riverbankcms block upsert <page-id> <block-id> --kind <block-kind> --data '<json>'
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
### Authoring Block JSON
|
|
53
|
+
|
|
54
|
+
Use `block-types.md` and `block-types-site.md` as the source of truth for fields.
|
|
55
|
+
|
|
56
|
+
- Fields marked `required` must be present with a valid value.
|
|
57
|
+
- Fields not marked `required` may be omitted. You do not need to include every optional key or set unused optional values to `null`.
|
|
58
|
+
- Defaults shown in the block reference are applied by the editor/rendering contract. Include an optional field only when you want to override that default.
|
|
59
|
+
- For nested objects and repeaters, required child fields apply once you include the parent value. For example, an omitted `images` repeater is valid, but `images: [{}]` is not because the item is present and its required `image` field is missing.
|
|
60
|
+
- `null` is only valid for field types that explicitly accept clearing to no value. When in doubt, omit optional fields instead of sending `null`.
|
|
61
|
+
|
|
52
62
|
**Examples:**
|
|
53
63
|
|
|
54
64
|
Add a hero block:
|
|
@@ -59,6 +69,44 @@ riverbankcms block upsert home main-hero \
|
|
|
59
69
|
--data '{"heading": "Welcome", "subheading": "to our website"}'
|
|
60
70
|
```
|
|
61
71
|
|
|
72
|
+
Add a minimal media and text block:
|
|
73
|
+
|
|
74
|
+
`block.media-text` has many display and carousel fields, but most are optional controls with defaults. A minimal content-only block only needs rich text:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"text": {
|
|
79
|
+
"type": "doc",
|
|
80
|
+
"content": [
|
|
81
|
+
{
|
|
82
|
+
"type": "paragraph",
|
|
83
|
+
"content": [{ "type": "text", "text": "Welcome to our space." }]
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Add media only when you have a complete media item:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"text": {
|
|
95
|
+
"type": "doc",
|
|
96
|
+
"content": [{ "type": "paragraph", "content": [] }]
|
|
97
|
+
},
|
|
98
|
+
"images": [
|
|
99
|
+
{
|
|
100
|
+
"image": {
|
|
101
|
+
"type": "image",
|
|
102
|
+
"src": "/images/studio.jpg",
|
|
103
|
+
"alt": "A bright studio room"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
62
110
|
Add a body text block:
|
|
63
111
|
|
|
64
112
|
```bash
|
|
@@ -105,6 +105,16 @@ Common field types for custom blocks:
|
|
|
105
105
|
| `repeater` | List of nested fields | `schema`, `minItems`, `maxItems` |
|
|
106
106
|
| `group` | Grouped fields | `schema` |
|
|
107
107
|
|
|
108
|
+
### Step 2.5: Model Required Fields Carefully
|
|
109
|
+
|
|
110
|
+
The same content authoring rules apply to custom blocks and system blocks:
|
|
111
|
+
|
|
112
|
+
- Leave `required` unset or `false` for fields that can be omitted from pushed JSON.
|
|
113
|
+
- Use `defaultValue` for optional controls that should have a stable editor/rendering default.
|
|
114
|
+
- Set `required: true` only when every pushed block must provide that field.
|
|
115
|
+
- For repeaters and groups, required child fields apply when the parent item/object is present. If a repeater item has `{ id: 'title', required: true }`, then every item in that repeater must include `title`.
|
|
116
|
+
- In React overrides, type optional fields as optional and handle missing values with defaults or conditional rendering.
|
|
117
|
+
|
|
108
118
|
### Step 3: Create React Override Component
|
|
109
119
|
|
|
110
120
|
Create a component to render your block:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { defineIslandPropsDecoder } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { defineIslandPropsDecoder } from './chunk-R3CZQLXF.mjs';
|
|
2
|
+
import './chunk-GH6IIOOE.mjs';
|
|
3
3
|
|
|
4
4
|
// src/rendering/islands/enhancers/accordion.ts
|
|
5
5
|
var decode = defineIslandPropsDecoder("accordion");
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { buildBookingPaymentSelectionViewModel, usePolling } from './chunk-
|
|
3
|
-
import { SpinnerNode } from './chunk-
|
|
2
|
+
import { buildBookingPaymentSelectionViewModel, usePolling } from './chunk-N2WJPTF7.mjs';
|
|
3
|
+
import { SpinnerNode } from './chunk-QQNVQ62W.mjs';
|
|
4
4
|
import { buildRouteIslandApiConfig } from './chunk-L4J5Y62O.mjs';
|
|
5
|
-
import { scrollToBlock, useDiscountCodeUi, buildCheckoutReturnUrls, DiscountCodeDisclosure } from './chunk-
|
|
5
|
+
import { scrollToBlock, useDiscountCodeUi, buildCheckoutReturnUrls, DiscountCodeDisclosure } from './chunk-ZJDBOLX7.mjs';
|
|
6
6
|
import { FieldValidationMessage, getFieldValidationControlProps } from './chunk-3DCJ45Z2.mjs';
|
|
7
7
|
import './chunk-WBQG7VJA.mjs';
|
|
8
|
-
import { isSpamProtectionEnabled, getChallengeUrl, AltchaWidget } from './chunk-
|
|
9
|
-
import { BlockApiProvider, useBlockApi, FeedbackMessage } from './chunk-
|
|
8
|
+
import { isSpamProtectionEnabled, getChallengeUrl, AltchaWidget } from './chunk-LJCX6V7Q.mjs';
|
|
9
|
+
import { BlockApiProvider, useBlockApi, FeedbackMessage } from './chunk-TNGL2WJF.mjs';
|
|
10
10
|
import { mountReactIsland } from './chunk-N7PBWBWQ.mjs';
|
|
11
|
-
import { defineIslandPropsDecoder, resolveBookingSiteId, resolveDefaultBookingServiceId, getBookingRedirectStateFromLocation, deriveBookingFormViewState, logger, getBookingSuccessMessage, resolveAppointmentCheckoutVerificationState, themeButtonClassName, toSelectedCollectionOptionKind, assertNever, prepareBookingSubmission, formatBookingSubmissionPreparationError, resolveBookingPaymentStep, BOOKING_FORM_DATA_RESERVED_KEYS, clearWorkflowFeedback, reportWorkflowError, reportWorkflowSuccess, okWith, err } from './chunk-
|
|
12
|
-
import { resolveBookingTimeZoneId, DEFAULT_BOOKING_TIME_ZONE, unsafeAsFormId, unsafeAsAppointmentResourceId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, formatLocalScheduleDate, addLocalScheduleDays, parseBookingLocalDate, unsafeAsPassId, formatBookingInstant } from './chunk-
|
|
11
|
+
import { defineIslandPropsDecoder, resolveBookingSiteId, resolveDefaultBookingServiceId, getBookingRedirectStateFromLocation, deriveBookingFormViewState, logger, getBookingSuccessMessage, getBookingServiceSlidingScale, formatBookingCentsAsCurrencyInput, parseBookingCurrencyInputToCents, resolveAppointmentCheckoutVerificationState, themeButtonClassName, toSelectedCollectionOptionKind, assertNever, prepareBookingSubmission, formatBookingSubmissionPreparationError, resolveBookingPaymentStep, BOOKING_FORM_DATA_RESERVED_KEYS, isBookingServiceSlidingAmountInRange, clearWorkflowFeedback, reportWorkflowError, reportWorkflowSuccess, okWith, err } from './chunk-R3CZQLXF.mjs';
|
|
12
|
+
import { resolveBookingTimeZoneId, DEFAULT_BOOKING_TIME_ZONE, unsafeAsFormId, unsafeAsAppointmentResourceId, unsafeAsCustomerPassId, unsafeAsAppointmentServiceId, formatLocalScheduleDate, addLocalScheduleDays, parseBookingLocalDate, unsafeAsPassId, formatCurrencyAmount, formatBookingInstant } from './chunk-GH6IIOOE.mjs';
|
|
13
13
|
import React2, { useRef, useState, useMemo, useReducer, useEffect, useCallback, useLayoutEffect } from 'react';
|
|
14
14
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
15
15
|
|
|
@@ -514,6 +514,7 @@ function useBookingSubmission(_siteId) {
|
|
|
514
514
|
customFields: data.customFields,
|
|
515
515
|
timezone: data.timezone,
|
|
516
516
|
discountCode: data.discountCode,
|
|
517
|
+
selectedSlidingAmount: data.selectedSlidingAmount,
|
|
517
518
|
selectedCollectionOptionKind: data.selectedCollectionOptionKind,
|
|
518
519
|
altchaSolution: data.altchaSolution ?? null,
|
|
519
520
|
successUrl: data.successUrl,
|
|
@@ -583,6 +584,9 @@ function createInitialBookingFlowReducerState(input) {
|
|
|
583
584
|
fieldValues: collectInitialFieldValues(initialData),
|
|
584
585
|
touchedFields: {},
|
|
585
586
|
discountCode: toOptionalString(initialData.discountCode) ?? "",
|
|
587
|
+
selectedSlidingAmountInput: toOptionalString(
|
|
588
|
+
initialData.selectedSlidingAmountInput
|
|
589
|
+
) ?? "",
|
|
586
590
|
selectedCollectionOptionKind: toSelectedCollectionOptionKind(
|
|
587
591
|
initialData.selectedCollectionOptionKind
|
|
588
592
|
),
|
|
@@ -635,6 +639,7 @@ function bookingFlowReducer(state, event) {
|
|
|
635
639
|
selectedDate: void 0,
|
|
636
640
|
selectedSlot: null,
|
|
637
641
|
selectedAppointmentPackageId: void 0,
|
|
642
|
+
selectedSlidingAmountInput: "",
|
|
638
643
|
selectedCollectionOptionKind: void 0,
|
|
639
644
|
validationErrors: clearBookingFormError(state.validationErrors)
|
|
640
645
|
};
|
|
@@ -688,6 +693,13 @@ function bookingFlowReducer(state, event) {
|
|
|
688
693
|
selectedAppointmentPackageId: void 0,
|
|
689
694
|
validationErrors: clearBookingFormError(state.validationErrors)
|
|
690
695
|
};
|
|
696
|
+
case "sliding-amount-changed":
|
|
697
|
+
return {
|
|
698
|
+
...state,
|
|
699
|
+
selectedSlidingAmountInput: event.value,
|
|
700
|
+
selectedAppointmentPackageId: void 0,
|
|
701
|
+
validationErrors: clearBookingFormError(state.validationErrors)
|
|
702
|
+
};
|
|
691
703
|
case "appointment-package-selected":
|
|
692
704
|
return {
|
|
693
705
|
...state,
|
|
@@ -790,6 +802,7 @@ function toBookingFormData(state) {
|
|
|
790
802
|
selectedDate: state.selectedDate,
|
|
791
803
|
selectedSlot: state.selectedSlot?.startAt,
|
|
792
804
|
discountCode: state.discountCode || void 0,
|
|
805
|
+
selectedSlidingAmountInput: state.selectedSlidingAmountInput || void 0,
|
|
793
806
|
selectedCollectionOptionKind: state.selectedCollectionOptionKind,
|
|
794
807
|
selectedAppointmentPackageId: state.selectedAppointmentPackageId
|
|
795
808
|
};
|
|
@@ -807,7 +820,8 @@ function buildBookingStepResolution(input) {
|
|
|
807
820
|
input.form.settings?.serviceIds && input.form.settings.serviceIds.length > 1
|
|
808
821
|
);
|
|
809
822
|
const selectedService = paymentResolution.service;
|
|
810
|
-
const
|
|
823
|
+
const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
|
|
824
|
+
const needsPayment = Boolean(selectedServiceSlidingScale) || paymentResolution.mode !== "hidden" || Boolean(
|
|
811
825
|
input.enableAppointmentPackages && selectedService && (selectedService.priceAmount ?? 0) > 0
|
|
812
826
|
);
|
|
813
827
|
const hasFields = Boolean(input.form.schema?.fields.length);
|
|
@@ -851,6 +865,18 @@ function validateBookingStep(input) {
|
|
|
851
865
|
services: input.services,
|
|
852
866
|
defaultServiceId: input.defaultServiceId
|
|
853
867
|
});
|
|
868
|
+
const slidingScale = getBookingServiceSlidingScale(resolution.service);
|
|
869
|
+
if (slidingScale && !isBookingServiceSlidingAmountInRange(
|
|
870
|
+
slidingScale,
|
|
871
|
+
parseBookingCurrencyInputToCents(input.data.selectedSlidingAmountInput)
|
|
872
|
+
)) {
|
|
873
|
+
return {
|
|
874
|
+
ok: false,
|
|
875
|
+
errors: createFormBookingValidationError(
|
|
876
|
+
"Please choose an amount within the sliding scale"
|
|
877
|
+
)
|
|
878
|
+
};
|
|
879
|
+
}
|
|
854
880
|
if (resolution.mode === "hidden" || resolution.mode === "disclosure_only") {
|
|
855
881
|
return { ok: true };
|
|
856
882
|
}
|
|
@@ -1260,11 +1286,27 @@ function useHeadlessBookingFlow(options) {
|
|
|
1260
1286
|
const selectedServiceId = state.selectedServiceId ?? defaultServiceId;
|
|
1261
1287
|
const selectedResourceId = state.selectedResourceId ?? defaultResourceId;
|
|
1262
1288
|
const selectedService = options.services.find((service) => service.id === selectedServiceId) ?? null;
|
|
1289
|
+
const lastSlidingServiceIdRef = useRef(null);
|
|
1290
|
+
useEffect(() => {
|
|
1291
|
+
const selectedId = selectedService?.id ?? null;
|
|
1292
|
+
if (lastSlidingServiceIdRef.current === selectedId) return;
|
|
1293
|
+
lastSlidingServiceIdRef.current = selectedId;
|
|
1294
|
+
const slidingScale = getBookingServiceSlidingScale(selectedService);
|
|
1295
|
+
dispatch({
|
|
1296
|
+
type: "sliding-amount-changed",
|
|
1297
|
+
value: slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatBookingCentsAsCurrencyInput(slidingScale.suggestedAmount) : ""
|
|
1298
|
+
});
|
|
1299
|
+
}, [selectedService]);
|
|
1263
1300
|
const hasPortalBearerAuth = (api.config.authOverrides?.portal ?? api.config.auth).type === "bearer";
|
|
1301
|
+
const selectedServiceSlidingScale = getBookingServiceSlidingScale(selectedService);
|
|
1302
|
+
const selectedSlidingAmount = parseBookingCurrencyInputToCents(
|
|
1303
|
+
state.selectedSlidingAmountInput
|
|
1304
|
+
);
|
|
1305
|
+
const selectedAppointmentPriceAmount = selectedServiceSlidingScale && selectedSlidingAmount !== null ? selectedSlidingAmount : selectedService?.priceAmount ?? 0;
|
|
1264
1306
|
const canLoadAppointmentPackages = Boolean(
|
|
1265
|
-
options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && (selectedService.priceAmount ?? 0) > 0
|
|
1307
|
+
options.enableAppointmentPackages && hasPortalBearerAuth && selectedService && !selectedServiceSlidingScale && (selectedService.priceAmount ?? 0) > 0
|
|
1266
1308
|
);
|
|
1267
|
-
const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
|
|
1309
|
+
const appointmentPackagesUnavailableReason = !options.enableAppointmentPackages ? "disabled" : !selectedService || selectedServiceSlidingScale || (selectedService.priceAmount ?? 0) <= 0 ? "missing-priced-service" : !hasPortalBearerAuth ? "missing-portal-auth" : null;
|
|
1268
1310
|
const data = useMemo(() => toBookingFormData(state), [state]);
|
|
1269
1311
|
const availabilityDatesQuery = useMemo(
|
|
1270
1312
|
() => buildBookingAvailabilityQuery({
|
|
@@ -1494,13 +1536,13 @@ function useHeadlessBookingFlow(options) {
|
|
|
1494
1536
|
state.selectedSlot
|
|
1495
1537
|
]);
|
|
1496
1538
|
const appointmentDiscountContext = useMemo(
|
|
1497
|
-
() => selectedService &&
|
|
1498
|
-
[
|
|
1539
|
+
() => selectedService && selectedAppointmentPriceAmount > 0 ? { type: "appointment", serviceId: selectedService.id } : null,
|
|
1540
|
+
[selectedAppointmentPriceAmount, selectedService?.id]
|
|
1499
1541
|
);
|
|
1500
1542
|
const discountUi = useDiscountCodeUi({
|
|
1501
1543
|
api,
|
|
1502
1544
|
discountContext: appointmentDiscountContext,
|
|
1503
|
-
subtotalCents:
|
|
1545
|
+
subtotalCents: selectedAppointmentPriceAmount
|
|
1504
1546
|
});
|
|
1505
1547
|
const { submit, clearFeedback } = useBookingSubmission(options.siteId);
|
|
1506
1548
|
const submittingRef = useRef(false);
|
|
@@ -1643,6 +1685,7 @@ function useHeadlessBookingFlow(options) {
|
|
|
1643
1685
|
type: "appointment-package-selected",
|
|
1644
1686
|
packageId: packageId ? unsafeAsCustomerPassId(packageId) : null
|
|
1645
1687
|
}),
|
|
1688
|
+
setSlidingAmountInput: (value) => dispatch({ type: "sliding-amount-changed", value }),
|
|
1646
1689
|
setDiscountCode: (code) => {
|
|
1647
1690
|
dispatch({ type: "discount-code-changed", code });
|
|
1648
1691
|
latestRef.current.setDiscountCode(code);
|
|
@@ -1713,11 +1756,11 @@ function useHeadlessBookingFlow(options) {
|
|
|
1713
1756
|
() => selectedService && paymentResolution.mode !== "unsupported" ? buildBookingPaymentSelectionViewModel({
|
|
1714
1757
|
serviceTitle: selectedService.title,
|
|
1715
1758
|
paymentTerms: selectedService.paymentTerms,
|
|
1716
|
-
priceAmount:
|
|
1759
|
+
priceAmount: selectedAppointmentPriceAmount,
|
|
1717
1760
|
priceCurrency: selectedService.priceCurrency,
|
|
1718
1761
|
bookingStartAt: state.selectedSlot?.startAt
|
|
1719
1762
|
}) : null,
|
|
1720
|
-
[paymentResolution.mode, selectedService, state.selectedSlot?.startAt]
|
|
1763
|
+
[paymentResolution.mode, selectedAppointmentPriceAmount, selectedService, state.selectedSlot?.startAt]
|
|
1721
1764
|
);
|
|
1722
1765
|
const appointmentPackageItems = appointmentPackages.tag === "loaded" ? appointmentPackages.value : [];
|
|
1723
1766
|
const selectedAppointmentPackage = appointmentPackageItems.find(
|
|
@@ -1746,6 +1789,9 @@ function useHeadlessBookingFlow(options) {
|
|
|
1746
1789
|
selection: null,
|
|
1747
1790
|
selectedCollectionOptionKind: state.selectedCollectionOptionKind,
|
|
1748
1791
|
selectedAppointmentPackageId: state.selectedAppointmentPackageId,
|
|
1792
|
+
selectedSlidingAmountInput: state.selectedSlidingAmountInput,
|
|
1793
|
+
selectedSlidingAmount,
|
|
1794
|
+
slidingScale: selectedServiceSlidingScale,
|
|
1749
1795
|
appointmentPackages: appointmentPackagesModel,
|
|
1750
1796
|
errorMessage: paymentResolution.errorMessage ?? "This service cannot be booked right now."
|
|
1751
1797
|
} : {
|
|
@@ -1754,6 +1800,9 @@ function useHeadlessBookingFlow(options) {
|
|
|
1754
1800
|
selection: paymentSelection,
|
|
1755
1801
|
selectedCollectionOptionKind: state.selectedCollectionOptionKind,
|
|
1756
1802
|
selectedAppointmentPackageId: state.selectedAppointmentPackageId,
|
|
1803
|
+
selectedSlidingAmountInput: state.selectedSlidingAmountInput,
|
|
1804
|
+
selectedSlidingAmount,
|
|
1805
|
+
slidingScale: selectedServiceSlidingScale,
|
|
1757
1806
|
appointmentPackages: appointmentPackagesModel
|
|
1758
1807
|
};
|
|
1759
1808
|
return {
|
|
@@ -1959,6 +2008,9 @@ var paymentOptionLayoutStyle = {
|
|
|
1959
2008
|
var summaryStackStyle = {
|
|
1960
2009
|
gap: "0.125rem"
|
|
1961
2010
|
};
|
|
2011
|
+
function formatBookingAmount(amount, currency) {
|
|
2012
|
+
return formatCurrencyAmount(amount, currency, { decimals: "auto" });
|
|
2013
|
+
}
|
|
1962
2014
|
function HeadlessBookingDefaultForm({
|
|
1963
2015
|
booking,
|
|
1964
2016
|
onSubmit
|
|
@@ -2235,6 +2287,7 @@ function HeadlessBookingPaymentSelectionStep({
|
|
|
2235
2287
|
}
|
|
2236
2288
|
const packageOptions = booking.payment.appointmentPackages.items;
|
|
2237
2289
|
const hasPackageOptions = packageOptions.length > 0;
|
|
2290
|
+
const slidingScale = booking.payment.slidingScale;
|
|
2238
2291
|
return /* @__PURE__ */ jsxs("div", { className: "ab-stack", children: [
|
|
2239
2292
|
/* @__PURE__ */ jsxs("div", { className: "ab-info-box", children: [
|
|
2240
2293
|
/* @__PURE__ */ jsx("h3", { className: "ab-subtitle", children: viewModel.sectionTitle }),
|
|
@@ -2245,6 +2298,28 @@ function HeadlessBookingPaymentSelectionStep({
|
|
|
2245
2298
|
booking.payment.appointmentPackages.state.message,
|
|
2246
2299
|
" You can continue with the normal payment option."
|
|
2247
2300
|
] }) : null,
|
|
2301
|
+
slidingScale ? /* @__PURE__ */ jsxs("div", { className: "rb-space-y-2", children: [
|
|
2302
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "appointment-sliding-amount", className: "form-label", children: "Choose your price" }),
|
|
2303
|
+
/* @__PURE__ */ jsxs("p", { className: "rb-caption status-muted", children: [
|
|
2304
|
+
formatBookingAmount(slidingScale.minAmount, slidingScale.currency),
|
|
2305
|
+
" to ",
|
|
2306
|
+
formatBookingAmount(slidingScale.maxAmount, slidingScale.currency),
|
|
2307
|
+
slidingScale.suggestedAmount !== null ? `, suggested ${formatBookingAmount(slidingScale.suggestedAmount, slidingScale.currency)}` : ""
|
|
2308
|
+
] }),
|
|
2309
|
+
/* @__PURE__ */ jsx(
|
|
2310
|
+
"input",
|
|
2311
|
+
{
|
|
2312
|
+
id: "appointment-sliding-amount",
|
|
2313
|
+
type: "number",
|
|
2314
|
+
className: "form-input",
|
|
2315
|
+
min: slidingScale.minAmount / 100,
|
|
2316
|
+
max: slidingScale.maxAmount / 100,
|
|
2317
|
+
step: "0.01",
|
|
2318
|
+
value: booking.payment.selectedSlidingAmountInput,
|
|
2319
|
+
onChange: (event) => booking.actions.setSlidingAmountInput(event.target.value)
|
|
2320
|
+
}
|
|
2321
|
+
)
|
|
2322
|
+
] }) : null,
|
|
2248
2323
|
/* @__PURE__ */ jsxs("div", { className: "ab-stack", role: "list", "aria-label": "Payment options", children: [
|
|
2249
2324
|
packageOptions.map((appointmentPackage) => {
|
|
2250
2325
|
const isSelected = booking.payment.selectedAppointmentPackageId === appointmentPackage.customerPassId;
|