@riverbankcms/sdk 0.132.0 → 0.134.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/CHANGELOG.md +10 -0
- package/dist/{PreviewEditorSidebar-DKQ5eFK9.mjs → PreviewEditorSidebar-DfRhJPFQ.mjs} +3 -3
- package/dist/{PreviewEditorUI-CNTcOjCC.mjs → PreviewEditorUI-Dppxcr_g.mjs} +2 -2
- package/dist/{SdkPreviewModeRuntime-DJu3qu33.mjs → SdkPreviewModeRuntime-8CyGCXir.mjs} +46 -15
- package/dist/{SiteChromeCustomizeContext-2TzT3vbj.mjs → SiteChromeCustomizeContext-DVMTMvqA.mjs} +497 -169
- package/dist/_dts/ai/src/contracts/proposals.d.ts +384 -384
- package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +219 -219
- package/dist/_dts/api/src/accountApi.d.ts +5 -5
- package/dist/_dts/api/src/appointmentEndpointDefinitions.d.ts +1 -1
- package/dist/_dts/api/src/bookingOperations.d.ts +2 -14
- package/dist/_dts/api/src/bookings.d.ts +2 -2
- package/dist/_dts/api/src/contentRuntime.d.ts +1 -1
- package/dist/_dts/api/src/courseEndpointDefinitions.d.ts +2 -2
- package/dist/_dts/api/src/endpoints.d.ts +3 -3
- package/dist/_dts/api/src/indexCoreTypes.d.ts +1 -1
- package/dist/_dts/api/src/navigation/contracts.d.ts +9 -6
- package/dist/_dts/api/src/navigation/linkResolver.d.ts +6 -2
- package/dist/_dts/api/src/navigation/types.d.ts +23 -6
- package/dist/_dts/api/src/navigation.d.ts +1 -1
- package/dist/_dts/api/src/publicBookingService.d.ts +15 -0
- package/dist/_dts/api/src/publicExperience/eventEndpoints.d.ts +3 -1
- package/dist/_dts/api/src/publicExperience/wholeSeriesEndpoints.d.ts +2 -0
- package/dist/_dts/api/src/sdk-event-upsert.d.ts +1 -1
- package/dist/_dts/api/src/sdkThemeWire.d.ts +50 -50
- package/dist/_dts/api/src/siteBanner.d.ts +4 -2
- package/dist/_dts/api/src/types.d.ts +1 -1
- package/dist/_dts/api/src/webhooks.d.ts +12 -12
- package/dist/_dts/blocks/src/client.d.ts +1 -1
- package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +28 -72
- package/dist/_dts/blocks/src/index.d.ts +3 -2
- package/dist/_dts/blocks/src/primitives/content/components/RichText.d.ts +2 -2
- package/dist/_dts/blocks/src/primitives/content/primitives.d.ts +1 -0
- package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +36 -14
- package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +3 -0
- package/dist/_dts/blocks/src/system/blocks/events/whole-series/schemas.d.ts +3 -3
- package/dist/_dts/blocks/src/system/blocks/events/whole-series/types.d.ts +3 -0
- package/dist/_dts/blocks/src/system/blocks/location-map.d.ts +4 -4
- package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +148 -148
- package/dist/_dts/blocks/src/system/blocks/offerings-preview/model.d.ts +2 -1
- package/dist/_dts/blocks/src/system/blocks/shared/bookingConsent.d.ts +36 -0
- package/dist/_dts/blocks/src/system/manifest/hydrateLinks.d.ts +9 -2
- package/dist/_dts/blocks/src/system/manifest/resolveBlockData.d.ts +8 -0
- package/dist/_dts/blocks/src/system/manifest/schema.d.ts +79 -79
- package/dist/_dts/blocks/src/system/runtime/api/types/appointments.d.ts +2 -11
- package/dist/_dts/blocks/src/system/runtime/api/types/courses.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/api/types/events.d.ts +1 -0
- package/dist/_dts/blocks/src/system/runtime/components/multi-step/DynamicFormFields.d.ts +3 -19
- package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/hooks/useSelectedAppointmentConsentForm.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.consent.d.ts +10 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.field-values.d.ts +4 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.fields.d.ts +21 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +2 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.interactive.d.ts +38 -144
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +5 -19
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-runtime-sources.d.ts +3 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/AttendeeDetailsStep.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationSurface.d.ts +18 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +4 -4
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/validation.d.ts +2 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/whole-series/components.d.ts +2 -1
- package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/whole-series/whole-series-registration.headless.contract.d.ts +2 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +34 -12
- package/dist/_dts/blocks/src/system/runtime/shared/components/BookingConsentAgreement.d.ts +7 -0
- package/dist/_dts/blocks/src/system/runtime/shared/types.d.ts +1 -0
- package/dist/_dts/blocks/src/system/types/link.d.ts +7 -1
- package/dist/_dts/content-model/src/content-types/schema.d.ts +4 -4
- package/dist/_dts/core/src/booking-consent.d.ts +8 -0
- package/dist/_dts/core/src/index.d.ts +1 -0
- package/dist/_dts/db/src/generated/supabase/database.types.d.ts +9 -3
- package/dist/_dts/db/src/schemas/forms.d.ts +21 -21
- package/dist/_dts/db/src/types/jsonb.d.ts +1 -1
- package/dist/_dts/editor/src/primitives/rich-text/RichTextEditorSurface.d.ts +2 -1
- package/dist/_dts/editor/src/primitives/rich-text/config.d.ts +2 -1
- package/dist/_dts/editor/src/primitives/rich-text/toolbar/linkPopoverUtils.d.ts +3 -2
- package/dist/_dts/editor/src/primitives/rich-text/useRichTextEditor.d.ts +3 -4
- package/dist/_dts/media-core/src/usageScanner.d.ts +1 -1
- package/dist/_dts/sdk/src/bookings/types.d.ts +7 -89
- package/dist/_dts/sdk/src/config/validation.d.ts +30 -30
- package/dist/_dts/sdk/src/contracts/content.d.ts +10 -36
- package/dist/_dts/sdk/src/contracts/rendering.d.ts +1 -1
- package/dist/_dts/sdk/src/manifest/passes/pass1-fields.d.ts +9 -9
- package/dist/_dts/sdk/src/public-api/contracts.d.ts +6 -19
- package/dist/_dts/sdk/src/version.d.ts +1 -1
- package/dist/_dts/theme-core/src/accordions/types.d.ts +2 -2
- package/dist/_dts/theme-core/src/buttons/types.d.ts +12 -12
- package/dist/_dts/theme-core/src/cards/types.d.ts +5 -5
- package/dist/_dts/theme-core/src/mock-themes/all.d.ts +72 -72
- package/dist/_dts/theme-core/src/navigation/types.d.ts +4 -4
- package/dist/_dts/theme-core/src/palette/colors.d.ts +1 -1
- package/dist/_dts/theme-core/src/palette/curated/types.d.ts +2 -2
- package/dist/_dts/theme-core/src/schema.d.ts +82 -82
- package/dist/_dts/theme-core/src/site-styles/cardRoleVocabulary.d.ts +1 -1
- package/dist/_dts/theme-core/src/site-styles/designState.d.ts +2 -2
- package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -4
- package/dist/_dts/theme-core/src/site-styles/logoBranding.d.ts +4 -4
- package/dist/_dts/theme-core/src/tokens/themeColorTokens.d.ts +1 -1
- package/dist/_dts/ui/src/badge.d.ts +1 -1
- package/dist/_dts/ui/src/button.d.ts +1 -1
- package/dist/_dts/ui/src/callout.d.ts +1 -1
- package/dist/_dts/ui/src/card.d.ts +1 -1
- package/dist/client/{StepIndicator-CGixP_LW.mjs → BookingConsentAgreement-BxgpGEFD.mjs} +26 -4
- package/dist/client/{DiscountCodeDisclosure-DO7llLxm.mjs → DiscountCodeDisclosure-CfuPOHD0.mjs} +1 -1
- package/dist/client/{EventCombined.client-xID7zN__.mjs → EventCombined.client-DB8Qfzi-.mjs} +3 -3
- package/dist/client/{EventListing.client-CriBslp0.mjs → EventListing.client-DD4X8hp4.mjs} +2 -2
- package/dist/client/{LeafletStaticMap-DYmHnM8g.mjs → LeafletStaticMap-QhgIBQbv.mjs} +1 -1
- package/dist/client/{WholeSeriesRegistrationWizard-BEjzSiOA.mjs → WholeSeriesRegistrationWizard-DwtC7bCs.mjs} +72 -22
- package/dist/client/{accordion-CgFbnnnQ.mjs → accordion-DFXpLvwg.mjs} +1 -1
- package/dist/client/{bookingForm-dUyGpbxc.mjs → bookingForm-sI05d3lk.mjs} +73 -47
- package/dist/client/bookings.mjs +989 -140
- package/dist/client/{carousel-BvBwkUzx.mjs → carousel-wjyWTZCh.mjs} +1 -1
- package/dist/client/{cart-DiZENT2E.mjs → cart-DCwdpA4i.mjs} +3 -3
- package/dist/client/{checkout-Dq13KIgR.mjs → checkout-DTDW3l0R.mjs} +3 -3
- package/dist/client/checkoutUrls-DQYC_Br-.mjs +187 -0
- package/dist/client/client.mjs +556 -237
- package/dist/client/{context-B83PiNzm.mjs → context-TSK1CGx8.mjs} +1 -14
- package/dist/client/{courseRegistration-CKC5NmyH.mjs → courseRegistration-A-Tg7qw_.mjs} +3 -3
- package/dist/client/{eventCalendar-BF0Jatfw.mjs → eventCalendar-B0Zg-i_w.mjs} +5 -5
- package/dist/client/{eventCombined-BuoCF8je.mjs → eventCombined-B8Y2wizd.mjs} +3 -3
- package/dist/client/{eventDetails-CKwh0S9s.mjs → eventDetails-DPDOwURJ.mjs} +3 -3
- package/dist/client/{eventListing-eUxaWFaB.mjs → eventListing-CjBcmSLn.mjs} +3 -3
- package/dist/client/{eventRegistration-CfkNOkPb.mjs → eventRegistration-CK30HsAq.mjs} +111 -64
- package/dist/client/{form-C17H1c_H.mjs → form-CUXfsABE.mjs} +2 -2
- package/dist/client/{headerSection-DEwUmrtt.mjs → headerSection-N-mN3rjr.mjs} +2 -2
- package/dist/client/hooks.mjs +106 -3
- package/dist/client/{locationMap-Pf2QwuvW.mjs → locationMap-oiIDQMBt.mjs} +2 -2
- package/dist/client/{newsletterForm-BWQAH5Sv.mjs → newsletterForm-CkQ2tTrM.mjs} +2 -2
- package/dist/client/{pricing-CVdvbopJ.mjs → pricing-Caa0QWzh.mjs} +1 -1
- package/dist/client/{productDetail-BZi1Fa_G.mjs → productDetail-CnidyrsN.mjs} +3 -3
- package/dist/client/{productList-qq-55DNk.mjs → productList-CttYcS_S.mjs} +3 -3
- package/dist/client/{propsDecoder-BxmqPKdP.mjs → propsDecoder-CeUMxI9a.mjs} +211 -100
- package/dist/client/rendering/client.mjs +428 -151
- package/dist/client/rendering/islands.mjs +18 -18
- package/dist/client/rendering.mjs +643 -324
- package/dist/client/{scrollToBlock-B3CcKdtG.mjs → scrollToBlock-lPr1oqxB.mjs} +1 -1
- package/dist/client/{shop-DNWJPut5.mjs → shop-DXQtMo2O.mjs} +6 -6
- package/dist/client/{shop-commerce-BhvQCosS.mjs → shop-commerce-quDL411J.mjs} +7 -164
- package/dist/client/{shop-commerce-store-B04sCNnW.mjs → shop-commerce-store-Dnu-8Iub.mjs} +1 -1
- package/dist/client/{shopShared-DNNgZlmW.mjs → shopShared-BWPUURvG.mjs} +3 -3
- package/dist/client/{useEventFilters-CUmhE24_.mjs → useEventFilters-DRhWp2_5.mjs} +3 -3
- package/dist/client/{usePolling-M5jG675x.mjs → usePolling-UN4WKnaM.mjs} +2 -2
- package/dist/preview-next/client/runtime.mjs +3 -3
- package/dist/{sdk-runtime-BEXb1wqo.mjs → sdk-runtime-Ca5RD78X.mjs} +1 -1
- package/dist/{separator-DGMuip1D.mjs → separator-CMhj6BTi.mjs} +1 -1
- package/dist/server/components.mjs +117 -37
- package/dist/server/config-validation.mjs +106 -3
- package/dist/server/config.mjs +106 -3
- package/dist/server/data.mjs +106 -3
- package/dist/server/index.mjs +1 -1
- package/dist/server/next.mjs +186 -106
- package/dist/server/page-converter.mjs +105 -2
- package/dist/server/prebuild.mjs +1 -1
- package/dist/server/rendering/server.mjs +119 -37
- package/dist/server/rendering.mjs +117 -37
- package/dist/server/routing.mjs +132 -73
- package/dist/server/server.mjs +107 -4
- package/dist/server/theme-bridge.mjs +4 -4
- package/package.json +2 -2
- package/dist/client/checkoutUrls-Ct8Mxna3.mjs +0 -26
package/dist/client/bookings.mjs
CHANGED
|
@@ -3211,20 +3211,7 @@ function createBlockApi(config) {
|
|
|
3211
3211
|
const path = buildPath(WHOLE_SERIES.checkout.path, { siteId: sid });
|
|
3212
3212
|
return request(WHOLE_SERIES.checkout, {
|
|
3213
3213
|
path,
|
|
3214
|
-
body:
|
|
3215
|
-
eventSeriesId: data.eventSeriesId,
|
|
3216
|
-
...data.pricingOptionId ? { pricingOptionId: data.pricingOptionId } : {},
|
|
3217
|
-
...data.selectedSlidingAmount !== void 0 ? { selectedSlidingAmount: data.selectedSlidingAmount } : {},
|
|
3218
|
-
email: data.email,
|
|
3219
|
-
name: data.name,
|
|
3220
|
-
phone: data.phone,
|
|
3221
|
-
formData: data.formData,
|
|
3222
|
-
altchaSolution: data.altchaSolution,
|
|
3223
|
-
discountCode: data.discountCode,
|
|
3224
|
-
successUrl: data.successUrl,
|
|
3225
|
-
cancelUrl: data.cancelUrl,
|
|
3226
|
-
claimToken: data.claimToken
|
|
3227
|
-
}
|
|
3214
|
+
body: data
|
|
3228
3215
|
});
|
|
3229
3216
|
},
|
|
3230
3217
|
async getWholeSeriesRegistrationStatus(params) {
|
|
@@ -3676,7 +3663,7 @@ function getChallengeUrl() {
|
|
|
3676
3663
|
}
|
|
3677
3664
|
//#endregion
|
|
3678
3665
|
//#region ../core/src/result.ts
|
|
3679
|
-
function ok(value) {
|
|
3666
|
+
function ok$1(value) {
|
|
3680
3667
|
return {
|
|
3681
3668
|
ok: true,
|
|
3682
3669
|
value
|
|
@@ -3696,11 +3683,14 @@ function parseUuidBrand(value, unsafeConstructor) {
|
|
|
3696
3683
|
const normalized = value.trim().toLowerCase();
|
|
3697
3684
|
if (normalized.length === 0) return err$1({ kind: "empty_string" });
|
|
3698
3685
|
if (!UUID_PATTERN.test(normalized)) return err$1({ kind: "invalid_uuid" });
|
|
3699
|
-
return ok(unsafeConstructor(normalized));
|
|
3686
|
+
return ok$1(unsafeConstructor(normalized));
|
|
3700
3687
|
}
|
|
3701
3688
|
const unsafeAsSiteId = (id) => id;
|
|
3702
3689
|
const unsafeAsEventOfferingId = (id) => id;
|
|
3703
3690
|
const unsafeAsEventSeriesId = (id) => id;
|
|
3691
|
+
const unsafeAsMediaAssetId = (id) => id;
|
|
3692
|
+
const unsafeAsStorageBucketName = (value) => value;
|
|
3693
|
+
const unsafeAsStoragePath = (value) => value;
|
|
3704
3694
|
const unsafeAsFormId = (id) => id;
|
|
3705
3695
|
const unsafeAsPassId = (id) => id;
|
|
3706
3696
|
const unsafeAsCustomerPassId = (id) => id;
|
|
@@ -3843,7 +3833,7 @@ function parseEventPageSurfaceSubject(value) {
|
|
|
3843
3833
|
kind: "invalidEventSeriesBookingScope",
|
|
3844
3834
|
bookingScope: value.bookingScope
|
|
3845
3835
|
});
|
|
3846
|
-
return ok(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope));
|
|
3836
|
+
return ok$1(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope));
|
|
3847
3837
|
}
|
|
3848
3838
|
case "eventOffering": {
|
|
3849
3839
|
const eventOfferingId = parseEventOfferingId(value.eventOfferingId);
|
|
@@ -3864,7 +3854,7 @@ function parseEventPageSurfaceSubject(value) {
|
|
|
3864
3854
|
});
|
|
3865
3855
|
parsedSeriesIds.push(eventSeriesId.value);
|
|
3866
3856
|
}
|
|
3867
|
-
return ok({
|
|
3857
|
+
return ok$1({
|
|
3868
3858
|
kind: "eventOffering",
|
|
3869
3859
|
eventOfferingId: eventOfferingId.value,
|
|
3870
3860
|
availableEventSeriesIds: parsedSeriesIds
|
|
@@ -3982,7 +3972,7 @@ function parseBookingTimeZone(timeZone) {
|
|
|
3982
3972
|
if (typeof timeZone !== "string") return err$1("invalid-time-zone");
|
|
3983
3973
|
const normalized = timeZone.trim();
|
|
3984
3974
|
if (normalized.length === 0 || NUMERIC_OFFSET_TIME_ZONE_PATTERN.test(normalized) || !isValidBookingTimeZone(normalized)) return err$1("invalid-time-zone");
|
|
3985
|
-
return ok(normalized);
|
|
3975
|
+
return ok$1(normalized);
|
|
3986
3976
|
}
|
|
3987
3977
|
//#endregion
|
|
3988
3978
|
//#region ../core/src/booking-date-time.ts
|
|
@@ -4061,7 +4051,7 @@ function parseUtcIsoDateTimeString(value) {
|
|
|
4061
4051
|
if (!hasUtcSuffix && !hasExplicitOffset) return err$1("missing-timezone-offset");
|
|
4062
4052
|
const date = new Date(normalized);
|
|
4063
4053
|
if (Number.isNaN(date.getTime())) return err$1("invalid-date-time");
|
|
4064
|
-
return ok(date.toISOString());
|
|
4054
|
+
return ok$1(date.toISOString());
|
|
4065
4055
|
}
|
|
4066
4056
|
function toUtcIsoDateTimeString(value) {
|
|
4067
4057
|
const parsed = parseUtcIsoDateTimeString(value);
|
|
@@ -4213,6 +4203,109 @@ function assertNever(value, message) {
|
|
|
4213
4203
|
throw new Error(message ?? `Unexpected value: ${String(value)}`);
|
|
4214
4204
|
}
|
|
4215
4205
|
//#endregion
|
|
4206
|
+
//#region ../blocks/src/system/manifest/richTextSchema.ts
|
|
4207
|
+
/**
|
|
4208
|
+
* Shared Rich Text Schema
|
|
4209
|
+
*
|
|
4210
|
+
* This module provides a single source of truth for TipTap rich text validation
|
|
4211
|
+
* across both the block-form editor and the block rendering system.
|
|
4212
|
+
*
|
|
4213
|
+
* Rich-text fields use one canonical root shape:
|
|
4214
|
+
* `{ type: 'doc', content: [...] }`.
|
|
4215
|
+
*/
|
|
4216
|
+
/**
|
|
4217
|
+
* TipTap document node schema (recursive structure)
|
|
4218
|
+
*
|
|
4219
|
+
* Supports the full TipTap document structure including:
|
|
4220
|
+
* - type: Node type (e.g., 'doc', 'paragraph', 'text')
|
|
4221
|
+
* - content: Nested array of child nodes
|
|
4222
|
+
* - text: Text content for text nodes
|
|
4223
|
+
* - attrs: Node attributes (e.g., alignment, color)
|
|
4224
|
+
* - marks: Text formatting marks (e.g., bold, italic)
|
|
4225
|
+
*/
|
|
4226
|
+
const TipTapMarkSchema = z.object({
|
|
4227
|
+
type: z.string().min(1),
|
|
4228
|
+
attrs: z.record(z.string(), z.unknown()).optional()
|
|
4229
|
+
}).strict();
|
|
4230
|
+
const TipTapNodeSchema = z.lazy(() => z.object({
|
|
4231
|
+
type: z.string().min(1),
|
|
4232
|
+
content: z.array(TipTapNodeSchema).optional(),
|
|
4233
|
+
text: z.string().optional(),
|
|
4234
|
+
attrs: z.record(z.string(), z.unknown()).optional(),
|
|
4235
|
+
marks: z.array(TipTapMarkSchema).optional()
|
|
4236
|
+
}).strict().superRefine((node, ctx) => {
|
|
4237
|
+
if (node.type === "doc") ctx.addIssue({
|
|
4238
|
+
code: "custom",
|
|
4239
|
+
path: ["type"],
|
|
4240
|
+
message: "Document nodes are only valid at the rich-text root"
|
|
4241
|
+
});
|
|
4242
|
+
if (node.type === "text") {
|
|
4243
|
+
if (node.text === void 0 || node.text.length === 0) ctx.addIssue({
|
|
4244
|
+
code: "custom",
|
|
4245
|
+
path: ["text"],
|
|
4246
|
+
message: "Text nodes require a non-empty text value"
|
|
4247
|
+
});
|
|
4248
|
+
if (node.content !== void 0) ctx.addIssue({
|
|
4249
|
+
code: "custom",
|
|
4250
|
+
path: ["content"],
|
|
4251
|
+
message: "Text nodes cannot contain child nodes"
|
|
4252
|
+
});
|
|
4253
|
+
return;
|
|
4254
|
+
}
|
|
4255
|
+
if (node.text !== void 0) ctx.addIssue({
|
|
4256
|
+
code: "custom",
|
|
4257
|
+
path: ["text"],
|
|
4258
|
+
message: "Only text nodes can contain a text value"
|
|
4259
|
+
});
|
|
4260
|
+
}));
|
|
4261
|
+
/**
|
|
4262
|
+
* Canonical schema for a TipTap rich-text document root.
|
|
4263
|
+
*
|
|
4264
|
+
* Stricter than {@link TipTapNodeSchema}: `type` is literally `'doc'` and
|
|
4265
|
+
* `content` is required (possibly empty). This is the shape every producer
|
|
4266
|
+
* (dummy content, editor output, migrations) must emit, and the shape every
|
|
4267
|
+
* non-legacy consumer can rely on.
|
|
4268
|
+
*
|
|
4269
|
+
*/
|
|
4270
|
+
const RichTextDocSchema = z.object({
|
|
4271
|
+
type: z.literal("doc"),
|
|
4272
|
+
content: z.array(TipTapNodeSchema)
|
|
4273
|
+
}).strict();
|
|
4274
|
+
//#endregion
|
|
4275
|
+
//#region ../blocks/src/system/blocks/shared/bookingConsent.ts
|
|
4276
|
+
const BOOKING_REQUIREMENT_CONSENT_FIELD_PREFIX = "booking_requirement_consent_";
|
|
4277
|
+
function toBookingRequirementConsentFieldId(requirementId) {
|
|
4278
|
+
return `${BOOKING_REQUIREMENT_CONSENT_FIELD_PREFIX}${requirementId}`;
|
|
4279
|
+
}
|
|
4280
|
+
function isBookingRequirementConsentFieldId(value) {
|
|
4281
|
+
return value.startsWith(BOOKING_REQUIREMENT_CONSENT_FIELD_PREFIX);
|
|
4282
|
+
}
|
|
4283
|
+
function toBookingRequirementConsentField(consent) {
|
|
4284
|
+
return {
|
|
4285
|
+
id: toBookingRequirementConsentFieldId(consent.requirementId),
|
|
4286
|
+
label: consent.title,
|
|
4287
|
+
type: "booking_requirement_consent",
|
|
4288
|
+
required: true,
|
|
4289
|
+
agreement: consent.agreement,
|
|
4290
|
+
checkboxLabel: consent.checkboxLabel
|
|
4291
|
+
};
|
|
4292
|
+
}
|
|
4293
|
+
const blockBookingConsentSchema = z.object({
|
|
4294
|
+
requirementId: z.string(),
|
|
4295
|
+
title: z.string(),
|
|
4296
|
+
agreement: RichTextDocSchema,
|
|
4297
|
+
checkboxLabel: z.string()
|
|
4298
|
+
});
|
|
4299
|
+
const bookingRequirementConsentFieldSchema = z.object({
|
|
4300
|
+
id: z.string(),
|
|
4301
|
+
label: z.string(),
|
|
4302
|
+
type: z.literal("booking_requirement_consent"),
|
|
4303
|
+
required: z.literal(true),
|
|
4304
|
+
agreement: RichTextDocSchema,
|
|
4305
|
+
checkboxLabel: z.string().min(1)
|
|
4306
|
+
});
|
|
4307
|
+
z.union([blockBookingConsentSchema, bookingRequirementConsentFieldSchema]);
|
|
4308
|
+
//#endregion
|
|
4216
4309
|
//#region ../blocks/src/system/runtime/nodes/booking-form.reserved-fields.ts
|
|
4217
4310
|
const BOOKING_FORM_DATA_RESERVED_KEY_VALUES = [
|
|
4218
4311
|
"serviceId",
|
|
@@ -4226,6 +4319,28 @@ const BOOKING_FORM_DATA_RESERVED_KEY_VALUES = [
|
|
|
4226
4319
|
];
|
|
4227
4320
|
const BOOKING_FORM_DATA_RESERVED_KEYS = new Set(BOOKING_FORM_DATA_RESERVED_KEY_VALUES);
|
|
4228
4321
|
//#endregion
|
|
4322
|
+
//#region ../blocks/src/system/runtime/nodes/booking-form.consent.ts
|
|
4323
|
+
function resolveSelectedAppointmentConsent(service) {
|
|
4324
|
+
return service?.consent ? {
|
|
4325
|
+
kind: "required-for-selected-service",
|
|
4326
|
+
field: toBookingRequirementConsentField(service.consent)
|
|
4327
|
+
} : { kind: "not-applicable" };
|
|
4328
|
+
}
|
|
4329
|
+
function projectSelectedAppointmentConsentIntoForm(form, selectedConsent) {
|
|
4330
|
+
if (!form) return null;
|
|
4331
|
+
const sourceFields = form.schema?.fields ?? [];
|
|
4332
|
+
const baseFields = sourceFields.filter((field) => field.type !== "booking_requirement_consent");
|
|
4333
|
+
const fields = selectedConsent.kind === "required-for-selected-service" ? [...baseFields, selectedConsent.field] : baseFields;
|
|
4334
|
+
if (selectedConsent.kind === "not-applicable" && baseFields.length === sourceFields.length) return form;
|
|
4335
|
+
return {
|
|
4336
|
+
...form,
|
|
4337
|
+
schema: {
|
|
4338
|
+
...form.schema,
|
|
4339
|
+
fields
|
|
4340
|
+
}
|
|
4341
|
+
};
|
|
4342
|
+
}
|
|
4343
|
+
//#endregion
|
|
4229
4344
|
//#region ../blocks/src/system/runtime/nodes/booking-form.state.ts
|
|
4230
4345
|
function toTrimmedString(value) {
|
|
4231
4346
|
const trimmed = value?.trim();
|
|
@@ -4408,7 +4523,6 @@ function readBookingFieldType(value) {
|
|
|
4408
4523
|
case "select":
|
|
4409
4524
|
case "radio":
|
|
4410
4525
|
case "checkbox":
|
|
4411
|
-
case "booking_requirement_consent":
|
|
4412
4526
|
case "tel":
|
|
4413
4527
|
case "url":
|
|
4414
4528
|
case "number":
|
|
@@ -4443,7 +4557,7 @@ function normalizeBookingSettings(value) {
|
|
|
4443
4557
|
};
|
|
4444
4558
|
}
|
|
4445
4559
|
function isBookingFormService(value) {
|
|
4446
|
-
return isRecord$5(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string";
|
|
4560
|
+
return isRecord$5(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
|
|
4447
4561
|
}
|
|
4448
4562
|
function collectBookingCustomFields(data) {
|
|
4449
4563
|
const customFields = {};
|
|
@@ -4687,6 +4801,11 @@ function getBookingSuccessMessage(successMessage) {
|
|
|
4687
4801
|
return successMessage?.trim() || "Your appointment has been booked! Check your email for confirmation.";
|
|
4688
4802
|
}
|
|
4689
4803
|
//#endregion
|
|
4804
|
+
//#region ../blocks/src/system/runtime/hooks/useSelectedAppointmentConsentForm.ts
|
|
4805
|
+
function useSelectedAppointmentConsentForm(form, selectedService) {
|
|
4806
|
+
return useMemo(() => projectSelectedAppointmentConsentIntoForm(form, resolveSelectedAppointmentConsent(selectedService)), [form, selectedService]);
|
|
4807
|
+
}
|
|
4808
|
+
//#endregion
|
|
4690
4809
|
//#region ../blocks/src/utils/date-formatting.ts
|
|
4691
4810
|
/**
|
|
4692
4811
|
* Resolve the browser/runtime timezone for booking surfaces.
|
|
@@ -5751,6 +5870,19 @@ function clearBookingFieldError(errors, fieldId) {
|
|
|
5751
5870
|
};
|
|
5752
5871
|
}
|
|
5753
5872
|
//#endregion
|
|
5873
|
+
//#region ../blocks/src/system/runtime/nodes/booking-form.field-values.ts
|
|
5874
|
+
function collectInitialBookingFieldValues(initialData) {
|
|
5875
|
+
const values = {};
|
|
5876
|
+
for (const [key, value] of Object.entries(initialData)) if (!BOOKING_FORM_DATA_RESERVED_KEYS.has(key) && !isBookingRequirementConsentFieldId(key) && isBookingFieldValue(value)) values[key] = value;
|
|
5877
|
+
return values;
|
|
5878
|
+
}
|
|
5879
|
+
function omitBookingConsentEntries(record) {
|
|
5880
|
+
return Object.fromEntries(Object.entries(record).filter(([key]) => !isBookingRequirementConsentFieldId(key)));
|
|
5881
|
+
}
|
|
5882
|
+
function isBookingFieldValue(value) {
|
|
5883
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
5884
|
+
}
|
|
5885
|
+
//#endregion
|
|
5754
5886
|
//#region ../blocks/src/system/runtime/nodes/booking-form.headless.state.ts
|
|
5755
5887
|
const unsafeAsBookingFieldId = (id) => id;
|
|
5756
5888
|
function createInitialBookingFlowReducerState(input) {
|
|
@@ -5762,7 +5894,7 @@ function createInitialBookingFlowReducerState(input) {
|
|
|
5762
5894
|
selectedResourceId: toOptionalAppointmentResourceId(toOptionalString(initialData.resourceId) ?? input.defaultResourceId),
|
|
5763
5895
|
selectedDate: toOptionalString(initialData.selectedDate),
|
|
5764
5896
|
selectedSlot: null,
|
|
5765
|
-
fieldValues:
|
|
5897
|
+
fieldValues: collectInitialBookingFieldValues(initialData),
|
|
5766
5898
|
touchedFields: {},
|
|
5767
5899
|
attemptedStepIds: {},
|
|
5768
5900
|
discountCode: toOptionalString(initialData.discountCode) ?? "",
|
|
@@ -5774,14 +5906,6 @@ function createInitialBookingFlowReducerState(input) {
|
|
|
5774
5906
|
feedback: { kind: "none" }
|
|
5775
5907
|
};
|
|
5776
5908
|
}
|
|
5777
|
-
function collectInitialFieldValues(initialData) {
|
|
5778
|
-
const values = {};
|
|
5779
|
-
for (const [key, value] of Object.entries(initialData)) {
|
|
5780
|
-
if (BOOKING_FORM_DATA_RESERVED_KEYS.has(key) || !isBookingFieldValue(value)) continue;
|
|
5781
|
-
values[key] = value;
|
|
5782
|
-
}
|
|
5783
|
-
return values;
|
|
5784
|
-
}
|
|
5785
5909
|
function toOptionalString(value) {
|
|
5786
5910
|
if (typeof value !== "string") return void 0;
|
|
5787
5911
|
const trimmed = value.trim();
|
|
@@ -5803,9 +5927,6 @@ function toOptionalAppointmentResourceId(value) {
|
|
|
5803
5927
|
function toOptionalCustomerPassId(value) {
|
|
5804
5928
|
return toOptionalBrandedId(value, unsafeAsCustomerPassId);
|
|
5805
5929
|
}
|
|
5806
|
-
function isBookingFieldValue(value) {
|
|
5807
|
-
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
5808
|
-
}
|
|
5809
5930
|
/**
|
|
5810
5931
|
* Send the flow back to the date step when the slot it was holding went away,
|
|
5811
5932
|
* leaving a scoped feedback message. A completed booking is left alone.
|
|
@@ -5827,17 +5948,7 @@ function applyAvailabilitySelectionInvalidation(state, invalidation) {
|
|
|
5827
5948
|
}
|
|
5828
5949
|
function bookingFlowReducer(state, event) {
|
|
5829
5950
|
switch (event.type) {
|
|
5830
|
-
case "service-selected": return
|
|
5831
|
-
...state,
|
|
5832
|
-
selectedServiceId: event.serviceId,
|
|
5833
|
-
selectedResourceId: void 0,
|
|
5834
|
-
selectedDate: void 0,
|
|
5835
|
-
selectedSlot: null,
|
|
5836
|
-
selectedAppointmentPackageId: void 0,
|
|
5837
|
-
selectedSlidingAmountInput: "",
|
|
5838
|
-
selectedCollectionOptionKind: void 0,
|
|
5839
|
-
validationErrors: clearBookingFormError(state.validationErrors)
|
|
5840
|
-
};
|
|
5951
|
+
case "service-selected": return applyBookingServiceSelection(state, event.serviceId);
|
|
5841
5952
|
case "resource-selected": return {
|
|
5842
5953
|
...state,
|
|
5843
5954
|
selectedResourceId: event.resourceId ?? void 0,
|
|
@@ -5944,6 +6055,24 @@ function bookingFlowReducer(state, event) {
|
|
|
5944
6055
|
default: return assertNever(event);
|
|
5945
6056
|
}
|
|
5946
6057
|
}
|
|
6058
|
+
function applyBookingServiceSelection(state, serviceId) {
|
|
6059
|
+
return {
|
|
6060
|
+
...state,
|
|
6061
|
+
selectedServiceId: serviceId,
|
|
6062
|
+
selectedResourceId: void 0,
|
|
6063
|
+
selectedDate: void 0,
|
|
6064
|
+
selectedSlot: null,
|
|
6065
|
+
selectedAppointmentPackageId: void 0,
|
|
6066
|
+
selectedSlidingAmountInput: "",
|
|
6067
|
+
selectedCollectionOptionKind: void 0,
|
|
6068
|
+
fieldValues: omitBookingConsentEntries(state.fieldValues),
|
|
6069
|
+
touchedFields: omitBookingConsentEntries(state.touchedFields),
|
|
6070
|
+
validationErrors: {
|
|
6071
|
+
...clearBookingFormError(state.validationErrors),
|
|
6072
|
+
fieldErrors: omitBookingConsentEntries(state.validationErrors.fieldErrors)
|
|
6073
|
+
}
|
|
6074
|
+
};
|
|
6075
|
+
}
|
|
5947
6076
|
function clearAvailabilityFeedback(feedback) {
|
|
5948
6077
|
return feedback.kind === "error" && feedback.scope === "availability" ? { kind: "none" } : feedback;
|
|
5949
6078
|
}
|
|
@@ -6104,7 +6233,8 @@ function validateBookingFields(input) {
|
|
|
6104
6233
|
return errors;
|
|
6105
6234
|
}
|
|
6106
6235
|
function validateBookingField(field, value) {
|
|
6107
|
-
|
|
6236
|
+
const requiredError = validateRequiredBookingField(field, value);
|
|
6237
|
+
if (requiredError) return requiredError;
|
|
6108
6238
|
if (isBlankFieldValue(value)) return null;
|
|
6109
6239
|
const strValue = typeof value === "string" ? value : String(value);
|
|
6110
6240
|
if (field.type === "email") return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(strValue) ? null : "Please enter a valid email address";
|
|
@@ -6133,6 +6263,10 @@ function validateBookingField(field, value) {
|
|
|
6133
6263
|
}
|
|
6134
6264
|
return null;
|
|
6135
6265
|
}
|
|
6266
|
+
function validateRequiredBookingField(field, value) {
|
|
6267
|
+
if (field.type === "booking_requirement_consent") return value === true ? null : `${field.label} is required`;
|
|
6268
|
+
return field.required && isBlankFieldValue(value) ? `${field.label} is required` : null;
|
|
6269
|
+
}
|
|
6136
6270
|
function deriveBookingFlowSteps(input) {
|
|
6137
6271
|
const stepResolution = buildBookingStepResolution({
|
|
6138
6272
|
form: input.form,
|
|
@@ -6204,7 +6338,8 @@ function deriveBookingFieldViews(input) {
|
|
|
6204
6338
|
return (input.form?.schema?.fields ?? []).map((field) => {
|
|
6205
6339
|
const fieldId = unsafeAsBookingFieldId(field.id);
|
|
6206
6340
|
const inputId = `${input.idPrefix}-${fieldId}`;
|
|
6207
|
-
const
|
|
6341
|
+
const helpText = field.type === "booking_requirement_consent" ? null : field.helpText ?? null;
|
|
6342
|
+
const helpTextId = helpText ? `${inputId}-help` : null;
|
|
6208
6343
|
const fieldValue = input.values[field.id] ?? null;
|
|
6209
6344
|
const localError = input.touched?.[field.id] ? validateBookingField(field, fieldValue) : null;
|
|
6210
6345
|
const error = input.errors[field.id] ?? localError;
|
|
@@ -6214,17 +6349,17 @@ function deriveBookingFieldViews(input) {
|
|
|
6214
6349
|
field,
|
|
6215
6350
|
id: fieldId,
|
|
6216
6351
|
label: field.label,
|
|
6217
|
-
type: field.type,
|
|
6218
6352
|
value: fieldValue,
|
|
6219
6353
|
required: Boolean(field.required),
|
|
6220
6354
|
error,
|
|
6221
|
-
helpText
|
|
6355
|
+
helpText,
|
|
6222
6356
|
inputId,
|
|
6223
6357
|
labelId: `${inputId}-label`,
|
|
6224
6358
|
helpTextId,
|
|
6225
6359
|
errorId,
|
|
6226
6360
|
describedBy,
|
|
6227
|
-
ariaInvalid: Boolean(error)
|
|
6361
|
+
ariaInvalid: Boolean(error),
|
|
6362
|
+
type: field.type
|
|
6228
6363
|
};
|
|
6229
6364
|
});
|
|
6230
6365
|
}
|
|
@@ -6486,6 +6621,7 @@ function useHeadlessBookingFlow(options) {
|
|
|
6486
6621
|
const selectedServiceId = state.selectedServiceId ?? defaultServiceId;
|
|
6487
6622
|
const selectedResourceId = state.selectedResourceId ?? defaultResourceId;
|
|
6488
6623
|
const selectedService = options.services.find((service) => service.id === selectedServiceId) ?? null;
|
|
6624
|
+
const runtimeForm = useSelectedAppointmentConsentForm(options.form, selectedService);
|
|
6489
6625
|
const lastSlidingServiceIdRef = useRef(null);
|
|
6490
6626
|
useEffect(() => {
|
|
6491
6627
|
const selectedId = selectedService?.id ?? null;
|
|
@@ -6526,7 +6662,7 @@ function useHeadlessBookingFlow(options) {
|
|
|
6526
6662
|
viewerTimeZone
|
|
6527
6663
|
]);
|
|
6528
6664
|
const stepState = useMemo(() => deriveBookingFlowSteps({
|
|
6529
|
-
form:
|
|
6665
|
+
form: runtimeForm,
|
|
6530
6666
|
services: options.services,
|
|
6531
6667
|
data,
|
|
6532
6668
|
defaultServiceId,
|
|
@@ -6542,7 +6678,7 @@ function useHeadlessBookingFlow(options) {
|
|
|
6542
6678
|
defaultServiceId,
|
|
6543
6679
|
defaultResourceId,
|
|
6544
6680
|
options.enableAppointmentPackages,
|
|
6545
|
-
|
|
6681
|
+
runtimeForm,
|
|
6546
6682
|
options.requiresResourceSelection,
|
|
6547
6683
|
options.requiresServiceSelection,
|
|
6548
6684
|
options.services,
|
|
@@ -6596,13 +6732,13 @@ function useHeadlessBookingFlow(options) {
|
|
|
6596
6732
|
state.selectedSlot
|
|
6597
6733
|
]);
|
|
6598
6734
|
const fields = useMemo(() => deriveBookingFieldViews({
|
|
6599
|
-
form:
|
|
6735
|
+
form: runtimeForm,
|
|
6600
6736
|
values: state.fieldValues,
|
|
6601
6737
|
errors: state.validationErrors.fieldErrors,
|
|
6602
6738
|
touched: state.touchedFields,
|
|
6603
6739
|
idPrefix: `booking-${options.formId}`
|
|
6604
6740
|
}), [
|
|
6605
|
-
|
|
6741
|
+
runtimeForm,
|
|
6606
6742
|
options.formId,
|
|
6607
6743
|
state.fieldValues,
|
|
6608
6744
|
state.touchedFields,
|
|
@@ -6722,7 +6858,7 @@ function useHeadlessBookingFlow(options) {
|
|
|
6722
6858
|
const latestRef = useLatestRef({
|
|
6723
6859
|
state,
|
|
6724
6860
|
data,
|
|
6725
|
-
form:
|
|
6861
|
+
form: runtimeForm,
|
|
6726
6862
|
formId: formKey,
|
|
6727
6863
|
services: options.services,
|
|
6728
6864
|
defaultServiceId,
|
|
@@ -6974,7 +7110,7 @@ function useHeadlessBookingFlow(options) {
|
|
|
6974
7110
|
phase: state.phase
|
|
6975
7111
|
});
|
|
6976
7112
|
const view = resolveHeadlessBookingView({
|
|
6977
|
-
form:
|
|
7113
|
+
form: runtimeForm,
|
|
6978
7114
|
feedback: state.feedback
|
|
6979
7115
|
});
|
|
6980
7116
|
const appointmentPackagesModel = resolveAppointmentPackagesModel({
|
|
@@ -12416,6 +12552,768 @@ function FieldValidationMessage({ id, children, className, live = "assertive" })
|
|
|
12416
12552
|
});
|
|
12417
12553
|
}
|
|
12418
12554
|
//#endregion
|
|
12555
|
+
//#region ../blocks/src/primitives/content/utils/sanitizeHref.ts
|
|
12556
|
+
const SAFE_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
12557
|
+
"http:",
|
|
12558
|
+
"https:",
|
|
12559
|
+
"mailto:",
|
|
12560
|
+
"tel:"
|
|
12561
|
+
]);
|
|
12562
|
+
const CONTROL_CHARS_PATTERN = /[\u0000-\u001F\u007F]+/g;
|
|
12563
|
+
function normalizeInput(value) {
|
|
12564
|
+
return value.replace(CONTROL_CHARS_PATTERN, "").trim();
|
|
12565
|
+
}
|
|
12566
|
+
function decodeForSchemeCheck(value) {
|
|
12567
|
+
try {
|
|
12568
|
+
return decodeURIComponent(value);
|
|
12569
|
+
} catch {
|
|
12570
|
+
return value;
|
|
12571
|
+
}
|
|
12572
|
+
}
|
|
12573
|
+
/**
|
|
12574
|
+
* Returns a safe href for rich-text anchors, or null when the value should not be rendered.
|
|
12575
|
+
*
|
|
12576
|
+
* Policy:
|
|
12577
|
+
* - Allow absolute URLs with protocols: http, https, mailto, tel.
|
|
12578
|
+
* - Allow root-relative ("/path") and hash-only ("#section") links.
|
|
12579
|
+
* - Reject protocol-relative URLs ("//example.com"), bare domains ("example.com"),
|
|
12580
|
+
* executable schemes (javascript/data/vbscript), malformed URLs, and empty values.
|
|
12581
|
+
* - Preserve percent-encoding from the original normalized input.
|
|
12582
|
+
*/
|
|
12583
|
+
function sanitizeHref(raw) {
|
|
12584
|
+
if (typeof raw !== "string") return null;
|
|
12585
|
+
const normalized = normalizeInput(raw);
|
|
12586
|
+
if (!normalized) return null;
|
|
12587
|
+
if (normalized.startsWith("//")) return null;
|
|
12588
|
+
if (normalized.startsWith("/")) return normalized;
|
|
12589
|
+
if (normalized.startsWith("#")) return normalized;
|
|
12590
|
+
const schemeMatch = normalizeInput(decodeForSchemeCheck(normalized)).match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
12591
|
+
if (!schemeMatch) return null;
|
|
12592
|
+
const rawScheme = schemeMatch[1];
|
|
12593
|
+
if (!rawScheme) return null;
|
|
12594
|
+
const protocol = `${rawScheme.toLowerCase()}:`;
|
|
12595
|
+
if (!SAFE_PROTOCOLS.has(protocol)) return null;
|
|
12596
|
+
try {
|
|
12597
|
+
const parsed = new URL(normalized);
|
|
12598
|
+
if (!SAFE_PROTOCOLS.has(parsed.protocol.toLowerCase())) return null;
|
|
12599
|
+
} catch {
|
|
12600
|
+
return null;
|
|
12601
|
+
}
|
|
12602
|
+
return normalized;
|
|
12603
|
+
}
|
|
12604
|
+
//#endregion
|
|
12605
|
+
//#region ../media-core/src/fileSafety.ts
|
|
12606
|
+
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
|
|
12607
|
+
function containsUnsafePathSegmentCharacter(value) {
|
|
12608
|
+
return value.includes("/") || value.includes("\\") || CONTROL_CHARACTER_PATTERN.test(value);
|
|
12609
|
+
}
|
|
12610
|
+
function isReservedPathSegment(value) {
|
|
12611
|
+
return value === "." || value === "..";
|
|
12612
|
+
}
|
|
12613
|
+
function isUnsafePathSegment(segment) {
|
|
12614
|
+
return segment.trim().length === 0 || segment !== segment.trim() || isReservedPathSegment(segment) || containsUnsafePathSegmentCharacter(segment);
|
|
12615
|
+
}
|
|
12616
|
+
({
|
|
12617
|
+
publicSite: unsafeAsStorageBucketName("media"),
|
|
12618
|
+
privateSite: unsafeAsStorageBucketName("site-files-private"),
|
|
12619
|
+
temporary: unsafeAsStorageBucketName("temp-uploads")
|
|
12620
|
+
}).publicSite;
|
|
12621
|
+
function parseStoragePath(value) {
|
|
12622
|
+
return joinStoragePathSegments(value.split("/"));
|
|
12623
|
+
}
|
|
12624
|
+
/**
|
|
12625
|
+
* Joins already-branded path segments while preserving the Storage RLS invariant
|
|
12626
|
+
* that every tenant/site identifier remains one segment, never a nested path.
|
|
12627
|
+
*/
|
|
12628
|
+
function joinStoragePathSegments(segments) {
|
|
12629
|
+
if (segments.some(isUnsafePathSegment)) return err$1("unsafe-path-segment");
|
|
12630
|
+
return ok$1(unsafeAsStoragePath(segments.join("/")));
|
|
12631
|
+
}
|
|
12632
|
+
//#endregion
|
|
12633
|
+
//#region ../media-core/src/storagePathDomain.ts
|
|
12634
|
+
function ok(value) {
|
|
12635
|
+
return {
|
|
12636
|
+
ok: true,
|
|
12637
|
+
value
|
|
12638
|
+
};
|
|
12639
|
+
}
|
|
12640
|
+
function fail(error) {
|
|
12641
|
+
return {
|
|
12642
|
+
ok: false,
|
|
12643
|
+
error
|
|
12644
|
+
};
|
|
12645
|
+
}
|
|
12646
|
+
function normalizeStringInput(value) {
|
|
12647
|
+
if (typeof value !== "string") return fail({ tag: "MISSING" });
|
|
12648
|
+
const trimmed = value.trim();
|
|
12649
|
+
if (!trimmed) return fail({ tag: "EMPTY" });
|
|
12650
|
+
return ok(trimmed);
|
|
12651
|
+
}
|
|
12652
|
+
function validateRelativeStoragePath(value) {
|
|
12653
|
+
if (value.startsWith("sites/")) return fail({ tag: "SITE_SCOPED_PATH" });
|
|
12654
|
+
if (value.startsWith("/")) return fail({ tag: "LEADING_SLASH" });
|
|
12655
|
+
if (value.includes("//")) return fail({ tag: "DOUBLE_SLASH" });
|
|
12656
|
+
if (value.includes("%")) return fail({ tag: "PERCENT_ENCODING" });
|
|
12657
|
+
if (value.split("/").some((segment) => segment === "." || segment === "..")) return fail({ tag: "TRAVERSAL" });
|
|
12658
|
+
return ok(value);
|
|
12659
|
+
}
|
|
12660
|
+
function brandRelativeStoragePath(value) {
|
|
12661
|
+
return value;
|
|
12662
|
+
}
|
|
12663
|
+
function parseRelativeStoragePath(value) {
|
|
12664
|
+
const normalized = normalizeStringInput(value);
|
|
12665
|
+
if (!normalized.ok) return normalized;
|
|
12666
|
+
const validated = validateRelativeStoragePath(normalized.value);
|
|
12667
|
+
if (!validated.ok) return validated;
|
|
12668
|
+
return ok(brandRelativeStoragePath(validated.value));
|
|
12669
|
+
}
|
|
12670
|
+
//#endregion
|
|
12671
|
+
//#region ../media-core/src/url.ts
|
|
12672
|
+
const DEFAULT_BASE_PATH = "/api/img";
|
|
12673
|
+
function isSiteScopedDirectImageStoragePath(storagePath) {
|
|
12674
|
+
return storagePath.startsWith("sites/") && parseStoragePath(storagePath).ok;
|
|
12675
|
+
}
|
|
12676
|
+
function encodeStoragePath(storagePath) {
|
|
12677
|
+
return storagePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
12678
|
+
}
|
|
12679
|
+
function resolveDirectImagePathTarget(target) {
|
|
12680
|
+
switch (target.kind) {
|
|
12681
|
+
case "relative": return `sites/${target.siteId}/${target.storagePath}`;
|
|
12682
|
+
case "site-scoped":
|
|
12683
|
+
case "bucket-scoped": return target.storagePath;
|
|
12684
|
+
}
|
|
12685
|
+
}
|
|
12686
|
+
function getDirectImageUrl(supabaseUrl, target, storageBucket = "media", options) {
|
|
12687
|
+
const baseUrl = supabaseUrl.replace(/\/$/, "");
|
|
12688
|
+
const encodedPath = encodeStoragePath(resolveDirectImagePathTarget(target));
|
|
12689
|
+
const isSvg = target.storagePath.toLowerCase().endsWith(".svg");
|
|
12690
|
+
if (options && !isSvg && (options.width || options.height)) {
|
|
12691
|
+
const params = new URLSearchParams();
|
|
12692
|
+
if (options.width) params.set("width", String(options.width));
|
|
12693
|
+
if (options.height) params.set("height", String(options.height));
|
|
12694
|
+
if (options.quality) params.set("quality", String(options.quality));
|
|
12695
|
+
if (options.resize) params.set("resize", options.resize);
|
|
12696
|
+
return `${baseUrl}/storage/v1/render/image/public/${storageBucket}/${encodedPath}?${params.toString()}`;
|
|
12697
|
+
}
|
|
12698
|
+
return `${baseUrl}/storage/v1/object/public/${storageBucket}/${encodedPath}`;
|
|
12699
|
+
}
|
|
12700
|
+
function getDirectImageUrlForRelativePath(supabaseUrl, storagePath, siteId, storageBucket = "media", options) {
|
|
12701
|
+
return getDirectImageUrl(supabaseUrl, {
|
|
12702
|
+
kind: "relative",
|
|
12703
|
+
storagePath,
|
|
12704
|
+
siteId
|
|
12705
|
+
}, storageBucket, options);
|
|
12706
|
+
}
|
|
12707
|
+
function getDirectImageUrlForSiteScopedPath(supabaseUrl, storagePath, storageBucket = "media", options) {
|
|
12708
|
+
return getDirectImageUrl(supabaseUrl, {
|
|
12709
|
+
kind: "site-scoped",
|
|
12710
|
+
storagePath
|
|
12711
|
+
}, storageBucket, options);
|
|
12712
|
+
}
|
|
12713
|
+
const formatNumber = (value) => Number.isInteger(value) ? value.toString() : value.toFixed(6).replace(/0+$/, "").replace(/\.$/, "");
|
|
12714
|
+
const ORDER = [
|
|
12715
|
+
"w",
|
|
12716
|
+
"h",
|
|
12717
|
+
"fit",
|
|
12718
|
+
"rect",
|
|
12719
|
+
"q",
|
|
12720
|
+
"autoFormat",
|
|
12721
|
+
"or"
|
|
12722
|
+
];
|
|
12723
|
+
const getImageUrl = (asset, params = {}, basePath = DEFAULT_BASE_PATH) => {
|
|
12724
|
+
const path = `${basePath.endsWith("/") ? basePath.slice(0, Math.max(basePath.length - 1, 0)) : basePath}/${encodeURIComponent(asset.id)}`;
|
|
12725
|
+
const searchParams = new URLSearchParams();
|
|
12726
|
+
if (params.w !== void 0) searchParams.set("w", formatNumber(params.w));
|
|
12727
|
+
if (params.h !== void 0) searchParams.set("h", formatNumber(params.h));
|
|
12728
|
+
if (params.fit !== void 0) searchParams.set("fit", params.fit);
|
|
12729
|
+
if (params.rect) searchParams.set("rect", params.rect.map(formatNumber).join(","));
|
|
12730
|
+
if (params.q !== void 0) searchParams.set("q", formatNumber(params.q));
|
|
12731
|
+
if (params.autoFormat) searchParams.append("auto", "format");
|
|
12732
|
+
if (params.or !== void 0) searchParams.set("or", String(params.or));
|
|
12733
|
+
const sorted = new URLSearchParams();
|
|
12734
|
+
ORDER.forEach((key) => {
|
|
12735
|
+
if (key === "autoFormat") {
|
|
12736
|
+
if (params.autoFormat) sorted.append("auto", "format");
|
|
12737
|
+
return;
|
|
12738
|
+
}
|
|
12739
|
+
const value = searchParams.get(key);
|
|
12740
|
+
if (value !== null) sorted.set(key, value);
|
|
12741
|
+
});
|
|
12742
|
+
const query = sorted.toString();
|
|
12743
|
+
return query ? `${path}?${query}` : path;
|
|
12744
|
+
};
|
|
12745
|
+
//#endregion
|
|
12746
|
+
//#region ../media-core/src/transformRect.ts
|
|
12747
|
+
function isValidTransformRectStructure(rect) {
|
|
12748
|
+
if (!Array.isArray(rect) || rect.length !== 4) return false;
|
|
12749
|
+
const [left, top, width, height] = rect;
|
|
12750
|
+
return Number.isFinite(left) && Number.isFinite(top) && Number.isFinite(width) && Number.isFinite(height) && left >= 0 && top >= 0 && width > 0 && height > 0;
|
|
12751
|
+
}
|
|
12752
|
+
//#endregion
|
|
12753
|
+
//#region ../media-core/src/aspectTransforms.ts
|
|
12754
|
+
const DEFAULT_RECT_EPSILON = .5;
|
|
12755
|
+
function parseAspectRatioValue(value) {
|
|
12756
|
+
if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : void 0;
|
|
12757
|
+
if (typeof value !== "string") return void 0;
|
|
12758
|
+
const normalized = value.trim();
|
|
12759
|
+
if (!normalized) return void 0;
|
|
12760
|
+
if (normalized === "auto") return void 0;
|
|
12761
|
+
if (normalized.includes(":") || normalized.includes("/")) {
|
|
12762
|
+
const delimiter = normalized.includes(":") ? ":" : "/";
|
|
12763
|
+
const [wRaw, hRaw] = normalized.split(delimiter);
|
|
12764
|
+
const width = Number(wRaw?.trim());
|
|
12765
|
+
const height = Number(hRaw?.trim());
|
|
12766
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
|
|
12767
|
+
return width / height;
|
|
12768
|
+
}
|
|
12769
|
+
const numeric = Number(normalized);
|
|
12770
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return void 0;
|
|
12771
|
+
return numeric;
|
|
12772
|
+
}
|
|
12773
|
+
const MAX_ASPECT_DISTANCE = .25;
|
|
12774
|
+
function resolveMediaTransformForAspect(transform, targetAspect) {
|
|
12775
|
+
if (!transform) return {};
|
|
12776
|
+
const base = {
|
|
12777
|
+
rect: transform.rect,
|
|
12778
|
+
hotspot: transform.hotspot,
|
|
12779
|
+
rotation: transform.rotation
|
|
12780
|
+
};
|
|
12781
|
+
const target = parseAspectRatioValue(targetAspect);
|
|
12782
|
+
const crops = transform.aspectCrops;
|
|
12783
|
+
if (!target || !crops || Object.keys(crops).length === 0) return base;
|
|
12784
|
+
let best = null;
|
|
12785
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
12786
|
+
for (const [key, candidate] of Object.entries(crops)) {
|
|
12787
|
+
const candidateAspect = parseAspectRatioValue(candidate.aspect ?? key);
|
|
12788
|
+
if (!candidateAspect) continue;
|
|
12789
|
+
const distance = Math.abs(Math.log(candidateAspect / target));
|
|
12790
|
+
if (distance < bestDistance) {
|
|
12791
|
+
best = candidate;
|
|
12792
|
+
bestDistance = distance;
|
|
12793
|
+
}
|
|
12794
|
+
}
|
|
12795
|
+
if (!best) return base;
|
|
12796
|
+
if (bestDistance > MAX_ASPECT_DISTANCE) return base;
|
|
12797
|
+
return {
|
|
12798
|
+
rect: best.rect ?? base.rect,
|
|
12799
|
+
hotspot: best.hotspot ?? base.hotspot,
|
|
12800
|
+
rotation: best.rotation ?? base.rotation
|
|
12801
|
+
};
|
|
12802
|
+
}
|
|
12803
|
+
function isValidTransformRect(rect) {
|
|
12804
|
+
return isValidTransformRectStructure(rect);
|
|
12805
|
+
}
|
|
12806
|
+
function isExplicitRectCrop(rect, sourceWidth, sourceHeight, epsilon = DEFAULT_RECT_EPSILON) {
|
|
12807
|
+
if (!isValidTransformRect(rect)) return false;
|
|
12808
|
+
if (!(typeof sourceWidth === "number" && Number.isFinite(sourceWidth) && sourceWidth > 0 && typeof sourceHeight === "number" && Number.isFinite(sourceHeight) && sourceHeight > 0)) return true;
|
|
12809
|
+
const [x, y, width, height] = rect;
|
|
12810
|
+
return !(Math.abs(x) <= epsilon && Math.abs(y) <= epsilon && Math.abs(width - sourceWidth) <= epsilon && Math.abs(height - sourceHeight) <= epsilon);
|
|
12811
|
+
}
|
|
12812
|
+
//#endregion
|
|
12813
|
+
//#region ../media-core/src/renderableMediaAsset.ts
|
|
12814
|
+
function attachRenderableMediaAssetFields(media, asset) {
|
|
12815
|
+
return {
|
|
12816
|
+
...media,
|
|
12817
|
+
assetId: asset.id,
|
|
12818
|
+
id: asset.id,
|
|
12819
|
+
identifier: mediaIdentifierOrFallback(media.identifier, asset.identifier),
|
|
12820
|
+
storageBucket: asset.storageBucket ?? media.storageBucket,
|
|
12821
|
+
storagePath: asset.storagePath ?? media.storagePath,
|
|
12822
|
+
width: asset.width ?? media.width,
|
|
12823
|
+
height: asset.height ?? media.height,
|
|
12824
|
+
mimeType: asset.mimeType ?? media.mimeType
|
|
12825
|
+
};
|
|
12826
|
+
}
|
|
12827
|
+
/**
|
|
12828
|
+
* Preserve an explicit per-usage identifier when one exists. Otherwise prefer
|
|
12829
|
+
* the canonical asset identifier, while keeping intentionally blank legacy
|
|
12830
|
+
* string values from becoming null.
|
|
12831
|
+
*/
|
|
12832
|
+
function mediaIdentifierOrFallback(current, fallback) {
|
|
12833
|
+
if (typeof current === "string" && current.trim().length > 0) return current;
|
|
12834
|
+
return fallback ?? (typeof current === "string" ? current : void 0);
|
|
12835
|
+
}
|
|
12836
|
+
//#endregion
|
|
12837
|
+
//#region ../blocks/src/system/runtime/nodes/media.tsx
|
|
12838
|
+
const MAX_RENDER_DIMENSION = 4096;
|
|
12839
|
+
const RELATIVE_IMG_PROXY_BASE_PATH = "/api/img";
|
|
12840
|
+
function getValidSourceDimension(value) {
|
|
12841
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
12842
|
+
}
|
|
12843
|
+
function isExplicitCropProxyEnabled() {
|
|
12844
|
+
const raw = process.env.NEXT_PUBLIC_MEDIA_EXPLICIT_CROP_PROXY_ENABLED ?? process.env.MEDIA_EXPLICIT_CROP_PROXY_ENABLED;
|
|
12845
|
+
if (!raw) return true;
|
|
12846
|
+
const normalized = raw.trim().toLowerCase();
|
|
12847
|
+
return !(normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off");
|
|
12848
|
+
}
|
|
12849
|
+
function getRequestedDimension(value) {
|
|
12850
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
12851
|
+
return Math.min(MAX_RENDER_DIMENSION, Math.max(1, Math.round(value)));
|
|
12852
|
+
}
|
|
12853
|
+
function mapResizeToFit(resize) {
|
|
12854
|
+
if (resize === "contain") return "clip";
|
|
12855
|
+
return "crop";
|
|
12856
|
+
}
|
|
12857
|
+
function shouldUseExplicitRectProxy(value, options, resolvedTransform, sourceWidth, sourceHeight) {
|
|
12858
|
+
if ("original" in options && options.original) return false;
|
|
12859
|
+
if (!isExplicitCropProxyEnabled()) return false;
|
|
12860
|
+
if (!value.assetId) return false;
|
|
12861
|
+
if (typeof resolvedTransform.rotation === "number" && resolvedTransform.rotation !== 0) return true;
|
|
12862
|
+
return isExplicitRectCrop(resolvedTransform.rect, sourceWidth, sourceHeight);
|
|
12863
|
+
}
|
|
12864
|
+
function buildProxyTransformParams(options, resolvedTransform) {
|
|
12865
|
+
const params = {
|
|
12866
|
+
w: "width" in options ? getRequestedDimension(options.width) : void 0,
|
|
12867
|
+
h: "height" in options ? getRequestedDimension(options.height) : void 0,
|
|
12868
|
+
q: "quality" in options ? options.quality : void 0,
|
|
12869
|
+
fit: mapResizeToFit("resize" in options ? options.resize : void 0),
|
|
12870
|
+
autoFormat: true
|
|
12871
|
+
};
|
|
12872
|
+
if (resolvedTransform.rect) params.rect = resolvedTransform.rect;
|
|
12873
|
+
if (resolvedTransform.rotation) params.or = resolvedTransform.rotation;
|
|
12874
|
+
return params;
|
|
12875
|
+
}
|
|
12876
|
+
function buildDirectTransformOptions(options) {
|
|
12877
|
+
if ("original" in options && options.original) return void 0;
|
|
12878
|
+
return {
|
|
12879
|
+
width: "width" in options ? getRequestedDimension(options.width) : void 0,
|
|
12880
|
+
height: "height" in options ? getRequestedDimension(options.height) : void 0,
|
|
12881
|
+
quality: "quality" in options ? options.quality : void 0,
|
|
12882
|
+
resize: "resize" in options ? options.resize : void 0
|
|
12883
|
+
};
|
|
12884
|
+
}
|
|
12885
|
+
function resolveTransformResolution(value, targetAspectRatio) {
|
|
12886
|
+
return {
|
|
12887
|
+
resolvedTransform: resolveMediaTransformForAspect(value.transform, targetAspectRatio),
|
|
12888
|
+
sourceWidth: getValidSourceDimension(value.width),
|
|
12889
|
+
sourceHeight: getValidSourceDimension(value.height)
|
|
12890
|
+
};
|
|
12891
|
+
}
|
|
12892
|
+
function getProxyImageUrl(value, options, transformAspectRatio, resolved) {
|
|
12893
|
+
if (!value.assetId) return void 0;
|
|
12894
|
+
const targetAspect = transformAspectRatio ?? resolveTargetAspectRatio(options);
|
|
12895
|
+
const { resolvedTransform, sourceWidth, sourceHeight } = resolved ?? resolveTransformResolution(value, targetAspect);
|
|
12896
|
+
if (!shouldUseExplicitRectProxy(value, options, resolvedTransform, sourceWidth, sourceHeight)) return void 0;
|
|
12897
|
+
const params = buildProxyTransformParams(options, resolvedTransform);
|
|
12898
|
+
const proxyBasePath = getImageProxyBasePath();
|
|
12899
|
+
const proxyUrl = getImageUrl({ id: unsafeAsMediaAssetId(value.assetId) }, params, proxyBasePath);
|
|
12900
|
+
debugMediaNodeLog("resolve:proxy-img", {
|
|
12901
|
+
assetId: value.assetId,
|
|
12902
|
+
params,
|
|
12903
|
+
proxyBasePath,
|
|
12904
|
+
proxyUrl
|
|
12905
|
+
});
|
|
12906
|
+
return proxyUrl;
|
|
12907
|
+
}
|
|
12908
|
+
function warnForMediaUrlResolution(result) {
|
|
12909
|
+
switch (result.kind) {
|
|
12910
|
+
case "missing-site-context":
|
|
12911
|
+
console.warn(`[blocks] Relative storagePath "${result.storagePath}" requires siteId context. Media URL may be incorrect. Ensure siteId is set via runWithMediaContext or setContextSiteId.`);
|
|
12912
|
+
return;
|
|
12913
|
+
case "missing-supabase-url":
|
|
12914
|
+
console.warn(`[blocks] ${result.message} Media URL may be unavailable. Ensure NEXT_PUBLIC_SUPABASE_URL is configured or pass supabaseUrl in the media context.`);
|
|
12915
|
+
return;
|
|
12916
|
+
case "invalid-storage-path":
|
|
12917
|
+
console.warn(`[blocks] Invalid media storagePath "${result.storagePath}".`);
|
|
12918
|
+
return;
|
|
12919
|
+
case "resolved":
|
|
12920
|
+
case "unsupported-media-type":
|
|
12921
|
+
case "missing-storage-path":
|
|
12922
|
+
case "missing-image-url": return;
|
|
12923
|
+
}
|
|
12924
|
+
}
|
|
12925
|
+
function resolveMediaStorageUrlResult(value, context, transformOptions) {
|
|
12926
|
+
const canonical = resolveMediaWithCanonicalAsset(value, context ?? void 0);
|
|
12927
|
+
if (!canonical.storagePath || canonical.storagePath.length === 0) return {
|
|
12928
|
+
kind: "missing-storage-path",
|
|
12929
|
+
assetId: canonical.assetId,
|
|
12930
|
+
identifier: canonical.identifier
|
|
12931
|
+
};
|
|
12932
|
+
const siteId = context?.siteId ?? getSiteId();
|
|
12933
|
+
const bucket = canonical.storageBucket ?? "media";
|
|
12934
|
+
const siteScopedPath = isSiteScopedDirectImageStoragePath(canonical.storagePath) ? canonical.storagePath : null;
|
|
12935
|
+
let supabaseUrl;
|
|
12936
|
+
try {
|
|
12937
|
+
supabaseUrl = context?.supabaseUrl?.replace(/\/$/, "") ?? getSupabaseUrl();
|
|
12938
|
+
} catch (error) {
|
|
12939
|
+
return {
|
|
12940
|
+
kind: "missing-supabase-url",
|
|
12941
|
+
message: error instanceof Error ? error.message : "Supabase URL is not configured."
|
|
12942
|
+
};
|
|
12943
|
+
}
|
|
12944
|
+
let directUrl;
|
|
12945
|
+
if (siteScopedPath) directUrl = getDirectImageUrlForSiteScopedPath(supabaseUrl, siteScopedPath, bucket, transformOptions);
|
|
12946
|
+
else {
|
|
12947
|
+
const relativePath = parseRelativeStoragePath(canonical.storagePath);
|
|
12948
|
+
if (!relativePath.ok) return {
|
|
12949
|
+
kind: "invalid-storage-path",
|
|
12950
|
+
storagePath: canonical.storagePath
|
|
12951
|
+
};
|
|
12952
|
+
if (!siteId) return {
|
|
12953
|
+
kind: "missing-site-context",
|
|
12954
|
+
storagePath: canonical.storagePath
|
|
12955
|
+
};
|
|
12956
|
+
directUrl = getDirectImageUrlForRelativePath(supabaseUrl, relativePath.value, siteId, bucket, transformOptions);
|
|
12957
|
+
}
|
|
12958
|
+
debugMediaNodeLog("resolve:direct-supabase", {
|
|
12959
|
+
storagePath: canonical.storagePath,
|
|
12960
|
+
siteId,
|
|
12961
|
+
bucket,
|
|
12962
|
+
directUrl,
|
|
12963
|
+
transformOptions
|
|
12964
|
+
});
|
|
12965
|
+
return {
|
|
12966
|
+
kind: "resolved",
|
|
12967
|
+
url: directUrl
|
|
12968
|
+
};
|
|
12969
|
+
}
|
|
12970
|
+
function getDirectImageUrlForValue(value, options, context) {
|
|
12971
|
+
const result = resolveMediaStorageUrlResult(value, context, buildDirectTransformOptions(options));
|
|
12972
|
+
return result.kind === "resolved" ? result.url : void 0;
|
|
12973
|
+
}
|
|
12974
|
+
function resolveStorageImageUrl(value, options, context, transformAspectRatio, resolved) {
|
|
12975
|
+
const proxyUrl = getProxyImageUrl(value, options, transformAspectRatio, resolved);
|
|
12976
|
+
if (proxyUrl) return proxyUrl;
|
|
12977
|
+
const directUrl = getDirectImageUrlForValue(value, options, context);
|
|
12978
|
+
if (directUrl) return directUrl;
|
|
12979
|
+
}
|
|
12980
|
+
/**
|
|
12981
|
+
* Common image size presets.
|
|
12982
|
+
* Use these for consistent sizing across the application.
|
|
12983
|
+
*/
|
|
12984
|
+
const ImagePresets = {
|
|
12985
|
+
/** Small thumbnails (200px) - for block previews, lists */
|
|
12986
|
+
thumbnail: {
|
|
12987
|
+
width: 200,
|
|
12988
|
+
quality: 80,
|
|
12989
|
+
resize: "contain"
|
|
12990
|
+
},
|
|
12991
|
+
/** Card-sized images (600px) - for cards, grids */
|
|
12992
|
+
card: {
|
|
12993
|
+
width: 600,
|
|
12994
|
+
quality: 80,
|
|
12995
|
+
resize: "contain"
|
|
12996
|
+
},
|
|
12997
|
+
/** Hero images (1200px) - for above-fold content */
|
|
12998
|
+
hero: {
|
|
12999
|
+
width: 1200,
|
|
13000
|
+
quality: 85,
|
|
13001
|
+
resize: "contain"
|
|
13002
|
+
},
|
|
13003
|
+
/** Full-width images (1920px) - for backgrounds, large displays */
|
|
13004
|
+
full: {
|
|
13005
|
+
width: 1920,
|
|
13006
|
+
quality: 85,
|
|
13007
|
+
resize: "contain"
|
|
13008
|
+
}
|
|
13009
|
+
};
|
|
13010
|
+
/**
|
|
13011
|
+
* Server-side: AsyncLocalStorage for request isolation.
|
|
13012
|
+
* Prevents race conditions when multiple requests are processed concurrently.
|
|
13013
|
+
*
|
|
13014
|
+
* Lazy initialization to avoid issues in environments that don't support async_hooks.
|
|
13015
|
+
*/
|
|
13016
|
+
let _serverStorage = null;
|
|
13017
|
+
let _serverStorageChecked = false;
|
|
13018
|
+
function getServerStorage() {
|
|
13019
|
+
if (typeof window !== "undefined") return null;
|
|
13020
|
+
if (_serverStorageChecked) return _serverStorage;
|
|
13021
|
+
_serverStorageChecked = true;
|
|
13022
|
+
try {
|
|
13023
|
+
const globalWithNonWebpackRequire = globalThis;
|
|
13024
|
+
const AsyncLocalStorage = (typeof globalWithNonWebpackRequire.__non_webpack_require__ === "function" ? globalWithNonWebpackRequire.__non_webpack_require__ : new Function("id", "return require(id)"))("async_hooks").AsyncLocalStorage;
|
|
13025
|
+
if (AsyncLocalStorage) {
|
|
13026
|
+
_serverStorage = new AsyncLocalStorage();
|
|
13027
|
+
return _serverStorage;
|
|
13028
|
+
}
|
|
13029
|
+
return null;
|
|
13030
|
+
} catch {
|
|
13031
|
+
console.warn("[blocks] AsyncLocalStorage not available - using module-level state");
|
|
13032
|
+
return null;
|
|
13033
|
+
}
|
|
13034
|
+
}
|
|
13035
|
+
/**
|
|
13036
|
+
* Client-side context storage.
|
|
13037
|
+
* Safe because browsers are single-threaded per tab.
|
|
13038
|
+
*
|
|
13039
|
+
* IMPORTANT: This must be shared across bundles.
|
|
13040
|
+
* SDK preview mode can load multiple independently-bundled entrypoints
|
|
13041
|
+
* (e.g. `@riverbankcms/sdk/rendering` + `@riverbankcms/sdk/preview-next/client`),
|
|
13042
|
+
* which can otherwise each get their own module-scoped singleton.
|
|
13043
|
+
*/
|
|
13044
|
+
const RB_MEDIA_CONTEXT_KEY = Symbol.for("riverbankcms.blocks.mediaContext");
|
|
13045
|
+
function getClientContextStore() {
|
|
13046
|
+
const g = globalThis;
|
|
13047
|
+
const existing = g[RB_MEDIA_CONTEXT_KEY];
|
|
13048
|
+
if (existing && typeof existing === "object") return existing;
|
|
13049
|
+
const next = {};
|
|
13050
|
+
g[RB_MEDIA_CONTEXT_KEY] = next;
|
|
13051
|
+
return next;
|
|
13052
|
+
}
|
|
13053
|
+
/**
|
|
13054
|
+
* Get the current media context.
|
|
13055
|
+
* Server-side: Uses AsyncLocalStorage for request isolation.
|
|
13056
|
+
* Client-side: Uses module-level state (safe in single-threaded browser).
|
|
13057
|
+
*/
|
|
13058
|
+
function getMediaContext() {
|
|
13059
|
+
const storage = getServerStorage();
|
|
13060
|
+
if (storage) return storage.getStore() ?? {};
|
|
13061
|
+
return getClientContextStore();
|
|
13062
|
+
}
|
|
13063
|
+
function getCurrentResolveImageUrlContext() {
|
|
13064
|
+
const context = getMediaContext();
|
|
13065
|
+
if (!context.supabaseUrl && !context.siteId && !context.mediaAssetIndex) return;
|
|
13066
|
+
return {
|
|
13067
|
+
supabaseUrl: context.supabaseUrl,
|
|
13068
|
+
siteId: context.siteId,
|
|
13069
|
+
mediaAssetIndex: context.mediaAssetIndex
|
|
13070
|
+
};
|
|
13071
|
+
}
|
|
13072
|
+
/**
|
|
13073
|
+
* Get the Supabase URL for constructing direct storage URLs.
|
|
13074
|
+
* Used for serving images directly from Supabase storage CDN.
|
|
13075
|
+
*
|
|
13076
|
+
* Resolution order:
|
|
13077
|
+
* 1. Context URL (set via runWithMediaContext or setContextSupabaseUrl)
|
|
13078
|
+
* 2. Environment variable NEXT_PUBLIC_SUPABASE_URL
|
|
13079
|
+
*
|
|
13080
|
+
* SDK sites receive the URL from the API, so they don't need the env var.
|
|
13081
|
+
* Dashboard and frontend apps use the env var.
|
|
13082
|
+
*/
|
|
13083
|
+
function getSupabaseUrl() {
|
|
13084
|
+
const context = getMediaContext();
|
|
13085
|
+
if (context.supabaseUrl) return context.supabaseUrl;
|
|
13086
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
13087
|
+
if (!url) throw new Error("NEXT_PUBLIC_SUPABASE_URL is not configured. Set it to your Supabase project URL (e.g., https://xxx.supabase.co)");
|
|
13088
|
+
return url.replace(/\/$/, "");
|
|
13089
|
+
}
|
|
13090
|
+
function resolveDashboardOrigin(raw) {
|
|
13091
|
+
if (!raw) return void 0;
|
|
13092
|
+
const trimmed = raw.trim();
|
|
13093
|
+
if (!trimmed) return void 0;
|
|
13094
|
+
const withoutApiSuffix = trimmed.replace(/\/+$/, "").replace(/\/api\/?$/, "");
|
|
13095
|
+
const normalized = /^https?:\/\//i.test(withoutApiSuffix) ? withoutApiSuffix : `http://${withoutApiSuffix.replace(/^\/+/, "")}`;
|
|
13096
|
+
try {
|
|
13097
|
+
return new URL(normalized).origin;
|
|
13098
|
+
} catch {
|
|
13099
|
+
return;
|
|
13100
|
+
}
|
|
13101
|
+
}
|
|
13102
|
+
function getImageProxyBasePath() {
|
|
13103
|
+
const dashboardOrigin = resolveDashboardOrigin(process.env.NEXT_PUBLIC_DASHBOARD_URL);
|
|
13104
|
+
if (!dashboardOrigin) return RELATIVE_IMG_PROXY_BASE_PATH;
|
|
13105
|
+
return `${dashboardOrigin}${RELATIVE_IMG_PROXY_BASE_PATH}`;
|
|
13106
|
+
}
|
|
13107
|
+
/**
|
|
13108
|
+
* Get the site ID for constructing storage paths.
|
|
13109
|
+
* Required for relative path resolution.
|
|
13110
|
+
*/
|
|
13111
|
+
function getSiteId() {
|
|
13112
|
+
return getMediaContext().siteId;
|
|
13113
|
+
}
|
|
13114
|
+
function getMediaAssetIndex(context) {
|
|
13115
|
+
return context?.mediaAssetIndex ?? getMediaContext().mediaAssetIndex;
|
|
13116
|
+
}
|
|
13117
|
+
function resolveCanonicalMediaAsset(value, context) {
|
|
13118
|
+
const index = getMediaAssetIndex(context);
|
|
13119
|
+
if (!index) return void 0;
|
|
13120
|
+
if (value.assetId) {
|
|
13121
|
+
const hit = index.byId.get(value.assetId);
|
|
13122
|
+
if (hit) return hit;
|
|
13123
|
+
}
|
|
13124
|
+
const identifier = typeof value.identifier === "string" ? value.identifier.trim() : "";
|
|
13125
|
+
if (identifier) {
|
|
13126
|
+
const hit = index.byIdentifier.get(identifier);
|
|
13127
|
+
if (hit) return hit;
|
|
13128
|
+
}
|
|
13129
|
+
}
|
|
13130
|
+
function resolveMediaWithCanonicalAsset(value, context) {
|
|
13131
|
+
const asset = resolveCanonicalMediaAsset(value, context);
|
|
13132
|
+
if (!asset) return value;
|
|
13133
|
+
return attachRenderableMediaAssetFields(value, asset);
|
|
13134
|
+
}
|
|
13135
|
+
const debugMediaNodeLog = (...args) => {
|
|
13136
|
+
if (typeof window === "undefined") return;
|
|
13137
|
+
if (Reflect.get(window, "__DEBUG_MEDIA_NODE__") === true) console.log("[MediaNode]", ...args);
|
|
13138
|
+
};
|
|
13139
|
+
function resolveTargetAspectRatio(size) {
|
|
13140
|
+
if ("original" in size) return void 0;
|
|
13141
|
+
if ("width" in size && "height" in size && typeof size.width === "number" && typeof size.height === "number") return size.width / size.height;
|
|
13142
|
+
}
|
|
13143
|
+
function resolveImageUrlWithContext(value, options, context, transformAspectRatio) {
|
|
13144
|
+
const result = resolveImageUrlWithContextResult(value, options, context, transformAspectRatio);
|
|
13145
|
+
warnForMediaUrlResolution(result);
|
|
13146
|
+
return result.kind === "resolved" ? result.url : void 0;
|
|
13147
|
+
}
|
|
13148
|
+
function resolveImageUrlWithContextResult(value, options, context, transformAspectRatio) {
|
|
13149
|
+
if (value.type !== "image") return {
|
|
13150
|
+
kind: "unsupported-media-type",
|
|
13151
|
+
mediaType: value.type
|
|
13152
|
+
};
|
|
13153
|
+
const url = resolveImageUrlWithContextInternal(value, options, context ?? void 0, transformAspectRatio);
|
|
13154
|
+
if (url) return {
|
|
13155
|
+
kind: "resolved",
|
|
13156
|
+
url
|
|
13157
|
+
};
|
|
13158
|
+
const canonical = resolveMediaWithCanonicalAsset(value, context ?? void 0);
|
|
13159
|
+
if (canonical.storagePath && canonical.storagePath.length > 0) return resolveMediaStorageUrlResult(canonical, context, buildDirectTransformOptions(options));
|
|
13160
|
+
return {
|
|
13161
|
+
kind: "missing-image-url",
|
|
13162
|
+
assetId: canonical.assetId,
|
|
13163
|
+
identifier: canonical.identifier
|
|
13164
|
+
};
|
|
13165
|
+
}
|
|
13166
|
+
function resolveImageUrlWithContextInternal(value, options, context, transformAspectRatio, resolved) {
|
|
13167
|
+
if (value.type !== "image") return void 0;
|
|
13168
|
+
const canonical = resolveMediaWithCanonicalAsset(value, context);
|
|
13169
|
+
debugMediaNodeLog("resolveImageUrl input", {
|
|
13170
|
+
assetId: canonical.assetId,
|
|
13171
|
+
storagePath: canonical.storagePath,
|
|
13172
|
+
src: canonical.src,
|
|
13173
|
+
type: canonical.type,
|
|
13174
|
+
options
|
|
13175
|
+
});
|
|
13176
|
+
if (canonical.storagePath && canonical.storagePath.length > 0) {
|
|
13177
|
+
const resolvedStorageUrl = resolveStorageImageUrl(canonical, options, context, transformAspectRatio, resolved);
|
|
13178
|
+
if (resolvedStorageUrl) return resolvedStorageUrl;
|
|
13179
|
+
}
|
|
13180
|
+
if (typeof canonical.src === "string" && canonical.src.length > 0) {
|
|
13181
|
+
debugMediaNodeLog("resolve:explicit-src", { src: canonical.src });
|
|
13182
|
+
return canonical.src;
|
|
13183
|
+
}
|
|
13184
|
+
const legacyUrl = typeof canonical === "object" && canonical !== null ? Reflect.get(canonical, "url") : void 0;
|
|
13185
|
+
debugMediaNodeLog("resolve:fallback-url", { fallbackUrl: typeof legacyUrl === "string" ? legacyUrl : null });
|
|
13186
|
+
return typeof legacyUrl === "string" ? legacyUrl : void 0;
|
|
13187
|
+
}
|
|
13188
|
+
//#endregion
|
|
13189
|
+
//#region ../blocks/src/primitives/content/components/RichText.tsx
|
|
13190
|
+
function parseDimension(value) {
|
|
13191
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.round(value);
|
|
13192
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
13193
|
+
const parsed = Number(value);
|
|
13194
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.round(parsed);
|
|
13195
|
+
}
|
|
13196
|
+
}
|
|
13197
|
+
function normalizeRel(target, rel) {
|
|
13198
|
+
const normalizedRel = rel?.trim();
|
|
13199
|
+
if (target !== "_blank") return normalizedRel && normalizedRel.length > 0 ? normalizedRel : void 0;
|
|
13200
|
+
const tokens = new Set((normalizedRel ?? "").split(/\s+/).map((token) => token.trim().toLowerCase()).filter(Boolean));
|
|
13201
|
+
tokens.add("noopener");
|
|
13202
|
+
tokens.add("noreferrer");
|
|
13203
|
+
return Array.from(tokens).join(" ");
|
|
13204
|
+
}
|
|
13205
|
+
function renderMarks(text, marks, key, linkTarget) {
|
|
13206
|
+
if (!marks || marks.length === 0) return text;
|
|
13207
|
+
return marks.reduce((acc, mark) => {
|
|
13208
|
+
switch (mark.type) {
|
|
13209
|
+
case "bold":
|
|
13210
|
+
case "strong": return /* @__PURE__ */ jsx("strong", { children: acc }, `${key}-b`);
|
|
13211
|
+
case "italic":
|
|
13212
|
+
case "em": return /* @__PURE__ */ jsx("em", { children: acc }, `${key}-i`);
|
|
13213
|
+
case "link": {
|
|
13214
|
+
if (typeof acc === "string" && acc.length === 0) return acc;
|
|
13215
|
+
const attrs = mark.attrs ?? {};
|
|
13216
|
+
const href = sanitizeHref(attrs.href);
|
|
13217
|
+
if (!href) return acc;
|
|
13218
|
+
const storedTarget = typeof attrs.target === "string" && attrs.target.trim().length > 0 ? attrs.target.trim() : void 0;
|
|
13219
|
+
const target = linkTarget === "new-tab" ? "_blank" : storedTarget;
|
|
13220
|
+
return /* @__PURE__ */ jsx("a", {
|
|
13221
|
+
href,
|
|
13222
|
+
target,
|
|
13223
|
+
rel: normalizeRel(target, typeof attrs.rel === "string" ? attrs.rel : void 0),
|
|
13224
|
+
children: acc
|
|
13225
|
+
}, `${key}-l`);
|
|
13226
|
+
}
|
|
13227
|
+
default: return acc;
|
|
13228
|
+
}
|
|
13229
|
+
}, text);
|
|
13230
|
+
}
|
|
13231
|
+
function renderDoc(node, key, imageResolveContext, linkTarget) {
|
|
13232
|
+
if (!node || !node.type) return null;
|
|
13233
|
+
const children = node.content?.map((child, index) => renderDoc(child, `${key}-${index}`, imageResolveContext, linkTarget));
|
|
13234
|
+
switch (node.type) {
|
|
13235
|
+
case "doc": return /* @__PURE__ */ jsx(Fragment, { children });
|
|
13236
|
+
case "paragraph":
|
|
13237
|
+
if (!children && !node.text) return /* @__PURE__ */ jsx("p", {}, key);
|
|
13238
|
+
return /* @__PURE__ */ jsx("p", { children: children ?? renderMarks(node.text ?? "", node.marks, key, linkTarget) }, key);
|
|
13239
|
+
case "heading": {
|
|
13240
|
+
const headingTag = `h${Math.min(Math.max(node.attrs?.level ?? 2, 1), 6)}`;
|
|
13241
|
+
return React.createElement(headingTag, { key }, children);
|
|
13242
|
+
}
|
|
13243
|
+
case "bulletList": return /* @__PURE__ */ jsx("ul", { children }, key);
|
|
13244
|
+
case "orderedList": return /* @__PURE__ */ jsx("ol", { children }, key);
|
|
13245
|
+
case "listItem": return /* @__PURE__ */ jsx("li", { children }, key);
|
|
13246
|
+
case "blockquote": return /* @__PURE__ */ jsx("blockquote", { children }, key);
|
|
13247
|
+
case "horizontalRule": return /* @__PURE__ */ jsx("hr", {}, key);
|
|
13248
|
+
case "hardBreak": return /* @__PURE__ */ jsx("br", {}, key);
|
|
13249
|
+
case "image": {
|
|
13250
|
+
const attrs = node.attrs ?? {};
|
|
13251
|
+
const src = typeof attrs.src === "string" ? attrs.src.trim() : "";
|
|
13252
|
+
const identifier = typeof attrs.identifier === "string" ? attrs.identifier.trim() : "";
|
|
13253
|
+
const alt = typeof attrs.alt === "string" ? attrs.alt : "";
|
|
13254
|
+
const title = typeof attrs.title === "string" && attrs.title.trim().length > 0 ? attrs.title : void 0;
|
|
13255
|
+
const width = parseDimension(attrs.width);
|
|
13256
|
+
const height = parseDimension(attrs.height);
|
|
13257
|
+
const resolvedSrc = src || (identifier ? resolveImageUrlWithContext({
|
|
13258
|
+
type: "image",
|
|
13259
|
+
identifier
|
|
13260
|
+
}, width ? {
|
|
13261
|
+
width,
|
|
13262
|
+
resize: "contain",
|
|
13263
|
+
quality: 85
|
|
13264
|
+
} : height ? {
|
|
13265
|
+
height,
|
|
13266
|
+
resize: "contain",
|
|
13267
|
+
quality: 85
|
|
13268
|
+
} : ImagePresets.full, imageResolveContext ?? void 0) ?? "" : "");
|
|
13269
|
+
if (!resolvedSrc) return null;
|
|
13270
|
+
return /* @__PURE__ */ jsx("img", {
|
|
13271
|
+
src: resolvedSrc,
|
|
13272
|
+
alt,
|
|
13273
|
+
title,
|
|
13274
|
+
width,
|
|
13275
|
+
height,
|
|
13276
|
+
loading: "lazy",
|
|
13277
|
+
decoding: "async"
|
|
13278
|
+
}, key);
|
|
13279
|
+
}
|
|
13280
|
+
case "text": return renderMarks(node.text ?? "", node.marks, key, linkTarget);
|
|
13281
|
+
default: return null;
|
|
13282
|
+
}
|
|
13283
|
+
}
|
|
13284
|
+
const DEFAULT_RICH_TEXT_CLASS = "rb-prose rb-max-w-none";
|
|
13285
|
+
function RichText({ as: Comp = "div", className, doc, path, linkTarget = "stored" }) {
|
|
13286
|
+
const imageResolveContext = getCurrentResolveImageUrlContext() ?? null;
|
|
13287
|
+
const content = doc ? renderDoc(doc, "doc", imageResolveContext, linkTarget) : null;
|
|
13288
|
+
const dataAttributes = path ? { "data-builder-richtext": path } : void 0;
|
|
13289
|
+
return /* @__PURE__ */ jsx(Comp, {
|
|
13290
|
+
className: className?.trim() || DEFAULT_RICH_TEXT_CLASS,
|
|
13291
|
+
...dataAttributes,
|
|
13292
|
+
children: content
|
|
13293
|
+
});
|
|
13294
|
+
}
|
|
13295
|
+
//#endregion
|
|
13296
|
+
//#region ../blocks/src/system/runtime/shared/components/BookingConsentAgreement.tsx
|
|
13297
|
+
function BookingConsentAgreement({ agreement, showTitle = true, title, titleId }) {
|
|
13298
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [showTitle ? /* @__PURE__ */ jsxs("div", {
|
|
13299
|
+
id: titleId,
|
|
13300
|
+
className: "form-label",
|
|
13301
|
+
children: [title, /* @__PURE__ */ jsx("span", {
|
|
13302
|
+
className: "required-marker",
|
|
13303
|
+
children: "*"
|
|
13304
|
+
})]
|
|
13305
|
+
}) : null, /* @__PURE__ */ jsx("div", {
|
|
13306
|
+
role: "group",
|
|
13307
|
+
"aria-label": showTitle ? void 0 : title,
|
|
13308
|
+
"aria-labelledby": showTitle ? titleId : void 0,
|
|
13309
|
+
children: /* @__PURE__ */ jsx(RichText, {
|
|
13310
|
+
doc: agreement,
|
|
13311
|
+
linkTarget: "new-tab",
|
|
13312
|
+
className: "rb-prose rb-max-w-none"
|
|
13313
|
+
})
|
|
13314
|
+
})] });
|
|
13315
|
+
}
|
|
13316
|
+
//#endregion
|
|
12419
13317
|
//#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
|
|
12420
13318
|
function optionCardClassName(isSelected) {
|
|
12421
13319
|
return `rb-option-card${isSelected ? " rb-option-card--selected" : ""}`;
|
|
@@ -12864,7 +13762,11 @@ function DefaultDynamicField({ booking, field }) {
|
|
|
12864
13762
|
return /* @__PURE__ */ jsxs("div", {
|
|
12865
13763
|
className: "rb-space-y-2",
|
|
12866
13764
|
children: [
|
|
12867
|
-
/* @__PURE__ */
|
|
13765
|
+
field.field.type === "booking_requirement_consent" ? /* @__PURE__ */ jsx(BookingConsentAgreement, {
|
|
13766
|
+
agreement: field.field.agreement,
|
|
13767
|
+
title: field.label,
|
|
13768
|
+
titleId: field.labelId
|
|
13769
|
+
}) : /* @__PURE__ */ jsxs("label", {
|
|
12868
13770
|
id: field.labelId,
|
|
12869
13771
|
htmlFor: field.inputId,
|
|
12870
13772
|
className: "form-label",
|
|
@@ -12909,7 +13811,7 @@ function DefaultFieldInput({ describedBy, error, errorId, field, onBlur, onChang
|
|
|
12909
13811
|
required: field.required,
|
|
12910
13812
|
...validationProps
|
|
12911
13813
|
};
|
|
12912
|
-
switch (field.type) {
|
|
13814
|
+
switch (field.field.type) {
|
|
12913
13815
|
case "textarea": return /* @__PURE__ */ jsx("textarea", {
|
|
12914
13816
|
...commonProps,
|
|
12915
13817
|
value: strValue,
|
|
@@ -13023,7 +13925,7 @@ function DefaultFieldInput({ describedBy, error, errorId, field, onBlur, onChang
|
|
|
13023
13925
|
className: "form-checkbox rb-mt-0.5"
|
|
13024
13926
|
}), /* @__PURE__ */ jsx("span", {
|
|
13025
13927
|
className: "rb-caption",
|
|
13026
|
-
children: field.field.
|
|
13928
|
+
children: field.field.checkboxLabel
|
|
13027
13929
|
})]
|
|
13028
13930
|
});
|
|
13029
13931
|
case "number": return /* @__PURE__ */ jsx("input", {
|
|
@@ -13092,10 +13994,10 @@ function DefaultBookingDiscount({ booking }) {
|
|
|
13092
13994
|
children: /* @__PURE__ */ jsxs("p", {
|
|
13093
13995
|
className: "ab-text",
|
|
13094
13996
|
children: [
|
|
13095
|
-
"Discount available:
|
|
13096
|
-
booking.discount.autoAppliedDiscount.publicLabel,
|
|
13997
|
+
"Discount available:",
|
|
13097
13998
|
" ",
|
|
13098
|
-
|
|
13999
|
+
booking.discount.autoAppliedDiscount.publicLabel,
|
|
14000
|
+
" will apply automatically at checkout."
|
|
13099
14001
|
]
|
|
13100
14002
|
})
|
|
13101
14003
|
}) : null]
|
|
@@ -13680,11 +14582,21 @@ function readAmount(value) {
|
|
|
13680
14582
|
}
|
|
13681
14583
|
//#endregion
|
|
13682
14584
|
//#region ../blocks/src/system/runtime/nodes/event-registration/validation.ts
|
|
13683
|
-
function
|
|
14585
|
+
function createInitialRegistrationFormData() {
|
|
14586
|
+
return {
|
|
14587
|
+
name: "",
|
|
14588
|
+
email: "",
|
|
14589
|
+
phone: "",
|
|
14590
|
+
notes: "",
|
|
14591
|
+
consentAccepted: false
|
|
14592
|
+
};
|
|
14593
|
+
}
|
|
14594
|
+
function validateFormData(formData, requiresConsent = false) {
|
|
13684
14595
|
const errors = {};
|
|
13685
14596
|
if (!formData.name.trim()) errors.name = "Name is required";
|
|
13686
14597
|
if (!formData.email.trim()) errors.email = "Email is required";
|
|
13687
14598
|
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) errors.email = "Invalid email address";
|
|
14599
|
+
if (requiresConsent && formData.consentAccepted !== true) errors.consentAccepted = "Please read and accept the agreement before continuing.";
|
|
13688
14600
|
return errors;
|
|
13689
14601
|
}
|
|
13690
14602
|
/**
|
|
@@ -14608,6 +15520,7 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
|
|
|
14608
15520
|
name: options.formData.name.trim(),
|
|
14609
15521
|
phone: options.formData.phone.trim() || void 0,
|
|
14610
15522
|
notes: options.formData.notes.trim() || void 0,
|
|
15523
|
+
consentAccepted: options.formData.consentAccepted,
|
|
14611
15524
|
altchaSolution: options.altchaSolution,
|
|
14612
15525
|
paymentMethod: method,
|
|
14613
15526
|
customerPassId: method === "pass" ? passId : void 0,
|
|
@@ -14825,75 +15738,6 @@ function requiresPaymentStep(params) {
|
|
|
14825
15738
|
return !isEntitlementSelection(params.paymentSelection);
|
|
14826
15739
|
}
|
|
14827
15740
|
//#endregion
|
|
14828
|
-
//#region ../blocks/src/system/manifest/richTextSchema.ts
|
|
14829
|
-
/**
|
|
14830
|
-
* Shared Rich Text Schema
|
|
14831
|
-
*
|
|
14832
|
-
* This module provides a single source of truth for TipTap rich text validation
|
|
14833
|
-
* across both the block-form editor and the block rendering system.
|
|
14834
|
-
*
|
|
14835
|
-
* Rich-text fields use one canonical root shape:
|
|
14836
|
-
* `{ type: 'doc', content: [...] }`.
|
|
14837
|
-
*/
|
|
14838
|
-
/**
|
|
14839
|
-
* TipTap document node schema (recursive structure)
|
|
14840
|
-
*
|
|
14841
|
-
* Supports the full TipTap document structure including:
|
|
14842
|
-
* - type: Node type (e.g., 'doc', 'paragraph', 'text')
|
|
14843
|
-
* - content: Nested array of child nodes
|
|
14844
|
-
* - text: Text content for text nodes
|
|
14845
|
-
* - attrs: Node attributes (e.g., alignment, color)
|
|
14846
|
-
* - marks: Text formatting marks (e.g., bold, italic)
|
|
14847
|
-
*/
|
|
14848
|
-
const TipTapMarkSchema = z.object({
|
|
14849
|
-
type: z.string().min(1),
|
|
14850
|
-
attrs: z.record(z.string(), z.unknown()).optional()
|
|
14851
|
-
}).strict();
|
|
14852
|
-
const TipTapNodeSchema = z.lazy(() => z.object({
|
|
14853
|
-
type: z.string().min(1),
|
|
14854
|
-
content: z.array(TipTapNodeSchema).optional(),
|
|
14855
|
-
text: z.string().optional(),
|
|
14856
|
-
attrs: z.record(z.string(), z.unknown()).optional(),
|
|
14857
|
-
marks: z.array(TipTapMarkSchema).optional()
|
|
14858
|
-
}).strict().superRefine((node, ctx) => {
|
|
14859
|
-
if (node.type === "doc") ctx.addIssue({
|
|
14860
|
-
code: "custom",
|
|
14861
|
-
path: ["type"],
|
|
14862
|
-
message: "Document nodes are only valid at the rich-text root"
|
|
14863
|
-
});
|
|
14864
|
-
if (node.type === "text") {
|
|
14865
|
-
if (node.text === void 0 || node.text.length === 0) ctx.addIssue({
|
|
14866
|
-
code: "custom",
|
|
14867
|
-
path: ["text"],
|
|
14868
|
-
message: "Text nodes require a non-empty text value"
|
|
14869
|
-
});
|
|
14870
|
-
if (node.content !== void 0) ctx.addIssue({
|
|
14871
|
-
code: "custom",
|
|
14872
|
-
path: ["content"],
|
|
14873
|
-
message: "Text nodes cannot contain child nodes"
|
|
14874
|
-
});
|
|
14875
|
-
return;
|
|
14876
|
-
}
|
|
14877
|
-
if (node.text !== void 0) ctx.addIssue({
|
|
14878
|
-
code: "custom",
|
|
14879
|
-
path: ["text"],
|
|
14880
|
-
message: "Only text nodes can contain a text value"
|
|
14881
|
-
});
|
|
14882
|
-
}));
|
|
14883
|
-
/**
|
|
14884
|
-
* Canonical schema for a TipTap rich-text document root.
|
|
14885
|
-
*
|
|
14886
|
-
* Stricter than {@link TipTapNodeSchema}: `type` is literally `'doc'` and
|
|
14887
|
-
* `content` is required (possibly empty). This is the shape every producer
|
|
14888
|
-
* (dummy content, editor output, migrations) must emit, and the shape every
|
|
14889
|
-
* non-legacy consumer can rely on.
|
|
14890
|
-
*
|
|
14891
|
-
*/
|
|
14892
|
-
const RichTextDocSchema = z.object({
|
|
14893
|
-
type: z.literal("doc"),
|
|
14894
|
-
content: z.array(TipTapNodeSchema)
|
|
14895
|
-
}).strict();
|
|
14896
|
-
//#endregion
|
|
14897
15741
|
//#region ../blocks/src/system/blocks/events/whole-series/pricing.ts
|
|
14898
15742
|
function isSaleWindowOpen(option, now) {
|
|
14899
15743
|
if (!option.isActive) return false;
|
|
@@ -15116,6 +15960,7 @@ const publicWholeSeriesOfferingSchema = z.object({
|
|
|
15116
15960
|
pricing: wholeSeriesPricingSchema,
|
|
15117
15961
|
currency: z.string(),
|
|
15118
15962
|
paymentTerms: blockBookingPaymentTermsSchema,
|
|
15963
|
+
consent: blockBookingConsentSchema.nullable(),
|
|
15119
15964
|
enrollmentCapacity: z.number().nullable(),
|
|
15120
15965
|
acceptsMemberships: z.boolean(),
|
|
15121
15966
|
status: z.enum([
|
|
@@ -15433,6 +16278,7 @@ const publicEventSchema = z.object({
|
|
|
15433
16278
|
timeZone: z.string(),
|
|
15434
16279
|
pricing: publicEventPricingSchema,
|
|
15435
16280
|
paymentTerms: blockBookingPaymentTermsSchema,
|
|
16281
|
+
consent: blockBookingConsentSchema.nullable(),
|
|
15436
16282
|
accessRestriction: accessRestrictionSchema,
|
|
15437
16283
|
allowedCategoryIds: z.array(z.string()),
|
|
15438
16284
|
requireGuestDetails: z.boolean().optional(),
|
|
@@ -15951,12 +16797,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
|
|
|
15951
16797
|
const step = flowState.step;
|
|
15952
16798
|
const paymentSelection = flowState.paymentSelection;
|
|
15953
16799
|
const [selectedEvent, setSelectedEvent] = useState(preSelectedEvent ?? null);
|
|
15954
|
-
const [formData, setFormData] = useState(
|
|
15955
|
-
name: "",
|
|
15956
|
-
email: "",
|
|
15957
|
-
phone: "",
|
|
15958
|
-
notes: ""
|
|
15959
|
-
});
|
|
16800
|
+
const [formData, setFormData] = useState(createInitialRegistrationFormData);
|
|
15960
16801
|
const [errors, setErrors] = useState({});
|
|
15961
16802
|
const [guests, setGuests] = useState([]);
|
|
15962
16803
|
const [guestErrors, setGuestErrors] = useState({});
|
|
@@ -16254,7 +17095,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
|
|
|
16254
17095
|
guests
|
|
16255
17096
|
]);
|
|
16256
17097
|
const validateForm = () => {
|
|
16257
|
-
const newErrors = validateFormData(formData);
|
|
17098
|
+
const newErrors = validateFormData(formData, Boolean(currentEvent?.consent));
|
|
16258
17099
|
setErrors(newErrors);
|
|
16259
17100
|
const newGuestErrors = {};
|
|
16260
17101
|
if (requireGuestDetails && guests.length > 0) {
|
|
@@ -16443,6 +17284,10 @@ function useEventRegistrationWizardWithDeps(options, deps) {
|
|
|
16443
17284
|
});
|
|
16444
17285
|
const handleEventSelect = (event) => {
|
|
16445
17286
|
setSelectedEvent(event);
|
|
17287
|
+
setFormData((current) => ({
|
|
17288
|
+
...current,
|
|
17289
|
+
consentAccepted: false
|
|
17290
|
+
}));
|
|
16446
17291
|
const slidingScale = getSlidingScaleForEvent(event);
|
|
16447
17292
|
setSelectedSlidingAmountInput(slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput$1(slidingScale.suggestedAmount) : "");
|
|
16448
17293
|
setSelectedAttendanceMode((event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person");
|
|
@@ -17328,6 +18173,7 @@ function validateSubmission(state, setters) {
|
|
|
17328
18173
|
message: "Choose an available set of dates to continue."
|
|
17329
18174
|
};
|
|
17330
18175
|
const errors = validateFormData$1(state.formData);
|
|
18176
|
+
if (state.selectedRun.consent && state.formData.consentAccepted !== true) errors.consentAccepted = "Please read and accept the agreement before continuing.";
|
|
17331
18177
|
setters.setErrors(errors);
|
|
17332
18178
|
if (Object.keys(errors).length > 0) return { kind: "validation-failed" };
|
|
17333
18179
|
const pricingMessage = getMissingWholeSeriesPricingSelectionMessage(state.pricing);
|
|
@@ -17351,6 +18197,7 @@ function registrantPayload(state) {
|
|
|
17351
18197
|
name: state.formData.name.trim(),
|
|
17352
18198
|
phone: state.formData.phone.trim() || void 0,
|
|
17353
18199
|
formData: { notes: state.formData.notes.trim() || void 0 },
|
|
18200
|
+
consentAccepted: state.formData.consentAccepted,
|
|
17354
18201
|
altchaSolution: state.altchaSolution,
|
|
17355
18202
|
claimToken: state.claimToken ?? void 0
|
|
17356
18203
|
};
|
|
@@ -17474,7 +18321,8 @@ const EMPTY_FORM = {
|
|
|
17474
18321
|
name: "",
|
|
17475
18322
|
email: "",
|
|
17476
18323
|
phone: "",
|
|
17477
|
-
notes: ""
|
|
18324
|
+
notes: "",
|
|
18325
|
+
consentAccepted: false
|
|
17478
18326
|
};
|
|
17479
18327
|
function useWholeSeriesRegistrationPortal(api) {
|
|
17480
18328
|
const [formData, setFormData] = useState(EMPTY_FORM);
|
|
@@ -18080,6 +18928,7 @@ function toPublicWholeSeriesOffering(run) {
|
|
|
18080
18928
|
slug: run.slug,
|
|
18081
18929
|
description: run.description,
|
|
18082
18930
|
teamMembers: run.teamMembers ? [...run.teamMembers] : void 0,
|
|
18931
|
+
consent: run.consent,
|
|
18083
18932
|
enrollmentOpen: run.registrationOpen,
|
|
18084
18933
|
pricing,
|
|
18085
18934
|
currency: run.pricing.currency,
|