@riverbankcms/sdk 0.74.0 → 0.75.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-5XL7M3OM.mjs → PreviewEditorSidebar-U2IHCBYN.mjs} +3 -3
- package/dist/{PreviewEditorUI-ZDFRDJID.mjs → PreviewEditorUI-ZGH2RQNP.mjs} +3 -3
- package/dist/SdkPreviewModeRuntime-MTUGBTLQ.mjs +8 -0
- package/dist/_dts/api/src/cache-tags.d.ts +22 -0
- package/dist/_dts/api/src/contentRuntime.d.ts +1 -0
- package/dist/_dts/api/src/endpoints.d.ts +24 -24
- package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +2 -1
- package/dist/_dts/block-form/src/blockDraftSaveability.d.ts +4 -1
- package/dist/_dts/blocks/src/client/booking-form.d.ts +2 -0
- package/dist/_dts/blocks/src/client/carousel.d.ts +1 -0
- package/dist/_dts/blocks/src/client/course-registration.d.ts +2 -0
- package/dist/_dts/blocks/src/client/events.d.ts +4 -0
- package/dist/_dts/blocks/src/client/form.d.ts +2 -0
- package/dist/_dts/blocks/src/client/header.d.ts +1 -0
- package/dist/_dts/blocks/src/client/location-map.d.ts +1 -0
- package/dist/_dts/blocks/src/client/newsletter-form.d.ts +2 -0
- package/dist/_dts/blocks/src/client/shop.d.ts +6 -0
- package/dist/_dts/blocks/src/lib/video.d.ts +2 -0
- package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +21 -1
- package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +1 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +3 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/events/index.d.ts +3 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/header-section.client.d.ts +1 -0
- package/dist/_dts/blocks/src/system/runtime/nodes/header-section.server.d.ts +1 -0
- package/dist/_dts/content-editor/src/hooks/validateBlocksForPublish.d.ts +5 -0
- package/dist/_dts/preview-next/src/client/preview/SiteChromeEditableSurface.d.ts +12 -2
- package/dist/_dts/sdk/src/public-api/contracts.d.ts +1 -0
- package/dist/_dts/sdk/src/rendering/islands/deferredVideoEmbeds.d.ts +2 -0
- package/dist/_dts/sdk/src/rendering/server.d.ts +3 -0
- package/dist/_dts/sdk/src/version.d.ts +1 -1
- package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
- package/dist/{chunk-YY7K3QD2.mjs → chunk-BGMNDF6T.mjs} +7 -7
- package/dist/{chunk-2MIWNFJM.mjs → chunk-GFE74WRD.mjs} +309 -84
- package/dist/{chunk-CEQBBLMJ.mjs → chunk-RWXQBRQZ.mjs} +1 -1
- package/dist/{chunk-7RX72SK2.mjs → chunk-VX7B5VAC.mjs} +36 -25
- package/dist/{chunk-YRJKZEQ3.mjs → chunk-Z7JLNTJR.mjs} +1 -1
- package/dist/{chunk-CBD47MTM.mjs → chunk-ZSHFGFUG.mjs} +2 -2
- package/dist/cli/index.mjs +3395 -3753
- package/dist/client/{accordion-ZZVXWV3N.mjs → accordion-T2UD2ZTH.mjs} +1 -1
- package/dist/client/bookingForm-GUP75FKN.mjs +3022 -0
- package/dist/client/bookings.mjs +19 -10
- package/dist/client/{carousel-25WK2H7G.mjs → carousel-2NQA6V57.mjs} +3 -2
- package/dist/client/cart-5TFIAFDS.mjs +18 -0
- package/dist/client/checkout-TP4WAHZE.mjs +18 -0
- package/dist/client/chunk-2PHQZ666.mjs +383 -0
- package/dist/client/chunk-3DCJ45Z2.mjs +36 -0
- package/dist/client/chunk-3PEDD5KJ.mjs +146 -0
- package/dist/client/chunk-CY2ZTSE5.mjs +204 -0
- package/dist/client/chunk-F7LHOKEH.mjs +146 -0
- package/dist/client/{chunk-6VGXRE34.mjs → chunk-FU2QRZMX.mjs} +6 -6
- package/dist/client/chunk-I2MMZO53.mjs +6 -0
- package/dist/client/chunk-II7O4XN3.mjs +2206 -0
- package/dist/client/chunk-M3L3KYYR.mjs +1181 -0
- package/dist/client/chunk-NWOUL6JY.mjs +1378 -0
- package/dist/client/chunk-OJPZEZU2.mjs +31 -0
- package/dist/client/chunk-RTZ3JKKX.mjs +178 -0
- package/dist/client/chunk-TQ5MSPDX.mjs +74 -0
- package/dist/client/chunk-VUAOE3AG.mjs +5592 -0
- package/dist/client/chunk-WJXXNDSE.mjs +245 -0
- package/dist/client/chunk-YLMKMCSJ.mjs +86 -0
- package/dist/client/chunk-ZLMS67RR.mjs +106 -0
- package/dist/client/client.mjs +51626 -52074
- package/dist/client/courseRegistration-5DPU6PDY.mjs +1737 -0
- package/dist/client/{eventCalendar-X6GIPYFM.mjs → eventCalendar-F3GZGTEF.mjs} +13 -2
- package/dist/client/{eventCombined-EZUOPM3G.mjs → eventCombined-BQOB6QMY.mjs} +13 -2
- package/dist/client/eventDetails-PEM2EJWE.mjs +25 -0
- package/dist/client/{eventListing-HEFGAETC.mjs → eventListing-YMIQPVME.mjs} +13 -2
- package/dist/client/{eventRegistration-3LCP2V3P.mjs → eventRegistration-4I6ZNDUF.mjs} +13 -2
- package/dist/client/form-ZJVAYZ6G.mjs +300 -0
- package/dist/client/headerSection-TOT5MGTF.mjs +729 -0
- package/dist/client/hooks.mjs +49 -33
- package/dist/client/locationMap-J66NZREU.mjs +227 -0
- package/dist/client/newsletterForm-ET2XUMRO.mjs +208 -0
- package/dist/client/productDetail-C2XIH7C5.mjs +18 -0
- package/dist/client/productList-T2AZLFTP.mjs +18 -0
- package/dist/client/rendering/client.mjs +11763 -12445
- package/dist/client/rendering/islands.mjs +98 -21
- package/dist/client/rendering.mjs +46778 -47229
- package/dist/client/shop-UZ6IUZXD.mjs +20 -0
- package/dist/preview-next/before-render.mjs +37 -25
- package/dist/preview-next/client/runtime.mjs +5 -5
- package/dist/preview-next/middleware.mjs +37 -25
- package/dist/{sdk-runtime-OHNONZ5U.mjs → sdk-runtime-JCUQM63Y.mjs} +6 -6
- package/dist/server/components.mjs +409 -266
- package/dist/server/config-validation.mjs +49 -33
- package/dist/server/config.mjs +49 -33
- package/dist/server/data.mjs +49 -33
- package/dist/server/index.mjs +45 -33
- package/dist/server/next.mjs +405 -262
- package/dist/server/page-converter.mjs +12 -8
- package/dist/server/prebuild.mjs +1 -1
- package/dist/server/rendering/server.mjs +493 -265
- package/dist/server/rendering.mjs +411 -268
- package/dist/server/routing.mjs +49 -33
- package/dist/server/server.mjs +50 -34
- package/dist/server/theme-bridge.mjs +5 -0
- package/dist/styles/index.css +5 -0
- package/package.json +2 -1
- package/dist/SdkPreviewModeRuntime-CRYNDSPL.mjs +0 -8
- package/dist/client/bookingForm-4X77HKDS.mjs +0 -34
- package/dist/client/cart-JWD2VRFU.mjs +0 -11
- package/dist/client/checkout-V7KYGWL7.mjs +0 -11
- package/dist/client/chunk-5IYGMTUE.mjs +0 -18614
- package/dist/client/chunk-RKCFVIMS.mjs +0 -26
- package/dist/client/courseRegistration-O6ZPK4YT.mjs +0 -29
- package/dist/client/eventDetails-3WODIXET.mjs +0 -14
- package/dist/client/form-AKXQMY6Z.mjs +0 -85
- package/dist/client/headerSection-5KHCMU6B.mjs +0 -12
- package/dist/client/locationMap-7HIADW76.mjs +0 -14
- package/dist/client/newsletterForm-5N4YTMU3.mjs +0 -60
- package/dist/client/productDetail-IZUA7ZP3.mjs +0 -11
- package/dist/client/productList-KWIIJE6I.mjs +0 -11
- package/dist/client/shop-XZQFRQWZ.mjs +0 -13
|
@@ -0,0 +1,1378 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { addPassToCommerceCart, useCommerceCart } from './chunk-ZLMS67RR.mjs';
|
|
3
|
+
import { CarouselNodeClient } from './chunk-II7O4XN3.mjs';
|
|
4
|
+
import { isRecord } from './chunk-I2MMZO53.mjs';
|
|
5
|
+
import { createBodyThemePortalRoot, consumeSearchParams, scrollToBlock, TerminalPanel, StateIcon, CheckIcon, CrossIcon, ActionRow, consumeSearchParam, buildCheckoutReturnUrls } from './chunk-2PHQZ666.mjs';
|
|
6
|
+
import { ModalShell, resolveDedicatedCheckoutPath } from './chunk-3PEDD5KJ.mjs';
|
|
7
|
+
import { useBlockApi, FeedbackMessage, BlockApiProvider } from './chunk-M3L3KYYR.mjs';
|
|
8
|
+
import { mountReactIsland } from './chunk-N7PBWBWQ.mjs';
|
|
9
|
+
import { themeButtonClassName, formatCommercePrice, formatInterval, getShopDisplayForItems, buildProductDetailDisplay, productItemKey, buildProductCartItem, buildProductCardDisplay, assertNever } from './chunk-FU2QRZMX.mjs';
|
|
10
|
+
import { getCurrentResolveImageUrlContext, resolveImageUrlWithContext, ImagePresets } from './chunk-PMAIXMAB.mjs';
|
|
11
|
+
import * as React3 from 'react';
|
|
12
|
+
import React3__default, { useState, useRef, useCallback, useEffect } from 'react';
|
|
13
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
|
|
16
|
+
// ../blocks/src/primitives/content/utils/sanitizeHref.ts
|
|
17
|
+
var SAFE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
|
|
18
|
+
var CONTROL_CHARS_PATTERN = /[\u0000-\u001F\u007F]+/g;
|
|
19
|
+
function normalizeInput(value) {
|
|
20
|
+
return value.replace(CONTROL_CHARS_PATTERN, "").trim();
|
|
21
|
+
}
|
|
22
|
+
function decodeForSchemeCheck(value) {
|
|
23
|
+
try {
|
|
24
|
+
return decodeURIComponent(value);
|
|
25
|
+
} catch {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function sanitizeHref(raw) {
|
|
30
|
+
if (typeof raw !== "string") return null;
|
|
31
|
+
const normalized = normalizeInput(raw);
|
|
32
|
+
if (!normalized) return null;
|
|
33
|
+
if (normalized.startsWith("//")) return null;
|
|
34
|
+
if (normalized.startsWith("/")) return normalized;
|
|
35
|
+
if (normalized.startsWith("#")) return normalized;
|
|
36
|
+
const decoded = normalizeInput(decodeForSchemeCheck(normalized));
|
|
37
|
+
const schemeMatch = decoded.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
38
|
+
if (!schemeMatch) return null;
|
|
39
|
+
const rawScheme = schemeMatch[1];
|
|
40
|
+
if (!rawScheme) return null;
|
|
41
|
+
const protocol = `${rawScheme.toLowerCase()}:`;
|
|
42
|
+
if (!SAFE_PROTOCOLS.has(protocol)) return null;
|
|
43
|
+
try {
|
|
44
|
+
const parsed = new URL(normalized);
|
|
45
|
+
if (!SAFE_PROTOCOLS.has(parsed.protocol.toLowerCase())) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
function parseDimension(value) {
|
|
54
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
55
|
+
return Math.round(value);
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
58
|
+
const parsed = Number(value);
|
|
59
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
60
|
+
return Math.round(parsed);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
function normalizeRel(target, rel) {
|
|
66
|
+
const normalizedRel = rel?.trim();
|
|
67
|
+
if (target !== "_blank") {
|
|
68
|
+
return normalizedRel && normalizedRel.length > 0 ? normalizedRel : void 0;
|
|
69
|
+
}
|
|
70
|
+
const tokens = new Set(
|
|
71
|
+
(normalizedRel ?? "").split(/\s+/).map((token) => token.trim().toLowerCase()).filter(Boolean)
|
|
72
|
+
);
|
|
73
|
+
tokens.add("noopener");
|
|
74
|
+
tokens.add("noreferrer");
|
|
75
|
+
return Array.from(tokens).join(" ");
|
|
76
|
+
}
|
|
77
|
+
function renderMarks(text, marks, key) {
|
|
78
|
+
if (!marks || marks.length === 0) return text;
|
|
79
|
+
return marks.reduce((acc, mark) => {
|
|
80
|
+
switch (mark.type) {
|
|
81
|
+
case "bold":
|
|
82
|
+
case "strong":
|
|
83
|
+
return /* @__PURE__ */ jsx("strong", { children: acc }, `${key}-b`);
|
|
84
|
+
case "italic":
|
|
85
|
+
case "em":
|
|
86
|
+
return /* @__PURE__ */ jsx("em", { children: acc }, `${key}-i`);
|
|
87
|
+
case "link": {
|
|
88
|
+
if (typeof acc === "string" && acc.length === 0) return acc;
|
|
89
|
+
const attrs = mark.attrs ?? {};
|
|
90
|
+
const href = sanitizeHref(attrs.href);
|
|
91
|
+
if (!href) return acc;
|
|
92
|
+
const target = typeof attrs.target === "string" && attrs.target.trim().length > 0 ? attrs.target.trim() : void 0;
|
|
93
|
+
const rel = typeof attrs.rel === "string" ? attrs.rel : void 0;
|
|
94
|
+
return /* @__PURE__ */ jsx("a", { href, target, rel: normalizeRel(target, rel), children: acc }, `${key}-l`);
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
return acc;
|
|
98
|
+
}
|
|
99
|
+
}, text);
|
|
100
|
+
}
|
|
101
|
+
function renderDoc(node, key, imageResolveContext) {
|
|
102
|
+
if (!node || !node.type) return null;
|
|
103
|
+
const children = node.content?.map((child, index) => renderDoc(child, `${key}-${index}`, imageResolveContext));
|
|
104
|
+
switch (node.type) {
|
|
105
|
+
case "doc":
|
|
106
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
107
|
+
case "paragraph":
|
|
108
|
+
if (!children && !node.text) return /* @__PURE__ */ jsx("p", {}, key);
|
|
109
|
+
return /* @__PURE__ */ jsx("p", { children: children ?? renderMarks(node.text ?? "", node.marks, key) }, key);
|
|
110
|
+
case "heading": {
|
|
111
|
+
const level = Math.min(Math.max(node.attrs?.level ?? 2, 1), 6);
|
|
112
|
+
const headingTag = `h${level}`;
|
|
113
|
+
return React3__default.createElement(headingTag, { key }, children);
|
|
114
|
+
}
|
|
115
|
+
case "bulletList":
|
|
116
|
+
return /* @__PURE__ */ jsx("ul", { children }, key);
|
|
117
|
+
case "orderedList":
|
|
118
|
+
return /* @__PURE__ */ jsx("ol", { children }, key);
|
|
119
|
+
case "listItem":
|
|
120
|
+
return /* @__PURE__ */ jsx("li", { children }, key);
|
|
121
|
+
case "blockquote":
|
|
122
|
+
return /* @__PURE__ */ jsx("blockquote", { children }, key);
|
|
123
|
+
case "horizontalRule":
|
|
124
|
+
return /* @__PURE__ */ jsx("hr", {}, key);
|
|
125
|
+
case "image": {
|
|
126
|
+
const attrs = node.attrs ?? {};
|
|
127
|
+
const src = typeof attrs.src === "string" ? attrs.src.trim() : "";
|
|
128
|
+
const identifier = typeof attrs.identifier === "string" ? attrs.identifier.trim() : "";
|
|
129
|
+
const alt = typeof attrs.alt === "string" ? attrs.alt : "";
|
|
130
|
+
const title = typeof attrs.title === "string" && attrs.title.trim().length > 0 ? attrs.title : void 0;
|
|
131
|
+
const width = parseDimension(attrs.width);
|
|
132
|
+
const height = parseDimension(attrs.height);
|
|
133
|
+
const resolvedSrc = src || (identifier ? resolveImageUrlWithContext(
|
|
134
|
+
{ type: "image", identifier },
|
|
135
|
+
width ? { width, resize: "contain", quality: 85 } : height ? { height, resize: "contain", quality: 85 } : ImagePresets.full,
|
|
136
|
+
imageResolveContext ?? void 0
|
|
137
|
+
) ?? "" : "");
|
|
138
|
+
if (!resolvedSrc) return null;
|
|
139
|
+
return /* @__PURE__ */ jsx(
|
|
140
|
+
"img",
|
|
141
|
+
{
|
|
142
|
+
src: resolvedSrc,
|
|
143
|
+
alt,
|
|
144
|
+
title,
|
|
145
|
+
width,
|
|
146
|
+
height,
|
|
147
|
+
loading: "lazy",
|
|
148
|
+
decoding: "async"
|
|
149
|
+
},
|
|
150
|
+
key
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
case "text":
|
|
154
|
+
return renderMarks(node.text ?? "", node.marks, key);
|
|
155
|
+
default:
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
var DEFAULT_RICH_TEXT_CLASS = "rb-prose rb-max-w-none";
|
|
160
|
+
function RichText({ as: Comp = "div", className, doc, path }) {
|
|
161
|
+
const imageResolveContext = getCurrentResolveImageUrlContext() ?? null;
|
|
162
|
+
const content = doc ? renderDoc(doc, "doc", imageResolveContext) : null;
|
|
163
|
+
const dataAttributes = path ? { "data-builder-richtext": path } : void 0;
|
|
164
|
+
const resolvedClassName = className?.trim() || DEFAULT_RICH_TEXT_CLASS;
|
|
165
|
+
return /* @__PURE__ */ jsx(Comp, { className: resolvedClassName, ...dataAttributes, children: content });
|
|
166
|
+
}
|
|
167
|
+
var TipTapMarkSchema = z.object({
|
|
168
|
+
type: z.string(),
|
|
169
|
+
attrs: z.record(z.string(), z.unknown()).optional()
|
|
170
|
+
});
|
|
171
|
+
var TipTapNodeSchema = z.lazy(
|
|
172
|
+
() => z.object({
|
|
173
|
+
type: z.string(),
|
|
174
|
+
content: z.array(TipTapNodeSchema).optional(),
|
|
175
|
+
text: z.string().optional(),
|
|
176
|
+
attrs: z.record(z.string(), z.unknown()).optional(),
|
|
177
|
+
marks: z.array(TipTapMarkSchema).optional()
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
z.union([
|
|
181
|
+
// Canonical: TipTap document directly
|
|
182
|
+
TipTapNodeSchema,
|
|
183
|
+
// Legacy (read-tolerance only): wrapped in { doc: {...} }
|
|
184
|
+
z.object({
|
|
185
|
+
doc: TipTapNodeSchema
|
|
186
|
+
})
|
|
187
|
+
]);
|
|
188
|
+
z.object({
|
|
189
|
+
type: z.literal("doc"),
|
|
190
|
+
content: z.array(TipTapNodeSchema)
|
|
191
|
+
});
|
|
192
|
+
function unwrapRichTextRoot(value) {
|
|
193
|
+
if (!isRecord(value)) return value;
|
|
194
|
+
if (!("doc" in value)) return value;
|
|
195
|
+
const inner = value.doc;
|
|
196
|
+
if (isRecord(inner) && inner.type === "doc") return inner;
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ../blocks/src/system/runtime/nodes/richText-coerce.ts
|
|
201
|
+
function parseInlineMarkdown(text) {
|
|
202
|
+
const out = [];
|
|
203
|
+
const pushText = (value, marks) => {
|
|
204
|
+
if (!value) return;
|
|
205
|
+
out.push(
|
|
206
|
+
marks && marks.length > 0 ? { type: "text", text: value, marks } : { type: "text", text: value }
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
let rest = text;
|
|
210
|
+
while (rest.length > 0) {
|
|
211
|
+
const start = rest.indexOf("**");
|
|
212
|
+
if (start === -1) {
|
|
213
|
+
pushText(rest);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
const end = rest.indexOf("**", start + 2);
|
|
217
|
+
if (end === -1) {
|
|
218
|
+
pushText(rest);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
pushText(rest.slice(0, start));
|
|
222
|
+
pushText(rest.slice(start + 2, end), [{ type: "bold" }]);
|
|
223
|
+
rest = rest.slice(end + 2);
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
function markdownToTipTapDoc(markdown) {
|
|
228
|
+
const normalized = markdown.replace(/\r\n/g, "\n").trim();
|
|
229
|
+
if (!normalized) return { type: "doc", content: [] };
|
|
230
|
+
const paragraphs = normalized.split(/\n{2,}/).map((p) => p.trim()).filter(Boolean);
|
|
231
|
+
return {
|
|
232
|
+
type: "doc",
|
|
233
|
+
content: paragraphs.map((p) => {
|
|
234
|
+
const inline = parseInlineMarkdown(p);
|
|
235
|
+
return inline.length > 0 ? { type: "paragraph", content: inline } : { type: "paragraph" };
|
|
236
|
+
})
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function coerceRichTextDoc(value) {
|
|
240
|
+
const raw = unwrapRichTextRoot(value);
|
|
241
|
+
if (typeof raw === "string") {
|
|
242
|
+
return markdownToTipTapDoc(raw);
|
|
243
|
+
}
|
|
244
|
+
if (isRecord(raw) && typeof raw.type === "string") {
|
|
245
|
+
return raw;
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
function ProductBadgesView({ badges }) {
|
|
250
|
+
if (badges.length === 0) return null;
|
|
251
|
+
return /* @__PURE__ */ jsx("div", { className: "rb-flex rb-flex-wrap rb-gap-2", children: badges.map((badge) => /* @__PURE__ */ jsx(
|
|
252
|
+
"span",
|
|
253
|
+
{
|
|
254
|
+
className: "rb-inline-flex rb-items-center rb-rounded-full rb-border rb-px-3 rb-py-1 rb-text-xs",
|
|
255
|
+
children: badge
|
|
256
|
+
},
|
|
257
|
+
badge
|
|
258
|
+
)) });
|
|
259
|
+
}
|
|
260
|
+
function VariantSelectView(props) {
|
|
261
|
+
const { options, selectedVariantId, disabled = false, onChange } = props;
|
|
262
|
+
if (options.length === 0) return null;
|
|
263
|
+
return /* @__PURE__ */ jsxs("label", { className: "rb-flex rb-flex-col rb-gap-1", children: [
|
|
264
|
+
/* @__PURE__ */ jsx("span", { className: "rb-label", children: "Variant" }),
|
|
265
|
+
/* @__PURE__ */ jsx(
|
|
266
|
+
"select",
|
|
267
|
+
{
|
|
268
|
+
className: "form-input",
|
|
269
|
+
value: selectedVariantId ?? "",
|
|
270
|
+
disabled,
|
|
271
|
+
onChange: onChange ? (event) => onChange(event.target.value || null) : void 0,
|
|
272
|
+
children: options.map((variant) => /* @__PURE__ */ jsxs("option", { value: variant.id, children: [
|
|
273
|
+
variant.label,
|
|
274
|
+
" ",
|
|
275
|
+
variant.soldOut ? "(Sold out)" : ""
|
|
276
|
+
] }, variant.id))
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
] });
|
|
280
|
+
}
|
|
281
|
+
function ProductCardView(props) {
|
|
282
|
+
const { display, layout, variantControl, actions, feedback } = props;
|
|
283
|
+
return /* @__PURE__ */ jsxs("article", { className: `rb-flex rb-gap-4 rb-rounded-2xl rb-border rb-p-4 ${layout === "list" ? "rb-flex-row" : "rb-flex-col"}`, children: [
|
|
284
|
+
display.image ? /* @__PURE__ */ jsx("a", { href: display.path ?? void 0, className: "rb-block rb-overflow-hidden rb-rounded-xl rb-border", children: /* @__PURE__ */ jsx("img", { src: display.image.url, alt: display.image.alt ?? "", className: "rb-h-48 rb-w-full rb-object-cover" }) }) : null,
|
|
285
|
+
/* @__PURE__ */ jsxs("div", { className: "rb-flex rb-flex-1 rb-flex-col rb-gap-3", children: [
|
|
286
|
+
/* @__PURE__ */ jsxs("div", { className: "rb-flex rb-flex-col rb-gap-2", children: [
|
|
287
|
+
/* @__PURE__ */ jsxs("div", { className: "rb-flex rb-items-start rb-justify-between rb-gap-3", children: [
|
|
288
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
289
|
+
display.path ? /* @__PURE__ */ jsx("a", { href: display.path, children: /* @__PURE__ */ jsx("h3", { children: display.title }) }) : /* @__PURE__ */ jsx("h3", { children: display.title }),
|
|
290
|
+
display.summary ? /* @__PURE__ */ jsx("p", { className: "rb-caption status-muted", children: display.summary }) : null
|
|
291
|
+
] }),
|
|
292
|
+
display.priceLabel ? /* @__PURE__ */ jsx("div", { className: "rb-text-right", children: /* @__PURE__ */ jsx("strong", { children: display.priceLabel }) }) : null
|
|
293
|
+
] }),
|
|
294
|
+
/* @__PURE__ */ jsx(ProductBadgesView, { badges: display.badges })
|
|
295
|
+
] }),
|
|
296
|
+
variantControl,
|
|
297
|
+
/* @__PURE__ */ jsx("div", { className: "rb-mt-auto rb-flex rb-items-center rb-gap-3", children: actions }),
|
|
298
|
+
feedback,
|
|
299
|
+
/* @__PURE__ */ jsx("span", { className: "rb-caption status-muted", children: display.selectionLabel })
|
|
300
|
+
] })
|
|
301
|
+
] });
|
|
302
|
+
}
|
|
303
|
+
function ProductListSectionView(props) {
|
|
304
|
+
const { display, children } = props;
|
|
305
|
+
return /* @__PURE__ */ jsxs("div", { className: `shop shop--product-list ${display.className ?? ""}`.trim(), "data-block": "shop", children: [
|
|
306
|
+
display.heading ? /* @__PURE__ */ jsx("h2", { children: display.heading }) : null,
|
|
307
|
+
display.state === "empty" ? /* @__PURE__ */ jsx("p", { className: "rb-caption status-muted", children: display.emptyMessage }) : /* @__PURE__ */ jsx(
|
|
308
|
+
"div",
|
|
309
|
+
{
|
|
310
|
+
className: `rb-grid rb-gap-4 ${display.layout === "list" ? "rb-grid-cols-1" : ""}`,
|
|
311
|
+
style: display.layout === "grid" ? { gridTemplateColumns: `repeat(${display.columns}, minmax(0, 1fr))` } : void 0,
|
|
312
|
+
children
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
] });
|
|
316
|
+
}
|
|
317
|
+
function ProductDetailView(props) {
|
|
318
|
+
const { display, gallery, variantControl, actions, feedback, body } = props;
|
|
319
|
+
if (display.state === "empty") {
|
|
320
|
+
return /* @__PURE__ */ jsx("div", { className: `shop shop--product-detail ${display.className ?? ""}`.trim(), "data-block": "shop", children: /* @__PURE__ */ jsx("p", { className: "rb-caption status-muted", children: display.emptyMessage }) });
|
|
321
|
+
}
|
|
322
|
+
return /* @__PURE__ */ jsxs("div", { className: `shop shop--product-detail rb-grid rb-gap-6 rb-md-grid-cols-2 ${display.className ?? ""}`.trim(), "data-block": "shop", children: [
|
|
323
|
+
/* @__PURE__ */ jsx("div", { className: "rb-flex rb-flex-col rb-gap-4", children: gallery }),
|
|
324
|
+
/* @__PURE__ */ jsxs("div", { className: "rb-flex rb-flex-col rb-gap-4", children: [
|
|
325
|
+
display.heading ? /* @__PURE__ */ jsx("p", { className: "rb-label", children: display.heading }) : null,
|
|
326
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
327
|
+
/* @__PURE__ */ jsx("h2", { children: display.title }),
|
|
328
|
+
/* @__PURE__ */ jsx("div", { className: "rb-mt-2", children: /* @__PURE__ */ jsx("strong", { children: display.priceLabel }) })
|
|
329
|
+
] }),
|
|
330
|
+
/* @__PURE__ */ jsx(ProductBadgesView, { badges: display.badges }),
|
|
331
|
+
display.summary ? /* @__PURE__ */ jsx("p", { className: "rb-caption status-muted", children: display.summary }) : null,
|
|
332
|
+
variantControl,
|
|
333
|
+
/* @__PURE__ */ jsxs("div", { className: "rb-flex rb-items-center rb-gap-3", children: [
|
|
334
|
+
actions,
|
|
335
|
+
display.categoryLabel ? /* @__PURE__ */ jsx("span", { className: "rb-caption status-muted", children: display.categoryLabel }) : null
|
|
336
|
+
] }),
|
|
337
|
+
feedback,
|
|
338
|
+
body
|
|
339
|
+
] })
|
|
340
|
+
] });
|
|
341
|
+
}
|
|
342
|
+
function ProductGalleryFrameView(props) {
|
|
343
|
+
const { gallery, renderCarousel } = props;
|
|
344
|
+
if (gallery.length === 0) return null;
|
|
345
|
+
const slides = gallery.map((image, index) => /* @__PURE__ */ jsx("div", { className: "rb-overflow-hidden rb-rounded-xl rb-border", children: /* @__PURE__ */ jsx(
|
|
346
|
+
"img",
|
|
347
|
+
{
|
|
348
|
+
src: image.url,
|
|
349
|
+
alt: image.alt ?? "",
|
|
350
|
+
className: "rb-h-full rb-w-full rb-object-cover"
|
|
351
|
+
}
|
|
352
|
+
) }, `${image.url}:${index}`));
|
|
353
|
+
if (slides.length === 1) {
|
|
354
|
+
return slides[0] ?? null;
|
|
355
|
+
}
|
|
356
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderCarousel(slides) });
|
|
357
|
+
}
|
|
358
|
+
function PassCardView(props) {
|
|
359
|
+
return /* @__PURE__ */ jsxs("article", { className: "shop__card shop__card--pass", children: [
|
|
360
|
+
/* @__PURE__ */ jsxs("div", { className: "shop__card-header", children: [
|
|
361
|
+
/* @__PURE__ */ jsx("h4", { className: "shop__card-name", children: props.display.title }),
|
|
362
|
+
/* @__PURE__ */ jsx("div", { className: "shop__card-price", children: props.display.priceLabel })
|
|
363
|
+
] }),
|
|
364
|
+
props.display.description ? /* @__PURE__ */ jsx("p", { className: "shop__card-description", children: props.display.description }) : null,
|
|
365
|
+
/* @__PURE__ */ jsxs("div", { className: "shop__card-details", children: [
|
|
366
|
+
/* @__PURE__ */ jsx("span", { className: "shop__card-credits", children: props.display.creditsLabel }),
|
|
367
|
+
props.display.perCreditLabel ? /* @__PURE__ */ jsxs("span", { className: "shop__card-per-credit", children: [
|
|
368
|
+
"(",
|
|
369
|
+
props.display.perCreditLabel,
|
|
370
|
+
")"
|
|
371
|
+
] }) : null,
|
|
372
|
+
props.display.validityLabel ? /* @__PURE__ */ jsx("span", { className: "shop__card-validity", children: props.display.validityLabel }) : null
|
|
373
|
+
] }),
|
|
374
|
+
props.action,
|
|
375
|
+
props.feedback
|
|
376
|
+
] });
|
|
377
|
+
}
|
|
378
|
+
function MembershipCardView(props) {
|
|
379
|
+
return /* @__PURE__ */ jsxs("article", { className: "shop__card shop__card--membership", children: [
|
|
380
|
+
/* @__PURE__ */ jsxs("div", { className: "shop__card-header", children: [
|
|
381
|
+
/* @__PURE__ */ jsx("h4", { className: "shop__card-name", children: props.display.title }),
|
|
382
|
+
/* @__PURE__ */ jsxs("div", { className: "shop__card-price", children: [
|
|
383
|
+
props.display.priceLabel,
|
|
384
|
+
/* @__PURE__ */ jsx("span", { className: "shop__card-interval", children: props.display.intervalLabel })
|
|
385
|
+
] })
|
|
386
|
+
] }),
|
|
387
|
+
props.display.description ? /* @__PURE__ */ jsx("p", { className: "shop__card-description", children: props.display.description }) : null,
|
|
388
|
+
/* @__PURE__ */ jsx("div", { className: "shop__card-details", children: /* @__PURE__ */ jsx("span", { className: "shop__card-unlimited", children: "Unlimited classes" }) }),
|
|
389
|
+
props.action
|
|
390
|
+
] });
|
|
391
|
+
}
|
|
392
|
+
function PassesMembershipsSectionView(props) {
|
|
393
|
+
const { display, beforeContent, passCards, membershipCards } = props;
|
|
394
|
+
const productsClass = `shop__products shop__products--${display.layout}`;
|
|
395
|
+
const gridStyle = display.state === "ready" ? { "--shop-columns": display.columns } : void 0;
|
|
396
|
+
return /* @__PURE__ */ jsxs(
|
|
397
|
+
"div",
|
|
398
|
+
{
|
|
399
|
+
className: `shop ${display.state === "ready" ? `shop--${display.layout}` : "shop--empty"} ${display.className ?? ""}`.trim(),
|
|
400
|
+
id: "shop",
|
|
401
|
+
"data-block": "shop",
|
|
402
|
+
style: gridStyle,
|
|
403
|
+
children: [
|
|
404
|
+
beforeContent,
|
|
405
|
+
display.state === "empty" ? /* @__PURE__ */ jsx("p", { className: "shop__empty-text", children: display.emptyMessage }) : null,
|
|
406
|
+
display.passes.length > 0 ? /* @__PURE__ */ jsxs("section", { className: "shop__section shop__section--passes", children: [
|
|
407
|
+
/* @__PURE__ */ jsx("h3", { className: "shop__section-heading", children: display.passesHeading }),
|
|
408
|
+
/* @__PURE__ */ jsx("div", { className: productsClass, children: passCards })
|
|
409
|
+
] }) : null,
|
|
410
|
+
display.memberships.length > 0 ? /* @__PURE__ */ jsxs("section", { className: "shop__section shop__section--memberships", children: [
|
|
411
|
+
/* @__PURE__ */ jsx("h3", { className: "shop__section-heading", children: display.membershipsHeading }),
|
|
412
|
+
/* @__PURE__ */ jsx("div", { className: productsClass, children: membershipCards })
|
|
413
|
+
] }) : null
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
function CartShellView(props) {
|
|
419
|
+
const { display, renderControls, footerActions } = props;
|
|
420
|
+
return /* @__PURE__ */ jsxs("div", { className: `shop shop--cart ${display.className ?? ""}`.trim(), "data-block": "shop", id: "shop-cart", children: [
|
|
421
|
+
display.heading ? /* @__PURE__ */ jsx("h2", { children: display.heading }) : null,
|
|
422
|
+
display.state === "empty" ? /* @__PURE__ */ jsxs("div", { className: "rb-flex rb-flex-col rb-gap-2", children: [
|
|
423
|
+
/* @__PURE__ */ jsx("p", { className: "rb-caption status-muted", children: display.emptyMessage }),
|
|
424
|
+
display.supportText ? /* @__PURE__ */ jsx("p", { className: "rb-caption status-muted", children: display.supportText }) : null
|
|
425
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "rb-flex rb-flex-col rb-gap-4", children: [
|
|
426
|
+
/* @__PURE__ */ jsx("div", { className: "rb-flex rb-flex-col rb-gap-3", children: display.items.map((item) => /* @__PURE__ */ jsxs("div", { className: "rb-flex rb-items-center rb-justify-between rb-gap-4 rb-rounded-xl rb-border rb-p-4", children: [
|
|
427
|
+
/* @__PURE__ */ jsxs("div", { className: "rb-flex rb-flex-col rb-gap-1", children: [
|
|
428
|
+
/* @__PURE__ */ jsx("strong", { children: item.title }),
|
|
429
|
+
/* @__PURE__ */ jsx("span", { className: "rb-caption status-muted", children: item.priceLabel })
|
|
430
|
+
] }),
|
|
431
|
+
/* @__PURE__ */ jsx("div", { className: "rb-flex rb-items-center rb-gap-2", children: renderControls ? renderControls(item) : null })
|
|
432
|
+
] }, item.key)) }),
|
|
433
|
+
/* @__PURE__ */ jsxs("div", { className: "rb-flex rb-items-center rb-justify-between rb-gap-4", children: [
|
|
434
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
435
|
+
/* @__PURE__ */ jsx("strong", { children: "Subtotal" }),
|
|
436
|
+
/* @__PURE__ */ jsx("div", { children: display.subtotalLabel })
|
|
437
|
+
] }),
|
|
438
|
+
/* @__PURE__ */ jsx("div", { className: "rb-flex rb-gap-2", children: footerActions })
|
|
439
|
+
] })
|
|
440
|
+
] })
|
|
441
|
+
] });
|
|
442
|
+
}
|
|
443
|
+
function CheckoutShellView(props) {
|
|
444
|
+
return /* @__PURE__ */ jsxs("div", { className: `shop shop--checkout ${props.display.className ?? ""}`.trim(), "data-block": "shop", id: "shop-checkout", children: [
|
|
445
|
+
props.display.heading ? /* @__PURE__ */ jsx("h2", { children: props.display.heading }) : null,
|
|
446
|
+
props.notice,
|
|
447
|
+
props.display.state === "ready" ? props.body : /* @__PURE__ */ jsx("p", { className: "rb-caption status-muted", children: props.display.message })
|
|
448
|
+
] });
|
|
449
|
+
}
|
|
450
|
+
var DEFAULT_TIMEOUT_MS = 2500;
|
|
451
|
+
function useTransientShopAddFeedback(timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
452
|
+
const [activeKey, setActiveKey] = React3.useState(null);
|
|
453
|
+
React3.useEffect(() => {
|
|
454
|
+
if (!activeKey) return;
|
|
455
|
+
const timeoutId = window.setTimeout(() => {
|
|
456
|
+
setActiveKey((current) => current === activeKey ? null : current);
|
|
457
|
+
}, timeoutMs);
|
|
458
|
+
return () => window.clearTimeout(timeoutId);
|
|
459
|
+
}, [activeKey, timeoutMs]);
|
|
460
|
+
return {
|
|
461
|
+
activeKey,
|
|
462
|
+
markAdded: setActiveKey,
|
|
463
|
+
clear: () => setActiveKey(null)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function ShopAddFeedback(props) {
|
|
467
|
+
if (!props.active) return null;
|
|
468
|
+
return /* @__PURE__ */ jsxs(
|
|
469
|
+
FeedbackMessage,
|
|
470
|
+
{
|
|
471
|
+
tone: "success",
|
|
472
|
+
className: "rb-caption status-muted",
|
|
473
|
+
role: "status",
|
|
474
|
+
live: "polite",
|
|
475
|
+
children: [
|
|
476
|
+
"Added ",
|
|
477
|
+
props.itemLabel,
|
|
478
|
+
" to cart.",
|
|
479
|
+
" ",
|
|
480
|
+
/* @__PURE__ */ jsx("a", { href: "#shop-cart", children: "View cart" }),
|
|
481
|
+
"."
|
|
482
|
+
]
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ../blocks/src/system/runtime/nodes/shop-checkout.state.ts
|
|
488
|
+
var initialShopCheckoutState = {
|
|
489
|
+
form: {
|
|
490
|
+
name: "",
|
|
491
|
+
email: ""
|
|
492
|
+
},
|
|
493
|
+
submission: "idle",
|
|
494
|
+
feedback: { tag: "idle" },
|
|
495
|
+
notice: null
|
|
496
|
+
};
|
|
497
|
+
function shopCheckoutReducer(state, action) {
|
|
498
|
+
switch (action.type) {
|
|
499
|
+
case "setField":
|
|
500
|
+
return {
|
|
501
|
+
...state,
|
|
502
|
+
form: {
|
|
503
|
+
...state.form,
|
|
504
|
+
[action.field]: action.value
|
|
505
|
+
},
|
|
506
|
+
feedback: { tag: "idle" }
|
|
507
|
+
};
|
|
508
|
+
case "submitStarted":
|
|
509
|
+
return {
|
|
510
|
+
...state,
|
|
511
|
+
submission: "submitting",
|
|
512
|
+
feedback: { tag: "idle" }
|
|
513
|
+
};
|
|
514
|
+
case "submitFailed":
|
|
515
|
+
return {
|
|
516
|
+
...state,
|
|
517
|
+
submission: "idle",
|
|
518
|
+
feedback: { tag: "error", message: action.message }
|
|
519
|
+
};
|
|
520
|
+
case "submitCompleted":
|
|
521
|
+
return {
|
|
522
|
+
...state,
|
|
523
|
+
submission: "idle"
|
|
524
|
+
};
|
|
525
|
+
case "setNotice":
|
|
526
|
+
return {
|
|
527
|
+
...state,
|
|
528
|
+
notice: action.notice
|
|
529
|
+
};
|
|
530
|
+
default:
|
|
531
|
+
return assertNever(action);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
var CHECKOUT_ANCHOR_ID = "shop-checkout";
|
|
535
|
+
function ProductListController(props) {
|
|
536
|
+
const api = useBlockApi();
|
|
537
|
+
const cart = useCommerceCart(api.config.siteId);
|
|
538
|
+
const addFeedback = useTransientShopAddFeedback();
|
|
539
|
+
const [variantSelection, setVariantSelection] = React3.useState(
|
|
540
|
+
() => Object.fromEntries(props.render.display.cards.map((card) => [card.productId, card.selectedVariantId]))
|
|
541
|
+
);
|
|
542
|
+
const cards = React3.useMemo(
|
|
543
|
+
() => props.render.hydration.products.map(
|
|
544
|
+
(product) => buildProductCardDisplay(
|
|
545
|
+
product,
|
|
546
|
+
{
|
|
547
|
+
showSummary: props.render.hydration.showSummary,
|
|
548
|
+
showBadges: props.render.hydration.showBadges,
|
|
549
|
+
showPrices: props.render.hydration.showPrices,
|
|
550
|
+
addButtonText: props.render.hydration.addButtonText
|
|
551
|
+
},
|
|
552
|
+
variantSelection[product.id] ?? null
|
|
553
|
+
)
|
|
554
|
+
),
|
|
555
|
+
[props.render.hydration, variantSelection]
|
|
556
|
+
);
|
|
557
|
+
const display = React3.useMemo(
|
|
558
|
+
() => ({
|
|
559
|
+
...props.render.display,
|
|
560
|
+
state: cards.length === 0 ? "empty" : "ready",
|
|
561
|
+
cards
|
|
562
|
+
}),
|
|
563
|
+
[cards, props.render.display]
|
|
564
|
+
);
|
|
565
|
+
return /* @__PURE__ */ jsx(ProductListSectionView, { display, children: display.cards.map((card) => /* @__PURE__ */ jsx(
|
|
566
|
+
ProductCardView,
|
|
567
|
+
{
|
|
568
|
+
display: card,
|
|
569
|
+
layout: display.layout,
|
|
570
|
+
variantControl: /* @__PURE__ */ jsx(
|
|
571
|
+
VariantSelectView,
|
|
572
|
+
{
|
|
573
|
+
options: card.variantOptions,
|
|
574
|
+
selectedVariantId: card.selectedVariantId,
|
|
575
|
+
onChange: (variantId) => setVariantSelection((current) => ({
|
|
576
|
+
...current,
|
|
577
|
+
[card.productId]: variantId
|
|
578
|
+
}))
|
|
579
|
+
}
|
|
580
|
+
),
|
|
581
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
582
|
+
/* @__PURE__ */ jsx(
|
|
583
|
+
"button",
|
|
584
|
+
{
|
|
585
|
+
type: "button",
|
|
586
|
+
className: themeButtonClassName({ variant: "default", size: "md" }),
|
|
587
|
+
onClick: () => {
|
|
588
|
+
const product = props.render.hydration.products.find((candidate) => candidate.id === card.productId);
|
|
589
|
+
if (!product) return;
|
|
590
|
+
const cartItem = buildProductCartItem({
|
|
591
|
+
product,
|
|
592
|
+
variantId: card.selectedVariantId,
|
|
593
|
+
quantity: 1
|
|
594
|
+
});
|
|
595
|
+
cart.addProduct(cartItem);
|
|
596
|
+
addFeedback.markAdded(cartItem.key);
|
|
597
|
+
},
|
|
598
|
+
disabled: card.soldOut,
|
|
599
|
+
children: card.soldOut ? "Sold out" : card.actionLabel
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
card.path ? /* @__PURE__ */ jsx(
|
|
603
|
+
"a",
|
|
604
|
+
{
|
|
605
|
+
href: card.path,
|
|
606
|
+
className: themeButtonClassName({ variant: "secondary", size: "sm" }),
|
|
607
|
+
children: "View details"
|
|
608
|
+
}
|
|
609
|
+
) : null
|
|
610
|
+
] }),
|
|
611
|
+
feedback: /* @__PURE__ */ jsx(
|
|
612
|
+
ShopAddFeedback,
|
|
613
|
+
{
|
|
614
|
+
active: addFeedback.activeKey === productItemKey(card.productId, card.selectedVariantId),
|
|
615
|
+
itemLabel: card.selectionLabel
|
|
616
|
+
}
|
|
617
|
+
)
|
|
618
|
+
},
|
|
619
|
+
card.productId
|
|
620
|
+
)) });
|
|
621
|
+
}
|
|
622
|
+
function ProductDetailController(props) {
|
|
623
|
+
const api = useBlockApi();
|
|
624
|
+
const cart = useCommerceCart(api.config.siteId);
|
|
625
|
+
const addFeedback = useTransientShopAddFeedback();
|
|
626
|
+
const selectedProduct = React3.useMemo(
|
|
627
|
+
() => props.render.hydration.products.find((product) => product.id === props.render.hydration.productId) ?? null,
|
|
628
|
+
[props.render.hydration.productId, props.render.hydration.products]
|
|
629
|
+
);
|
|
630
|
+
const [variantId, setVariantId] = React3.useState(
|
|
631
|
+
props.render.display.state === "ready" ? props.render.display.selectedVariantId : null
|
|
632
|
+
);
|
|
633
|
+
React3.useEffect(() => {
|
|
634
|
+
setVariantId(props.render.display.state === "ready" ? props.render.display.selectedVariantId : null);
|
|
635
|
+
}, [props.render.display]);
|
|
636
|
+
const display = React3.useMemo(
|
|
637
|
+
() => buildProductDetailDisplay(
|
|
638
|
+
selectedProduct,
|
|
639
|
+
{
|
|
640
|
+
heading: props.render.display.heading,
|
|
641
|
+
className: props.render.display.className,
|
|
642
|
+
emptyMessage: props.render.display.state === "empty" ? props.render.display.emptyMessage : void 0,
|
|
643
|
+
showSummary: props.render.hydration.showSummary,
|
|
644
|
+
showBadges: props.render.hydration.showBadges,
|
|
645
|
+
addButtonText: props.render.hydration.addButtonText
|
|
646
|
+
},
|
|
647
|
+
variantId
|
|
648
|
+
),
|
|
649
|
+
[props.render.display, props.render.hydration, selectedProduct, variantId]
|
|
650
|
+
);
|
|
651
|
+
const bodyDoc = display.state === "ready" && props.render.hydration.showBody ? coerceRichTextDoc(display.body) : null;
|
|
652
|
+
return /* @__PURE__ */ jsx(
|
|
653
|
+
ProductDetailView,
|
|
654
|
+
{
|
|
655
|
+
display,
|
|
656
|
+
gallery: display.state === "ready" ? /* @__PURE__ */ jsx(
|
|
657
|
+
ProductGalleryFrameView,
|
|
658
|
+
{
|
|
659
|
+
gallery: display.gallery,
|
|
660
|
+
renderCarousel: (slides) => /* @__PURE__ */ jsx(CarouselNodeClient, { slidesToShow: 1, showDots: true, showControls: true, className: "rb-w-full", children: slides })
|
|
661
|
+
}
|
|
662
|
+
) : null,
|
|
663
|
+
variantControl: display.state === "ready" ? /* @__PURE__ */ jsx(
|
|
664
|
+
VariantSelectView,
|
|
665
|
+
{
|
|
666
|
+
options: display.variantOptions,
|
|
667
|
+
selectedVariantId: display.selectedVariantId,
|
|
668
|
+
onChange: setVariantId
|
|
669
|
+
}
|
|
670
|
+
) : null,
|
|
671
|
+
actions: display.state === "ready" ? /* @__PURE__ */ jsx(
|
|
672
|
+
"button",
|
|
673
|
+
{
|
|
674
|
+
type: "button",
|
|
675
|
+
className: themeButtonClassName({ variant: "default", size: "md" }),
|
|
676
|
+
disabled: display.soldOut,
|
|
677
|
+
onClick: () => {
|
|
678
|
+
if (!selectedProduct) return;
|
|
679
|
+
const cartItem = buildProductCartItem({
|
|
680
|
+
product: selectedProduct,
|
|
681
|
+
variantId: display.selectedVariantId,
|
|
682
|
+
quantity: 1
|
|
683
|
+
});
|
|
684
|
+
cart.addProduct(cartItem);
|
|
685
|
+
addFeedback.markAdded(cartItem.key);
|
|
686
|
+
},
|
|
687
|
+
children: display.soldOut ? "Sold out" : display.actionLabel
|
|
688
|
+
}
|
|
689
|
+
) : null,
|
|
690
|
+
feedback: display.state === "ready" ? /* @__PURE__ */ jsx(
|
|
691
|
+
ShopAddFeedback,
|
|
692
|
+
{
|
|
693
|
+
active: addFeedback.activeKey === productItemKey(selectedProduct?.id ?? "", display.selectedVariantId),
|
|
694
|
+
itemLabel: display.selectionLabel
|
|
695
|
+
}
|
|
696
|
+
) : null,
|
|
697
|
+
body: bodyDoc ? /* @__PURE__ */ jsx("div", { className: "rb-prose rb-max-w-none", children: /* @__PURE__ */ jsx(RichText, { doc: bodyDoc }) }) : null
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
function CartController(props) {
|
|
702
|
+
const api = useBlockApi();
|
|
703
|
+
const cart = useCommerceCart(api.config.siteId);
|
|
704
|
+
const display = React3.useMemo(
|
|
705
|
+
() => getShopDisplayForItems(props.render, cart.items),
|
|
706
|
+
[cart.items, props.render]
|
|
707
|
+
);
|
|
708
|
+
return /* @__PURE__ */ jsx(
|
|
709
|
+
CartShellView,
|
|
710
|
+
{
|
|
711
|
+
display,
|
|
712
|
+
renderControls: (item) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
713
|
+
/* @__PURE__ */ jsx(
|
|
714
|
+
"input",
|
|
715
|
+
{
|
|
716
|
+
type: "number",
|
|
717
|
+
min: 1,
|
|
718
|
+
className: "form-input rb-w-20",
|
|
719
|
+
value: item.quantity,
|
|
720
|
+
readOnly: !item.quantityEditable,
|
|
721
|
+
"aria-label": `Quantity for ${item.title}`,
|
|
722
|
+
onChange: item.quantityEditable ? (event) => cart.updateQuantity(item.key, Number(event.target.value)) : void 0
|
|
723
|
+
}
|
|
724
|
+
),
|
|
725
|
+
/* @__PURE__ */ jsx(
|
|
726
|
+
"button",
|
|
727
|
+
{
|
|
728
|
+
type: "button",
|
|
729
|
+
className: themeButtonClassName({ variant: "secondary", size: "sm" }),
|
|
730
|
+
onClick: () => cart.removeItem(item.key),
|
|
731
|
+
children: "Remove"
|
|
732
|
+
}
|
|
733
|
+
)
|
|
734
|
+
] }),
|
|
735
|
+
footerActions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
736
|
+
/* @__PURE__ */ jsx(
|
|
737
|
+
"button",
|
|
738
|
+
{
|
|
739
|
+
type: "button",
|
|
740
|
+
className: themeButtonClassName({ variant: "secondary", size: "sm" }),
|
|
741
|
+
onClick: () => cart.clear(),
|
|
742
|
+
children: display.clearButtonText
|
|
743
|
+
}
|
|
744
|
+
),
|
|
745
|
+
/* @__PURE__ */ jsx(
|
|
746
|
+
"button",
|
|
747
|
+
{
|
|
748
|
+
type: "button",
|
|
749
|
+
className: themeButtonClassName({ variant: "default", size: "md" }),
|
|
750
|
+
onClick: () => {
|
|
751
|
+
window.location.href = resolveDedicatedCheckoutPath(window.location.pathname);
|
|
752
|
+
},
|
|
753
|
+
children: display.checkoutButtonText
|
|
754
|
+
}
|
|
755
|
+
)
|
|
756
|
+
] })
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
function CheckoutController(props) {
|
|
761
|
+
const api = useBlockApi();
|
|
762
|
+
const cart = useCommerceCart(api.config.siteId);
|
|
763
|
+
const [state, dispatch] = React3.useReducer(
|
|
764
|
+
shopCheckoutReducer,
|
|
765
|
+
initialShopCheckoutState
|
|
766
|
+
);
|
|
767
|
+
React3.useEffect(() => {
|
|
768
|
+
const checkout = consumeSearchParam("checkout");
|
|
769
|
+
if (checkout === "success") {
|
|
770
|
+
cart.clear();
|
|
771
|
+
dispatch({ type: "setNotice", notice: "success" });
|
|
772
|
+
} else if (checkout === "cancelled") {
|
|
773
|
+
dispatch({ type: "setNotice", notice: "cancelled" });
|
|
774
|
+
}
|
|
775
|
+
}, [cart]);
|
|
776
|
+
const display = React3.useMemo(
|
|
777
|
+
() => getShopDisplayForItems(props.render, cart.items),
|
|
778
|
+
[cart.items, props.render]
|
|
779
|
+
);
|
|
780
|
+
const handleSubmit = async (event) => {
|
|
781
|
+
event.preventDefault();
|
|
782
|
+
if (display.state !== "ready" || state.submission === "submitting") return;
|
|
783
|
+
dispatch({ type: "submitStarted" });
|
|
784
|
+
try {
|
|
785
|
+
const { successUrl, cancelUrl } = buildCheckoutReturnUrls("checkout", CHECKOUT_ANCHOR_ID);
|
|
786
|
+
const checkoutItems = cart.items.map(
|
|
787
|
+
(item) => item.kind === "product" ? {
|
|
788
|
+
type: "product",
|
|
789
|
+
productId: item.productId,
|
|
790
|
+
...item.variantId ? { variantId: item.variantId } : {},
|
|
791
|
+
quantity: item.quantity
|
|
792
|
+
} : {
|
|
793
|
+
type: "pass",
|
|
794
|
+
passId: item.passId
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
const result = await api.createUnifiedCheckout({
|
|
798
|
+
mode: "one_time",
|
|
799
|
+
items: checkoutItems,
|
|
800
|
+
customer: {
|
|
801
|
+
email: state.form.email.trim().toLowerCase(),
|
|
802
|
+
name: state.form.name.trim()
|
|
803
|
+
},
|
|
804
|
+
successUrl,
|
|
805
|
+
cancelUrl
|
|
806
|
+
});
|
|
807
|
+
if (result.type === "redirect") {
|
|
808
|
+
window.location.href = result.checkoutUrl;
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
cart.clear();
|
|
812
|
+
window.location.href = successUrl;
|
|
813
|
+
} catch (submitError) {
|
|
814
|
+
dispatch({
|
|
815
|
+
type: "submitFailed",
|
|
816
|
+
message: submitError instanceof Error ? submitError.message : "Checkout failed"
|
|
817
|
+
});
|
|
818
|
+
return;
|
|
819
|
+
} finally {
|
|
820
|
+
dispatch({ type: "submitCompleted" });
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
return /* @__PURE__ */ jsx(
|
|
824
|
+
CheckoutShellView,
|
|
825
|
+
{
|
|
826
|
+
display,
|
|
827
|
+
notice: state.notice ? /* @__PURE__ */ jsx(
|
|
828
|
+
FeedbackMessage,
|
|
829
|
+
{
|
|
830
|
+
tone: state.notice === "success" ? "success" : "warning",
|
|
831
|
+
className: "rb-mb-4 rb-rounded-xl rb-border rb-p-4",
|
|
832
|
+
role: "status",
|
|
833
|
+
children: state.notice === "success" ? "Payment successful." : "Checkout cancelled."
|
|
834
|
+
}
|
|
835
|
+
) : null,
|
|
836
|
+
body: /* @__PURE__ */ jsxs("form", { className: "rb-flex rb-flex-col rb-gap-4", onSubmit: handleSubmit, children: [
|
|
837
|
+
/* @__PURE__ */ jsxs("label", { className: "rb-flex rb-flex-col rb-gap-1", children: [
|
|
838
|
+
/* @__PURE__ */ jsx("span", { className: "rb-label", children: "Name" }),
|
|
839
|
+
/* @__PURE__ */ jsx(
|
|
840
|
+
"input",
|
|
841
|
+
{
|
|
842
|
+
className: "form-input",
|
|
843
|
+
value: state.form.name,
|
|
844
|
+
onChange: (event) => dispatch({ type: "setField", field: "name", value: event.target.value }),
|
|
845
|
+
required: true
|
|
846
|
+
}
|
|
847
|
+
)
|
|
848
|
+
] }),
|
|
849
|
+
/* @__PURE__ */ jsxs("label", { className: "rb-flex rb-flex-col rb-gap-1", children: [
|
|
850
|
+
/* @__PURE__ */ jsx("span", { className: "rb-label", children: "Email" }),
|
|
851
|
+
/* @__PURE__ */ jsx(
|
|
852
|
+
"input",
|
|
853
|
+
{
|
|
854
|
+
className: "form-input",
|
|
855
|
+
type: "email",
|
|
856
|
+
value: state.form.email,
|
|
857
|
+
onChange: (event) => dispatch({ type: "setField", field: "email", value: event.target.value }),
|
|
858
|
+
required: true
|
|
859
|
+
}
|
|
860
|
+
)
|
|
861
|
+
] }),
|
|
862
|
+
state.feedback.tag === "error" ? /* @__PURE__ */ jsx(
|
|
863
|
+
FeedbackMessage,
|
|
864
|
+
{
|
|
865
|
+
tone: "error",
|
|
866
|
+
className: "rb-caption status-muted",
|
|
867
|
+
role: "alert",
|
|
868
|
+
children: state.feedback.message
|
|
869
|
+
}
|
|
870
|
+
) : null,
|
|
871
|
+
/* @__PURE__ */ jsx(
|
|
872
|
+
"button",
|
|
873
|
+
{
|
|
874
|
+
type: "submit",
|
|
875
|
+
className: themeButtonClassName({ variant: "default", size: "md" }),
|
|
876
|
+
disabled: state.submission === "submitting",
|
|
877
|
+
children: state.submission === "submitting" ? "Loading\u2026" : display.submitButtonText
|
|
878
|
+
}
|
|
879
|
+
)
|
|
880
|
+
] })
|
|
881
|
+
}
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
function CartClient(props) {
|
|
885
|
+
return /* @__PURE__ */ jsx(CartController, { render: props.render });
|
|
886
|
+
}
|
|
887
|
+
function CheckoutClient(props) {
|
|
888
|
+
return /* @__PURE__ */ jsx(CheckoutController, { render: props.render });
|
|
889
|
+
}
|
|
890
|
+
function ProductDetailClient(props) {
|
|
891
|
+
return /* @__PURE__ */ jsx(ProductDetailController, { render: props.render });
|
|
892
|
+
}
|
|
893
|
+
function ProductListClient(props) {
|
|
894
|
+
return /* @__PURE__ */ jsx(ProductListController, { render: props.render });
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// ../blocks/src/system/runtime/nodes/shop.state.ts
|
|
898
|
+
var EMPTY_FORM = {
|
|
899
|
+
email: "",
|
|
900
|
+
name: ""
|
|
901
|
+
};
|
|
902
|
+
var CLOSED_CHECKOUT = { tag: "closed" };
|
|
903
|
+
var initialShopPassesMembershipsState = {
|
|
904
|
+
returnNotice: null,
|
|
905
|
+
checkout: CLOSED_CHECKOUT
|
|
906
|
+
};
|
|
907
|
+
function createOpenCheckoutState(item) {
|
|
908
|
+
return {
|
|
909
|
+
tag: "open",
|
|
910
|
+
item,
|
|
911
|
+
form: EMPTY_FORM,
|
|
912
|
+
submitting: false,
|
|
913
|
+
formError: null,
|
|
914
|
+
duplicateWarning: null
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
function shopPassesMembershipsReducer(state, action) {
|
|
918
|
+
switch (action.type) {
|
|
919
|
+
case "openPassCheckout":
|
|
920
|
+
return {
|
|
921
|
+
...state,
|
|
922
|
+
checkout: createOpenCheckoutState({
|
|
923
|
+
productType: "pass",
|
|
924
|
+
product: action.pass
|
|
925
|
+
})
|
|
926
|
+
};
|
|
927
|
+
case "openMembershipCheckout":
|
|
928
|
+
return {
|
|
929
|
+
...state,
|
|
930
|
+
checkout: createOpenCheckoutState({
|
|
931
|
+
productType: "membership",
|
|
932
|
+
product: action.membership
|
|
933
|
+
})
|
|
934
|
+
};
|
|
935
|
+
case "closeCheckout":
|
|
936
|
+
return {
|
|
937
|
+
...state,
|
|
938
|
+
checkout: CLOSED_CHECKOUT
|
|
939
|
+
};
|
|
940
|
+
case "setCheckoutField":
|
|
941
|
+
if (state.checkout.tag !== "open") return state;
|
|
942
|
+
return {
|
|
943
|
+
...state,
|
|
944
|
+
checkout: {
|
|
945
|
+
...state.checkout,
|
|
946
|
+
form: {
|
|
947
|
+
...state.checkout.form,
|
|
948
|
+
[action.field]: action.value
|
|
949
|
+
},
|
|
950
|
+
formError: null,
|
|
951
|
+
duplicateWarning: null
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
case "submitStarted":
|
|
955
|
+
if (state.checkout.tag !== "open") return state;
|
|
956
|
+
return {
|
|
957
|
+
...state,
|
|
958
|
+
checkout: {
|
|
959
|
+
...state.checkout,
|
|
960
|
+
submitting: true,
|
|
961
|
+
formError: null,
|
|
962
|
+
duplicateWarning: null
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
case "submitFailed":
|
|
966
|
+
if (state.checkout.tag !== "open") return state;
|
|
967
|
+
return {
|
|
968
|
+
...state,
|
|
969
|
+
checkout: {
|
|
970
|
+
...state.checkout,
|
|
971
|
+
submitting: false,
|
|
972
|
+
formError: action.error
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
case "submitDuplicateWarning":
|
|
976
|
+
if (state.checkout.tag !== "open") return state;
|
|
977
|
+
return {
|
|
978
|
+
...state,
|
|
979
|
+
checkout: {
|
|
980
|
+
...state.checkout,
|
|
981
|
+
submitting: false,
|
|
982
|
+
duplicateWarning: action.warning
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
case "setReturnNotice":
|
|
986
|
+
return {
|
|
987
|
+
...state,
|
|
988
|
+
returnNotice: action.notice
|
|
989
|
+
};
|
|
990
|
+
case "clearReturnNotice":
|
|
991
|
+
return {
|
|
992
|
+
...state,
|
|
993
|
+
returnNotice: null
|
|
994
|
+
};
|
|
995
|
+
default:
|
|
996
|
+
return assertNever(action);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function getOpenCheckout(state) {
|
|
1000
|
+
return state.checkout.tag === "open" ? state.checkout : null;
|
|
1001
|
+
}
|
|
1002
|
+
var SHOP_ANCHOR_ID = "shop";
|
|
1003
|
+
function ShopClient(props) {
|
|
1004
|
+
return /* @__PURE__ */ jsx(PassesMembershipsController, { render: props.render });
|
|
1005
|
+
}
|
|
1006
|
+
function getCheckoutModalTitle(checkout) {
|
|
1007
|
+
if (!checkout) return "";
|
|
1008
|
+
if (checkout.item.productType === "membership") return "Subscribe to Membership";
|
|
1009
|
+
return checkout.item.product.productUseCase === "appointment_package" ? "Purchase Package" : "Purchase Pass";
|
|
1010
|
+
}
|
|
1011
|
+
function PassesMembershipsController({ render }) {
|
|
1012
|
+
const api = useBlockApi();
|
|
1013
|
+
const [state, dispatch] = React3__default.useReducer(
|
|
1014
|
+
shopPassesMembershipsReducer,
|
|
1015
|
+
initialShopPassesMembershipsState
|
|
1016
|
+
);
|
|
1017
|
+
const passAddFeedback = useTransientShopAddFeedback();
|
|
1018
|
+
const [modalPortalRoot, setModalPortalRoot] = useState(null);
|
|
1019
|
+
const shopRootRef = useRef(null);
|
|
1020
|
+
const emailInputRef = useRef(null);
|
|
1021
|
+
const focusedCheckoutKeyRef = useRef(null);
|
|
1022
|
+
const checkout = getOpenCheckout(state);
|
|
1023
|
+
const display = render.display;
|
|
1024
|
+
const hydration = render.hydration;
|
|
1025
|
+
const checkoutTitle = getCheckoutModalTitle(checkout);
|
|
1026
|
+
const handleBuyPass = useCallback((pass) => {
|
|
1027
|
+
if (hydration.passPurchaseMode === "add_to_cart") {
|
|
1028
|
+
addPassToCommerceCart(api.config.siteId, {
|
|
1029
|
+
passId: pass.id,
|
|
1030
|
+
title: pass.name,
|
|
1031
|
+
unitPriceCents: pass.price,
|
|
1032
|
+
currency: pass.currency
|
|
1033
|
+
});
|
|
1034
|
+
passAddFeedback.markAdded(pass.id);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
dispatch({ type: "openPassCheckout", pass });
|
|
1038
|
+
}, [api.config.siteId, hydration.passPurchaseMode, passAddFeedback]);
|
|
1039
|
+
const handleSubscribe = useCallback((membership) => {
|
|
1040
|
+
dispatch({ type: "openMembershipCheckout", membership });
|
|
1041
|
+
}, []);
|
|
1042
|
+
const closeCheckout = useCallback(() => {
|
|
1043
|
+
dispatch({ type: "closeCheckout" });
|
|
1044
|
+
}, []);
|
|
1045
|
+
useEffect(() => {
|
|
1046
|
+
const checkoutKey = checkout ? `${checkout.item.productType}:${checkout.item.product.id}` : null;
|
|
1047
|
+
if (!checkoutKey) {
|
|
1048
|
+
focusedCheckoutKeyRef.current = null;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (checkoutKey !== focusedCheckoutKeyRef.current && emailInputRef.current) {
|
|
1052
|
+
emailInputRef.current.focus();
|
|
1053
|
+
focusedCheckoutKeyRef.current = checkoutKey;
|
|
1054
|
+
}
|
|
1055
|
+
}, [checkout]);
|
|
1056
|
+
useEffect(() => {
|
|
1057
|
+
const sourceEl = shopRootRef.current;
|
|
1058
|
+
if (!sourceEl) return;
|
|
1059
|
+
const portal = createBodyThemePortalRoot(sourceEl);
|
|
1060
|
+
if (!portal) return;
|
|
1061
|
+
portal.root.style.setProperty("--rb-modal-z-index", "20000");
|
|
1062
|
+
portal.root.style.setProperty("--rb-modal-overlay-bg", "rgb(var(--tb-text, 0 0 0) / 0.6)");
|
|
1063
|
+
setModalPortalRoot(portal.root);
|
|
1064
|
+
return () => {
|
|
1065
|
+
portal.cleanup();
|
|
1066
|
+
};
|
|
1067
|
+
}, []);
|
|
1068
|
+
useEffect(() => {
|
|
1069
|
+
const { checkout: checkoutStatus } = consumeSearchParams(["checkout", "product", "session_id"]);
|
|
1070
|
+
if (!checkoutStatus) return;
|
|
1071
|
+
scrollToBlock(SHOP_ANCHOR_ID);
|
|
1072
|
+
if (checkoutStatus === "success") {
|
|
1073
|
+
dispatch({ type: "setReturnNotice", notice: { kind: "success" } });
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
if (checkoutStatus === "cancelled") {
|
|
1077
|
+
dispatch({ type: "setReturnNotice", notice: { kind: "cancelled" } });
|
|
1078
|
+
}
|
|
1079
|
+
}, []);
|
|
1080
|
+
const submitCheckout = useCallback(async (confirmDuplicate = false) => {
|
|
1081
|
+
if (!checkout) return;
|
|
1082
|
+
const email = checkout.form.email.trim().toLowerCase();
|
|
1083
|
+
const name = checkout.form.name.trim();
|
|
1084
|
+
if (!email || !name) {
|
|
1085
|
+
dispatch({ type: "submitFailed", error: "Please enter your email and name" });
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
1089
|
+
dispatch({ type: "submitFailed", error: "Please enter a valid email address" });
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
dispatch({ type: "submitStarted" });
|
|
1093
|
+
try {
|
|
1094
|
+
const currentUrl = window.location.href;
|
|
1095
|
+
const successUrl = new URL(currentUrl);
|
|
1096
|
+
successUrl.searchParams.set("checkout", "success");
|
|
1097
|
+
successUrl.searchParams.set("product", checkout.item.product.id);
|
|
1098
|
+
const cancelUrl = new URL(currentUrl);
|
|
1099
|
+
cancelUrl.searchParams.set("checkout", "cancelled");
|
|
1100
|
+
const result = await api.createShopCheckout({
|
|
1101
|
+
productType: checkout.item.productType,
|
|
1102
|
+
productId: checkout.item.product.id,
|
|
1103
|
+
customerEmail: email,
|
|
1104
|
+
customerName: name,
|
|
1105
|
+
successUrl: successUrl.toString(),
|
|
1106
|
+
cancelUrl: cancelUrl.toString(),
|
|
1107
|
+
confirmDuplicate
|
|
1108
|
+
});
|
|
1109
|
+
if ("warning" in result && result.warning === "duplicate_membership") {
|
|
1110
|
+
dispatch({
|
|
1111
|
+
type: "submitDuplicateWarning",
|
|
1112
|
+
warning: result
|
|
1113
|
+
});
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
if ("bypassed" in result && result.bypassed) {
|
|
1117
|
+
window.location.href = successUrl.toString();
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if ("checkoutUrl" in result && result.checkoutUrl) {
|
|
1121
|
+
window.location.href = result.checkoutUrl;
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
throw new Error("No checkout URL returned");
|
|
1125
|
+
} catch (submitError) {
|
|
1126
|
+
dispatch({
|
|
1127
|
+
type: "submitFailed",
|
|
1128
|
+
error: submitError instanceof Error ? submitError.message : "An error occurred"
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
}, [api, checkout]);
|
|
1132
|
+
const handleFormSubmit = useCallback((event) => {
|
|
1133
|
+
event.preventDefault();
|
|
1134
|
+
submitCheckout(false);
|
|
1135
|
+
}, [submitCheckout]);
|
|
1136
|
+
const handleConfirmDuplicate = useCallback(() => {
|
|
1137
|
+
submitCheckout(true);
|
|
1138
|
+
}, [submitCheckout]);
|
|
1139
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1140
|
+
/* @__PURE__ */ jsx("div", { ref: shopRootRef, children: /* @__PURE__ */ jsx(
|
|
1141
|
+
PassesMembershipsSectionView,
|
|
1142
|
+
{
|
|
1143
|
+
display,
|
|
1144
|
+
beforeContent: /* @__PURE__ */ jsx(Fragment, { children: state.returnNotice ? /* @__PURE__ */ jsx(
|
|
1145
|
+
TerminalPanel,
|
|
1146
|
+
{
|
|
1147
|
+
tone: state.returnNotice.kind === "success" ? "success" : "warning",
|
|
1148
|
+
className: "shop__return",
|
|
1149
|
+
titleClassName: "shop__return-title",
|
|
1150
|
+
bodyClassName: "shop__return-text",
|
|
1151
|
+
icon: /* @__PURE__ */ jsx(StateIcon, { variant: state.returnNotice.kind === "success" ? "success" : "warning", children: state.returnNotice.kind === "success" ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CrossIcon, {}) }),
|
|
1152
|
+
title: state.returnNotice.kind === "success" ? "Payment successful" : "Payment cancelled",
|
|
1153
|
+
body: state.returnNotice.kind === "success" ? "Check your email for confirmation." : "You can try again whenever you\u2019re ready.",
|
|
1154
|
+
actions: /* @__PURE__ */ jsx(
|
|
1155
|
+
"button",
|
|
1156
|
+
{
|
|
1157
|
+
type: "button",
|
|
1158
|
+
className: themeButtonClassName({
|
|
1159
|
+
variant: "secondary",
|
|
1160
|
+
size: "sm",
|
|
1161
|
+
extraClassName: "shop__return-dismiss"
|
|
1162
|
+
}),
|
|
1163
|
+
onClick: () => dispatch({ type: "clearReturnNotice" }),
|
|
1164
|
+
children: "Dismiss"
|
|
1165
|
+
}
|
|
1166
|
+
)
|
|
1167
|
+
}
|
|
1168
|
+
) : null }),
|
|
1169
|
+
passCards: display.passes.map((passDisplay) => {
|
|
1170
|
+
const pass = hydration.passes.find((candidate) => candidate.id === passDisplay.id);
|
|
1171
|
+
if (!pass) return null;
|
|
1172
|
+
return /* @__PURE__ */ jsx(
|
|
1173
|
+
PassCardView,
|
|
1174
|
+
{
|
|
1175
|
+
display: passDisplay,
|
|
1176
|
+
action: /* @__PURE__ */ jsx(
|
|
1177
|
+
"button",
|
|
1178
|
+
{
|
|
1179
|
+
type: "button",
|
|
1180
|
+
className: themeButtonClassName({
|
|
1181
|
+
variant: "default",
|
|
1182
|
+
size: "md",
|
|
1183
|
+
extraClassName: "shop__cta"
|
|
1184
|
+
}),
|
|
1185
|
+
onClick: () => handleBuyPass(pass),
|
|
1186
|
+
children: passDisplay.actionLabel
|
|
1187
|
+
}
|
|
1188
|
+
),
|
|
1189
|
+
feedback: /* @__PURE__ */ jsx(
|
|
1190
|
+
ShopAddFeedback,
|
|
1191
|
+
{
|
|
1192
|
+
active: passAddFeedback.activeKey === pass.id,
|
|
1193
|
+
itemLabel: pass.name
|
|
1194
|
+
}
|
|
1195
|
+
)
|
|
1196
|
+
},
|
|
1197
|
+
pass.id
|
|
1198
|
+
);
|
|
1199
|
+
}),
|
|
1200
|
+
membershipCards: display.memberships.map((membershipDisplay) => {
|
|
1201
|
+
const membership = hydration.memberships.find((candidate) => candidate.id === membershipDisplay.id);
|
|
1202
|
+
if (!membership) return null;
|
|
1203
|
+
return /* @__PURE__ */ jsx(
|
|
1204
|
+
MembershipCardView,
|
|
1205
|
+
{
|
|
1206
|
+
display: membershipDisplay,
|
|
1207
|
+
action: /* @__PURE__ */ jsx(
|
|
1208
|
+
"button",
|
|
1209
|
+
{
|
|
1210
|
+
type: "button",
|
|
1211
|
+
className: themeButtonClassName({
|
|
1212
|
+
variant: "default",
|
|
1213
|
+
size: "md",
|
|
1214
|
+
extraClassName: "shop__cta"
|
|
1215
|
+
}),
|
|
1216
|
+
onClick: () => handleSubscribe(membership),
|
|
1217
|
+
children: membershipDisplay.actionLabel
|
|
1218
|
+
}
|
|
1219
|
+
)
|
|
1220
|
+
},
|
|
1221
|
+
membership.id
|
|
1222
|
+
);
|
|
1223
|
+
})
|
|
1224
|
+
}
|
|
1225
|
+
) }),
|
|
1226
|
+
checkout ? /* @__PURE__ */ jsx(
|
|
1227
|
+
ModalShell,
|
|
1228
|
+
{
|
|
1229
|
+
title: checkoutTitle,
|
|
1230
|
+
titleId: "shop-modal-title",
|
|
1231
|
+
onClose: closeCheckout,
|
|
1232
|
+
portal: true,
|
|
1233
|
+
portalRoot: modalPortalRoot,
|
|
1234
|
+
overlayClassName: "rb-modal-overlay",
|
|
1235
|
+
modalClassName: "rb-modal",
|
|
1236
|
+
headerClassName: "rb-modal__header",
|
|
1237
|
+
titleClassName: "rb-modal__title",
|
|
1238
|
+
closeButtonClassName: "rb-modal__close",
|
|
1239
|
+
closeIcon: /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }),
|
|
1240
|
+
children: /* @__PURE__ */ jsxs("div", { className: "rb-modal__body", children: [
|
|
1241
|
+
/* @__PURE__ */ jsxs("p", { className: "shop__modal-product", children: [
|
|
1242
|
+
checkout.item.product.name,
|
|
1243
|
+
" \u2014 ",
|
|
1244
|
+
formatCommercePrice(checkout.item.product.price, checkout.item.product.currency),
|
|
1245
|
+
checkout.item.productType === "membership" ? /* @__PURE__ */ jsx("span", { className: "shop__modal-interval", children: formatInterval(checkout.item.product.billingInterval) }) : null
|
|
1246
|
+
] }),
|
|
1247
|
+
checkout.duplicateWarning ? /* @__PURE__ */ jsxs(
|
|
1248
|
+
FeedbackMessage,
|
|
1249
|
+
{
|
|
1250
|
+
tone: "warning",
|
|
1251
|
+
className: "shop__modal-warning",
|
|
1252
|
+
role: "alert",
|
|
1253
|
+
children: [
|
|
1254
|
+
/* @__PURE__ */ jsx("p", { className: "shop__modal-warning-title", children: "You already have an active membership" }),
|
|
1255
|
+
/* @__PURE__ */ jsxs("p", { className: "shop__modal-warning-text", children: [
|
|
1256
|
+
"Your current membership: ",
|
|
1257
|
+
/* @__PURE__ */ jsx("strong", { children: checkout.duplicateWarning.existingMembership.name }),
|
|
1258
|
+
checkout.duplicateWarning.existingMembership.expiresAt ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1259
|
+
" (expires ",
|
|
1260
|
+
new Date(checkout.duplicateWarning.existingMembership.expiresAt).toLocaleDateString(),
|
|
1261
|
+
")"
|
|
1262
|
+
] }) : null
|
|
1263
|
+
] }),
|
|
1264
|
+
/* @__PURE__ */ jsxs(ActionRow, { className: "shop__modal-warning-actions", children: [
|
|
1265
|
+
/* @__PURE__ */ jsx(
|
|
1266
|
+
"button",
|
|
1267
|
+
{
|
|
1268
|
+
type: "button",
|
|
1269
|
+
className: themeButtonClassName({ variant: "secondary", size: "md" }),
|
|
1270
|
+
onClick: closeCheckout,
|
|
1271
|
+
children: "Cancel"
|
|
1272
|
+
}
|
|
1273
|
+
),
|
|
1274
|
+
/* @__PURE__ */ jsx(
|
|
1275
|
+
"button",
|
|
1276
|
+
{
|
|
1277
|
+
type: "button",
|
|
1278
|
+
className: themeButtonClassName({ variant: "default", size: "md" }),
|
|
1279
|
+
onClick: handleConfirmDuplicate,
|
|
1280
|
+
disabled: checkout.submitting,
|
|
1281
|
+
children: checkout.submitting ? "Processing..." : "Subscribe Anyway"
|
|
1282
|
+
}
|
|
1283
|
+
)
|
|
1284
|
+
] })
|
|
1285
|
+
]
|
|
1286
|
+
}
|
|
1287
|
+
) : /* @__PURE__ */ jsxs("form", { onSubmit: handleFormSubmit, className: "shop__modal-form", children: [
|
|
1288
|
+
/* @__PURE__ */ jsxs("div", { className: "shop__form-field", children: [
|
|
1289
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "shop-email", className: "form-label", children: "Email address" }),
|
|
1290
|
+
/* @__PURE__ */ jsx(
|
|
1291
|
+
"input",
|
|
1292
|
+
{
|
|
1293
|
+
ref: emailInputRef,
|
|
1294
|
+
type: "email",
|
|
1295
|
+
id: "shop-email",
|
|
1296
|
+
className: "form-input",
|
|
1297
|
+
value: checkout.form.email,
|
|
1298
|
+
onChange: (event) => dispatch({ type: "setCheckoutField", field: "email", value: event.target.value }),
|
|
1299
|
+
placeholder: "you@example.com",
|
|
1300
|
+
required: true,
|
|
1301
|
+
disabled: checkout.submitting
|
|
1302
|
+
}
|
|
1303
|
+
)
|
|
1304
|
+
] }),
|
|
1305
|
+
/* @__PURE__ */ jsxs("div", { className: "shop__form-field", children: [
|
|
1306
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "shop-name", className: "form-label", children: "Full name" }),
|
|
1307
|
+
/* @__PURE__ */ jsx(
|
|
1308
|
+
"input",
|
|
1309
|
+
{
|
|
1310
|
+
type: "text",
|
|
1311
|
+
id: "shop-name",
|
|
1312
|
+
className: "form-input",
|
|
1313
|
+
value: checkout.form.name,
|
|
1314
|
+
onChange: (event) => dispatch({ type: "setCheckoutField", field: "name", value: event.target.value }),
|
|
1315
|
+
placeholder: "Jane Smith",
|
|
1316
|
+
required: true,
|
|
1317
|
+
disabled: checkout.submitting
|
|
1318
|
+
}
|
|
1319
|
+
)
|
|
1320
|
+
] }),
|
|
1321
|
+
checkout.formError ? /* @__PURE__ */ jsx(
|
|
1322
|
+
FeedbackMessage,
|
|
1323
|
+
{
|
|
1324
|
+
tone: "error",
|
|
1325
|
+
className: "shop__modal-error",
|
|
1326
|
+
role: "alert",
|
|
1327
|
+
children: checkout.formError
|
|
1328
|
+
}
|
|
1329
|
+
) : null,
|
|
1330
|
+
/* @__PURE__ */ jsxs(ActionRow, { className: "shop__modal-actions", children: [
|
|
1331
|
+
/* @__PURE__ */ jsx(
|
|
1332
|
+
"button",
|
|
1333
|
+
{
|
|
1334
|
+
type: "button",
|
|
1335
|
+
className: themeButtonClassName({ variant: "secondary", size: "md" }),
|
|
1336
|
+
onClick: closeCheckout,
|
|
1337
|
+
disabled: checkout.submitting,
|
|
1338
|
+
children: "Cancel"
|
|
1339
|
+
}
|
|
1340
|
+
),
|
|
1341
|
+
/* @__PURE__ */ jsx(
|
|
1342
|
+
"button",
|
|
1343
|
+
{
|
|
1344
|
+
type: "submit",
|
|
1345
|
+
className: themeButtonClassName({ variant: "default", size: "md" }),
|
|
1346
|
+
disabled: checkout.submitting,
|
|
1347
|
+
children: checkout.submitting ? "Processing..." : "Continue to Payment"
|
|
1348
|
+
}
|
|
1349
|
+
)
|
|
1350
|
+
] })
|
|
1351
|
+
] })
|
|
1352
|
+
] })
|
|
1353
|
+
}
|
|
1354
|
+
) : null
|
|
1355
|
+
] });
|
|
1356
|
+
}
|
|
1357
|
+
function mountShopBlockIsland(root, props, Component, label) {
|
|
1358
|
+
const { siteId, apiBaseUrl, stage } = props;
|
|
1359
|
+
if (!siteId) {
|
|
1360
|
+
throw new Error(`[sdk:islands] ${label} requires siteId`);
|
|
1361
|
+
}
|
|
1362
|
+
if (!apiBaseUrl) {
|
|
1363
|
+
throw new Error(`[sdk:islands] ${label} requires apiBaseUrl`);
|
|
1364
|
+
}
|
|
1365
|
+
const apiConfig = {
|
|
1366
|
+
baseUrl: apiBaseUrl,
|
|
1367
|
+
auth: { type: "none" },
|
|
1368
|
+
siteId,
|
|
1369
|
+
isPreview: stage === "preview"
|
|
1370
|
+
};
|
|
1371
|
+
return mountReactIsland(root, ({ root: reactRoot }) => {
|
|
1372
|
+
reactRoot.render(
|
|
1373
|
+
/* @__PURE__ */ jsx(BlockApiProvider, { config: apiConfig, children: /* @__PURE__ */ jsx(Component, { ...props }) })
|
|
1374
|
+
);
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
export { CartClient, CheckoutClient, ProductDetailClient, ProductListClient, ShopClient, mountShopBlockIsland };
|