@patientos/website-kit 0.1.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/LICENSE +201 -0
- package/README.md +30 -0
- package/dist/checkout-block-CrO7OxI9.d.ts +121 -0
- package/dist/chunk-G3U6EXJM.js +1240 -0
- package/dist/chunk-JTZ6GYA7.js +30 -0
- package/dist/chunk-LCHW6XF4.js +64 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-Q4URDZAK.js +36 -0
- package/dist/chunk-QDO3SJWR.js +72 -0
- package/dist/chunk-THMT43MV.js +61 -0
- package/dist/chunk-TOPPLOH2.js +12229 -0
- package/dist/chunk-ZOF22TJA.js +19 -0
- package/dist/consult-room.d.ts +35 -0
- package/dist/consult-room.js +31540 -0
- package/dist/contrast.d.ts +63 -0
- package/dist/contrast.js +15 -0
- package/dist/index.d.ts +189 -0
- package/dist/index.js +484 -0
- package/dist/islands-impl.d.ts +68 -0
- package/dist/islands-impl.js +23 -0
- package/dist/islands-registry.d.ts +17 -0
- package/dist/islands-registry.js +14 -0
- package/dist/islands.d.ts +57 -0
- package/dist/islands.js +15 -0
- package/dist/patient-surface-theme.d.ts +22 -0
- package/dist/patient-surface-theme.js +7 -0
- package/dist/portal-account-EhOtLV5u.d.ts +113 -0
- package/dist/portal-account.client-CTget1-3.d.ts +169 -0
- package/dist/portal-booking-client.d.ts +232 -0
- package/dist/portal-booking-client.js +51 -0
- package/dist/script-sources.d.ts +11 -0
- package/dist/script-sources.js +17 -0
- package/dist/show-when.d.ts +70 -0
- package/dist/show-when.js +9 -0
- package/dist/website-kit.css +3580 -0
- package/package.json +120 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ISLANDS,
|
|
3
|
+
mountIslands
|
|
4
|
+
} from "./chunk-Q4URDZAK.js";
|
|
5
|
+
import {
|
|
6
|
+
PortalAccount,
|
|
7
|
+
PortalPanel,
|
|
8
|
+
getAppointmentTypes,
|
|
9
|
+
getClinic,
|
|
10
|
+
getPublicApi,
|
|
11
|
+
getServices,
|
|
12
|
+
getThemeTokens
|
|
13
|
+
} from "./chunk-TOPPLOH2.js";
|
|
14
|
+
import {
|
|
15
|
+
BPOINT_SCRIPT_ORIGIN,
|
|
16
|
+
BPOINT_SCRIPT_URL,
|
|
17
|
+
CONSULT_CHUNK_PATH,
|
|
18
|
+
THIRD_PARTY_SCRIPT_ORIGINS,
|
|
19
|
+
TURNSTILE_SCRIPT_ORIGIN,
|
|
20
|
+
TURNSTILE_SCRIPT_URL
|
|
21
|
+
} from "./chunk-ZOF22TJA.js";
|
|
22
|
+
import {
|
|
23
|
+
evaluateShowWhen,
|
|
24
|
+
visibleQuestions
|
|
25
|
+
} from "./chunk-QDO3SJWR.js";
|
|
26
|
+
import {
|
|
27
|
+
ISLAND_ATTR,
|
|
28
|
+
ISLAND_PROPS_ATTR,
|
|
29
|
+
Island,
|
|
30
|
+
mountIslandsWith,
|
|
31
|
+
readIslandProps
|
|
32
|
+
} from "./chunk-THMT43MV.js";
|
|
33
|
+
import "./chunk-G3U6EXJM.js";
|
|
34
|
+
import {
|
|
35
|
+
contrastRatio,
|
|
36
|
+
deriveForeground,
|
|
37
|
+
derivePrimaryPair,
|
|
38
|
+
parseHexColor,
|
|
39
|
+
relativeLuminance
|
|
40
|
+
} from "./chunk-LCHW6XF4.js";
|
|
41
|
+
import {
|
|
42
|
+
patientSurfaceTheme
|
|
43
|
+
} from "./chunk-JTZ6GYA7.js";
|
|
44
|
+
import "./chunk-MLKGABMK.js";
|
|
45
|
+
|
|
46
|
+
// src/tokens.tsx
|
|
47
|
+
import { jsx } from "react/jsx-runtime";
|
|
48
|
+
var defaultTheme = {
|
|
49
|
+
// palette
|
|
50
|
+
brand: "#1f6feb",
|
|
51
|
+
brandForeground: "#ffffff",
|
|
52
|
+
neutral: "#f4f6f8",
|
|
53
|
+
neutralForeground: "#1b2733",
|
|
54
|
+
accent: "#0fb39a",
|
|
55
|
+
background: "#ffffff",
|
|
56
|
+
foreground: "#1b2733",
|
|
57
|
+
muted: "#5b6b7a",
|
|
58
|
+
// type scale
|
|
59
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
60
|
+
fontFamilyHeading: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
61
|
+
fontSizeBase: "1rem",
|
|
62
|
+
fontSizeSm: "0.875rem",
|
|
63
|
+
fontSizeLg: "1.25rem",
|
|
64
|
+
fontSizeH2: "2rem",
|
|
65
|
+
fontSizeH1: "3rem",
|
|
66
|
+
// geometry
|
|
67
|
+
spacingUnit: "0.5rem",
|
|
68
|
+
radius: "0.625rem"
|
|
69
|
+
};
|
|
70
|
+
function themeToCssVars(theme) {
|
|
71
|
+
return {
|
|
72
|
+
"--sk-brand": theme.brand,
|
|
73
|
+
"--sk-brand-foreground": theme.brandForeground,
|
|
74
|
+
"--sk-neutral": theme.neutral,
|
|
75
|
+
"--sk-neutral-foreground": theme.neutralForeground,
|
|
76
|
+
"--sk-accent": theme.accent,
|
|
77
|
+
"--sk-background": theme.background,
|
|
78
|
+
"--sk-foreground": theme.foreground,
|
|
79
|
+
"--sk-muted": theme.muted,
|
|
80
|
+
"--sk-font": theme.fontFamily,
|
|
81
|
+
"--sk-font-heading": theme.fontFamilyHeading,
|
|
82
|
+
"--sk-font-size-base": theme.fontSizeBase,
|
|
83
|
+
"--sk-font-size-sm": theme.fontSizeSm,
|
|
84
|
+
"--sk-font-size-lg": theme.fontSizeLg,
|
|
85
|
+
"--sk-font-size-h2": theme.fontSizeH2,
|
|
86
|
+
"--sk-font-size-h1": theme.fontSizeH1,
|
|
87
|
+
"--sk-space": theme.spacingUnit,
|
|
88
|
+
"--sk-radius": theme.radius
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function resolveTheme(theme) {
|
|
92
|
+
const merged = theme ? { ...defaultTheme, ...theme } : defaultTheme;
|
|
93
|
+
return { ...merged, brandForeground: deriveForeground(merged.brand) };
|
|
94
|
+
}
|
|
95
|
+
function ThemeProvider({
|
|
96
|
+
theme,
|
|
97
|
+
children,
|
|
98
|
+
className,
|
|
99
|
+
...rest
|
|
100
|
+
}) {
|
|
101
|
+
const resolved = resolveTheme(theme);
|
|
102
|
+
const style = themeToCssVars(resolved);
|
|
103
|
+
return /* @__PURE__ */ jsx("div", { "data-sk-theme": "", "data-sk-root": "", className, style, ...rest, children });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/layout.tsx
|
|
107
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
108
|
+
function cx(...parts) {
|
|
109
|
+
return parts.filter(Boolean).join(" ");
|
|
110
|
+
}
|
|
111
|
+
function Container({
|
|
112
|
+
className,
|
|
113
|
+
children,
|
|
114
|
+
...rest
|
|
115
|
+
}) {
|
|
116
|
+
return /* @__PURE__ */ jsx2("div", { className: cx("sk-container", className), ...rest, children });
|
|
117
|
+
}
|
|
118
|
+
function Row({
|
|
119
|
+
className,
|
|
120
|
+
children,
|
|
121
|
+
...rest
|
|
122
|
+
}) {
|
|
123
|
+
return /* @__PURE__ */ jsx2("div", { className: cx("sk-row", className), ...rest, children });
|
|
124
|
+
}
|
|
125
|
+
function Stack({
|
|
126
|
+
className,
|
|
127
|
+
children,
|
|
128
|
+
...rest
|
|
129
|
+
}) {
|
|
130
|
+
return /* @__PURE__ */ jsx2("div", { className: cx("sk-stack", className), ...rest, children });
|
|
131
|
+
}
|
|
132
|
+
function WebsiteShell({
|
|
133
|
+
theme,
|
|
134
|
+
title,
|
|
135
|
+
children
|
|
136
|
+
}) {
|
|
137
|
+
const resolved = resolveTheme(theme);
|
|
138
|
+
const vars = themeToCssVars(resolved);
|
|
139
|
+
const rootCss = ":root{" + Object.entries(vars).map(([k, v]) => `${k}:${v};`).join("") + "}";
|
|
140
|
+
return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
|
|
141
|
+
/* @__PURE__ */ jsxs("head", { children: [
|
|
142
|
+
/* @__PURE__ */ jsx2("meta", { charSet: "utf-8" }),
|
|
143
|
+
/* @__PURE__ */ jsx2("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
|
|
144
|
+
title ? /* @__PURE__ */ jsx2("title", { children: title }) : null,
|
|
145
|
+
/* @__PURE__ */ jsx2("link", { rel: "stylesheet", href: "/website-kit.css" }),
|
|
146
|
+
/* @__PURE__ */ jsx2("style", { dangerouslySetInnerHTML: { __html: guardStyleCss(rootCss) } }),
|
|
147
|
+
/* @__PURE__ */ jsx2("script", { type: "module", src: "/main.js" })
|
|
148
|
+
] }),
|
|
149
|
+
/* @__PURE__ */ jsx2("body", { children: /* @__PURE__ */ jsx2("div", { "data-sk-theme": "", "data-sk-root": "", children }) })
|
|
150
|
+
] });
|
|
151
|
+
}
|
|
152
|
+
function guardStyleCss(css) {
|
|
153
|
+
return css.replace(/<\/(style)/gi, "<\\/$1");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/components.tsx
|
|
157
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
158
|
+
function cx2(...parts) {
|
|
159
|
+
return parts.filter(Boolean).join(" ");
|
|
160
|
+
}
|
|
161
|
+
function Button({
|
|
162
|
+
href,
|
|
163
|
+
variant = "solid",
|
|
164
|
+
className,
|
|
165
|
+
children,
|
|
166
|
+
target,
|
|
167
|
+
rel,
|
|
168
|
+
...rest
|
|
169
|
+
}) {
|
|
170
|
+
const cls = cx2("sk-button", `sk-button--${variant}`, className);
|
|
171
|
+
if (href) {
|
|
172
|
+
return /* @__PURE__ */ jsx3(
|
|
173
|
+
"a",
|
|
174
|
+
{
|
|
175
|
+
className: cls,
|
|
176
|
+
href,
|
|
177
|
+
target,
|
|
178
|
+
rel,
|
|
179
|
+
...rest,
|
|
180
|
+
children
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return /* @__PURE__ */ jsx3("button", { className: cls, ...rest, children });
|
|
185
|
+
}
|
|
186
|
+
function Hero({
|
|
187
|
+
heading,
|
|
188
|
+
subheading,
|
|
189
|
+
image,
|
|
190
|
+
cta,
|
|
191
|
+
className,
|
|
192
|
+
style,
|
|
193
|
+
...rest
|
|
194
|
+
}) {
|
|
195
|
+
const mergedStyle = image ? { ...style, ["--sk-hero-image"]: `url("${image}")` } : style ?? {};
|
|
196
|
+
return /* @__PURE__ */ jsx3(
|
|
197
|
+
"section",
|
|
198
|
+
{
|
|
199
|
+
className: cx2("sk-hero", image && "sk-hero--image", className),
|
|
200
|
+
style: mergedStyle,
|
|
201
|
+
...rest,
|
|
202
|
+
children: /* @__PURE__ */ jsxs2("div", { className: "sk-hero__inner", children: [
|
|
203
|
+
/* @__PURE__ */ jsx3("h1", { className: "sk-hero__heading", children: heading }),
|
|
204
|
+
subheading ? /* @__PURE__ */ jsx3("p", { className: "sk-hero__subheading", children: subheading }) : null,
|
|
205
|
+
cta ? /* @__PURE__ */ jsx3("div", { className: "sk-hero__cta", children: cta }) : null
|
|
206
|
+
] })
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
function Section({
|
|
211
|
+
title,
|
|
212
|
+
intro,
|
|
213
|
+
children,
|
|
214
|
+
className,
|
|
215
|
+
...rest
|
|
216
|
+
}) {
|
|
217
|
+
return /* @__PURE__ */ jsx3("section", { className: cx2("sk-section", className), ...rest, children: /* @__PURE__ */ jsxs2("div", { className: "sk-section__inner", children: [
|
|
218
|
+
title ? /* @__PURE__ */ jsx3("h2", { className: "sk-section__title", children: title }) : null,
|
|
219
|
+
intro ? /* @__PURE__ */ jsx3("p", { className: "sk-section__intro", children: intro }) : null,
|
|
220
|
+
children
|
|
221
|
+
] }) });
|
|
222
|
+
}
|
|
223
|
+
function ServiceGrid({
|
|
224
|
+
services,
|
|
225
|
+
className,
|
|
226
|
+
...rest
|
|
227
|
+
}) {
|
|
228
|
+
return /* @__PURE__ */ jsx3("ul", { className: cx2("sk-service-grid", className), ...rest, children: services.map((s, i) => /* @__PURE__ */ jsxs2("li", { className: "sk-service-grid__item", children: [
|
|
229
|
+
s.icon ? /* @__PURE__ */ jsx3("img", { className: "sk-service-grid__icon", src: s.icon, alt: "", "aria-hidden": "true" }) : null,
|
|
230
|
+
/* @__PURE__ */ jsx3("h3", { className: "sk-service-grid__title", children: s.title }),
|
|
231
|
+
/* @__PURE__ */ jsx3("p", { className: "sk-service-grid__desc", children: s.description })
|
|
232
|
+
] }, `${s.title}-${i}`)) });
|
|
233
|
+
}
|
|
234
|
+
function TeamGrid({
|
|
235
|
+
members,
|
|
236
|
+
className,
|
|
237
|
+
...rest
|
|
238
|
+
}) {
|
|
239
|
+
return /* @__PURE__ */ jsx3("ul", { className: cx2("sk-team-grid", className), ...rest, children: members.map((m, i) => /* @__PURE__ */ jsxs2("li", { className: "sk-team-grid__item", children: [
|
|
240
|
+
m.photo ? /* @__PURE__ */ jsx3(
|
|
241
|
+
"img",
|
|
242
|
+
{
|
|
243
|
+
className: "sk-team-grid__photo",
|
|
244
|
+
src: m.photo,
|
|
245
|
+
alt: `Photo of ${m.name}`
|
|
246
|
+
}
|
|
247
|
+
) : /* @__PURE__ */ jsx3("div", { className: "sk-team-grid__photo sk-team-grid__photo--placeholder", "aria-hidden": "true" }),
|
|
248
|
+
/* @__PURE__ */ jsx3("p", { className: "sk-team-grid__name", children: m.name }),
|
|
249
|
+
/* @__PURE__ */ jsx3("p", { className: "sk-team-grid__role", children: m.role })
|
|
250
|
+
] }, `${m.name}-${i}`)) });
|
|
251
|
+
}
|
|
252
|
+
function Hours({ rows, className, ...rest }) {
|
|
253
|
+
return /* @__PURE__ */ jsx3("dl", { className: cx2("sk-hours", className), ...rest, children: rows.map((r, i) => /* @__PURE__ */ jsxs2("div", { className: "sk-hours__row", children: [
|
|
254
|
+
/* @__PURE__ */ jsx3("dt", { className: "sk-hours__day", children: r.day }),
|
|
255
|
+
/* @__PURE__ */ jsx3("dd", { className: "sk-hours__time", children: r.hours })
|
|
256
|
+
] }, `${r.day}-${i}`)) });
|
|
257
|
+
}
|
|
258
|
+
function MapEmbed({
|
|
259
|
+
src,
|
|
260
|
+
address,
|
|
261
|
+
height = "320px",
|
|
262
|
+
title = "Map",
|
|
263
|
+
className,
|
|
264
|
+
style,
|
|
265
|
+
...rest
|
|
266
|
+
}) {
|
|
267
|
+
const resolvedSrc = src ?? (address ? `https://www.google.com/maps?q=${encodeURIComponent(address)}&output=embed` : void 0);
|
|
268
|
+
return /* @__PURE__ */ jsx3(
|
|
269
|
+
"div",
|
|
270
|
+
{
|
|
271
|
+
className: cx2("sk-map", className),
|
|
272
|
+
style: { ...style, ["--sk-map-height"]: height },
|
|
273
|
+
...rest,
|
|
274
|
+
children: resolvedSrc ? /* @__PURE__ */ jsx3(
|
|
275
|
+
"iframe",
|
|
276
|
+
{
|
|
277
|
+
className: "sk-map__frame",
|
|
278
|
+
src: resolvedSrc,
|
|
279
|
+
title,
|
|
280
|
+
loading: "lazy",
|
|
281
|
+
referrerPolicy: "no-referrer-when-downgrade"
|
|
282
|
+
}
|
|
283
|
+
) : null
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
function FAQ({ items, className, ...rest }) {
|
|
288
|
+
return /* @__PURE__ */ jsx3("div", { className: cx2("sk-faq", className), ...rest, children: items.map((it, i) => /* @__PURE__ */ jsxs2("details", { className: "sk-faq__item", children: [
|
|
289
|
+
/* @__PURE__ */ jsx3("summary", { className: "sk-faq__q", children: it.q }),
|
|
290
|
+
/* @__PURE__ */ jsx3("div", { className: "sk-faq__a", children: it.a })
|
|
291
|
+
] }, `${it.q}-${i}`)) });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/certificate-funnel.tsx
|
|
295
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
296
|
+
function CertificateFunnel({
|
|
297
|
+
serviceKeys,
|
|
298
|
+
serviceKey,
|
|
299
|
+
serviceCopy,
|
|
300
|
+
heading,
|
|
301
|
+
className
|
|
302
|
+
}) {
|
|
303
|
+
const publicApi = getPublicApi();
|
|
304
|
+
const clinic = getClinic();
|
|
305
|
+
const theme = patientSurfaceTheme(getThemeTokens());
|
|
306
|
+
const snapshotServices = getServices().filter((s) => !!s.key).map((s) => ({ key: s.key, label: serviceCopy?.[s.key] ?? s.label }));
|
|
307
|
+
const services = serviceKeys ? serviceKeys.map((k) => snapshotServices.find((s) => s.key === k)).filter((s) => !!s) : snapshotServices;
|
|
308
|
+
const offered = serviceKey ? services.filter((s) => s.key === serviceKey) : services;
|
|
309
|
+
const title = heading ?? "Medical certificates";
|
|
310
|
+
return /* @__PURE__ */ jsxs3(
|
|
311
|
+
Island,
|
|
312
|
+
{
|
|
313
|
+
name: "certificate-funnel",
|
|
314
|
+
props: {
|
|
315
|
+
serviceKey,
|
|
316
|
+
heading: title,
|
|
317
|
+
publicApi,
|
|
318
|
+
services: offered,
|
|
319
|
+
clinicName: clinic.name,
|
|
320
|
+
clinicPhone: clinic.phone
|
|
321
|
+
},
|
|
322
|
+
className: ["sk-certificate-funnel", className].filter(Boolean).join(" "),
|
|
323
|
+
style: themeStyle(theme),
|
|
324
|
+
children: [
|
|
325
|
+
/* @__PURE__ */ jsx4("h2", { className: "sk-certificate-funnel__heading", children: title }),
|
|
326
|
+
offered.length > 0 && /* @__PURE__ */ jsx4("ul", { className: "sk-certificate-funnel__services", children: offered.map((s) => /* @__PURE__ */ jsx4("li", { children: s.label }, s.key)) }),
|
|
327
|
+
/* @__PURE__ */ jsxs3("p", { className: "sk-certificate-funnel__placeholder", children: [
|
|
328
|
+
"Request one online.",
|
|
329
|
+
" ",
|
|
330
|
+
clinic.phone ? `Book by phone: ${clinic.phone}` : "Or call the clinic to book."
|
|
331
|
+
] })
|
|
332
|
+
]
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
function themeStyle(theme) {
|
|
337
|
+
const style = {};
|
|
338
|
+
if (theme.primary) {
|
|
339
|
+
const { bg, fg } = derivePrimaryPair(theme.primary);
|
|
340
|
+
style["--sk-island-primary"] = bg;
|
|
341
|
+
style["--sk-island-primary-fg"] = fg;
|
|
342
|
+
}
|
|
343
|
+
if (theme.accent) style["--sk-island-accent"] = theme.accent;
|
|
344
|
+
if (theme.radius) style["--sk-island-radius"] = theme.radius;
|
|
345
|
+
if (theme.fontFamily) style["--sk-island-font"] = theme.fontFamily;
|
|
346
|
+
return style;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/booking-block.tsx
|
|
350
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
351
|
+
function BookingBlock({
|
|
352
|
+
appointmentTypeId,
|
|
353
|
+
practitionerId,
|
|
354
|
+
appointmentTypeIds,
|
|
355
|
+
heading,
|
|
356
|
+
className
|
|
357
|
+
}) {
|
|
358
|
+
const publicApi = getPublicApi();
|
|
359
|
+
const clinic = getClinic();
|
|
360
|
+
const theme = patientSurfaceTheme(getThemeTokens());
|
|
361
|
+
const all = getAppointmentTypes().filter((t) => !!t.id).map((t) => ({
|
|
362
|
+
id: t.id,
|
|
363
|
+
label: t.label,
|
|
364
|
+
durationMinutes: t.durationMinutes,
|
|
365
|
+
modality: t.modality
|
|
366
|
+
}));
|
|
367
|
+
const filtered = appointmentTypeIds ? appointmentTypeIds.map((id) => all.find((t) => t.id === id)).filter((t) => !!t) : all;
|
|
368
|
+
const offered = appointmentTypeId ? filtered.filter((t) => t.id === appointmentTypeId) : filtered;
|
|
369
|
+
const title = heading ?? "Book an appointment";
|
|
370
|
+
return /* @__PURE__ */ jsxs4(
|
|
371
|
+
Island,
|
|
372
|
+
{
|
|
373
|
+
name: "booking",
|
|
374
|
+
props: {
|
|
375
|
+
appointmentTypeId,
|
|
376
|
+
practitionerId,
|
|
377
|
+
heading: title,
|
|
378
|
+
publicApi,
|
|
379
|
+
appointmentTypes: offered,
|
|
380
|
+
clinicName: clinic.name,
|
|
381
|
+
clinicPhone: clinic.phone
|
|
382
|
+
},
|
|
383
|
+
className: ["sk-booking", className].filter(Boolean).join(" "),
|
|
384
|
+
style: themeStyle(theme),
|
|
385
|
+
children: [
|
|
386
|
+
/* @__PURE__ */ jsx5("h2", { className: "sk-booking__heading", children: title }),
|
|
387
|
+
offered.length > 0 && /* @__PURE__ */ jsx5("ul", { className: "sk-booking__types", children: offered.map((t) => /* @__PURE__ */ jsxs4("li", { children: [
|
|
388
|
+
t.label,
|
|
389
|
+
t.durationMinutes ? ` \xB7 ${t.durationMinutes} min` : ""
|
|
390
|
+
] }, t.id)) }),
|
|
391
|
+
/* @__PURE__ */ jsx5("p", { className: "sk-booking__placeholder", children: clinic.phone ? `Book online, or call the clinic on ${clinic.phone}.` : "Book online, or call the clinic." })
|
|
392
|
+
]
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/store-block.tsx
|
|
398
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
399
|
+
function Store({
|
|
400
|
+
categoryHandle,
|
|
401
|
+
title,
|
|
402
|
+
columns,
|
|
403
|
+
className
|
|
404
|
+
}) {
|
|
405
|
+
return /* @__PURE__ */ jsxs5(
|
|
406
|
+
Island,
|
|
407
|
+
{
|
|
408
|
+
name: "store",
|
|
409
|
+
props: { categoryHandle, title, columns },
|
|
410
|
+
className: ["sk-store", className].filter(Boolean).join(" "),
|
|
411
|
+
children: [
|
|
412
|
+
/* @__PURE__ */ jsx6("h2", { className: "sk-store__heading", children: title ?? "Shop" }),
|
|
413
|
+
/* @__PURE__ */ jsx6("p", { className: "sk-store__placeholder", children: "Our online shop needs JavaScript. Please contact the clinic to order." })
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/cart-block.tsx
|
|
420
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
421
|
+
function Cart({ className }) {
|
|
422
|
+
return /* @__PURE__ */ jsxs6(Island, { name: "cart", className: ["sk-cart", className].filter(Boolean).join(" "), children: [
|
|
423
|
+
/* @__PURE__ */ jsx7("h2", { className: "sk-cart__heading", children: "Your cart" }),
|
|
424
|
+
/* @__PURE__ */ jsx7("p", { className: "sk-cart__placeholder", children: "Your cart needs JavaScript to load." })
|
|
425
|
+
] });
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/checkout-block.tsx
|
|
429
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
430
|
+
function Checkout({ className }) {
|
|
431
|
+
return /* @__PURE__ */ jsxs7(Island, { name: "checkout", className: ["sk-checkout", className].filter(Boolean).join(" "), children: [
|
|
432
|
+
/* @__PURE__ */ jsx8("h2", { className: "sk-checkout__heading", children: "Checkout" }),
|
|
433
|
+
/* @__PURE__ */ jsx8("p", { className: "sk-checkout__placeholder", children: "Checkout needs JavaScript to load." })
|
|
434
|
+
] });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/index.ts
|
|
438
|
+
var WEBSITE_KIT_VERSION = "0.1.0";
|
|
439
|
+
export {
|
|
440
|
+
BPOINT_SCRIPT_ORIGIN,
|
|
441
|
+
BPOINT_SCRIPT_URL,
|
|
442
|
+
BookingBlock,
|
|
443
|
+
Button,
|
|
444
|
+
CONSULT_CHUNK_PATH,
|
|
445
|
+
Cart,
|
|
446
|
+
CertificateFunnel,
|
|
447
|
+
Checkout,
|
|
448
|
+
Container,
|
|
449
|
+
FAQ,
|
|
450
|
+
Hero,
|
|
451
|
+
Hours,
|
|
452
|
+
ISLANDS,
|
|
453
|
+
ISLAND_ATTR,
|
|
454
|
+
ISLAND_PROPS_ATTR,
|
|
455
|
+
Island,
|
|
456
|
+
MapEmbed,
|
|
457
|
+
PortalAccount,
|
|
458
|
+
PortalPanel,
|
|
459
|
+
Row,
|
|
460
|
+
Section,
|
|
461
|
+
ServiceGrid,
|
|
462
|
+
Stack,
|
|
463
|
+
Store,
|
|
464
|
+
THIRD_PARTY_SCRIPT_ORIGINS,
|
|
465
|
+
TURNSTILE_SCRIPT_ORIGIN,
|
|
466
|
+
TURNSTILE_SCRIPT_URL,
|
|
467
|
+
TeamGrid,
|
|
468
|
+
ThemeProvider,
|
|
469
|
+
WEBSITE_KIT_VERSION,
|
|
470
|
+
WebsiteShell,
|
|
471
|
+
contrastRatio,
|
|
472
|
+
defaultTheme,
|
|
473
|
+
deriveForeground,
|
|
474
|
+
evaluateShowWhen,
|
|
475
|
+
mountIslands,
|
|
476
|
+
mountIslandsWith,
|
|
477
|
+
parseHexColor,
|
|
478
|
+
patientSurfaceTheme,
|
|
479
|
+
readIslandProps,
|
|
480
|
+
relativeLuminance,
|
|
481
|
+
resolveTheme,
|
|
482
|
+
themeToCssVars,
|
|
483
|
+
visibleQuestions
|
|
484
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { b as BookingBlockProps, h as CertificateFunnelProps, n as StoreProps, e as CartProps, k as CheckoutProps } from './checkout-block-CrO7OxI9.js';
|
|
3
|
+
import { f as PortalPanelProps } from './portal-account-EhOtLV5u.js';
|
|
4
|
+
import { F as FetchLike } from './portal-account.client-CTget1-3.js';
|
|
5
|
+
export { P as PortalAccountClient } from './portal-account.client-CTget1-3.js';
|
|
6
|
+
|
|
7
|
+
declare function BookingBlockClient(props: BookingBlockProps): React.ReactElement;
|
|
8
|
+
|
|
9
|
+
declare function CertificateFunnelClient(props: CertificateFunnelProps): React.ReactElement;
|
|
10
|
+
|
|
11
|
+
type SummaryItem = {
|
|
12
|
+
id: string;
|
|
13
|
+
title?: string | null;
|
|
14
|
+
status?: string | null;
|
|
15
|
+
/** appointments */
|
|
16
|
+
start?: string | null;
|
|
17
|
+
/** documents */
|
|
18
|
+
date?: string | null;
|
|
19
|
+
/** orders */
|
|
20
|
+
placedAt?: string | null;
|
|
21
|
+
total?: string | null;
|
|
22
|
+
currency?: string | null;
|
|
23
|
+
locationName?: string | null;
|
|
24
|
+
practitionerName?: string | null;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Everything the panel can be showing. A discriminated union, derived once by
|
|
28
|
+
* `loadPortalPanel`, so the render is a pure function of state — and so the whole
|
|
29
|
+
* behaviour is testable without a DOM.
|
|
30
|
+
*/
|
|
31
|
+
type PortalPanelState = {
|
|
32
|
+
kind: 'loading';
|
|
33
|
+
}
|
|
34
|
+
/** No patient session on this host — show the inline magic-link ramp. */
|
|
35
|
+
| {
|
|
36
|
+
kind: 'signed-out';
|
|
37
|
+
}
|
|
38
|
+
/** A link was requested; we never say whether the address matched an account. */
|
|
39
|
+
| {
|
|
40
|
+
kind: 'link-sent';
|
|
41
|
+
email: string;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'signed-in';
|
|
44
|
+
givenName: string | null;
|
|
45
|
+
items: SummaryItem[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The panel cannot answer here: no portal on this host (404), the surface is off, or
|
|
49
|
+
* the network failed. Degrades to a plain link out — never an error dump.
|
|
50
|
+
*/
|
|
51
|
+
| {
|
|
52
|
+
kind: 'link-out';
|
|
53
|
+
};
|
|
54
|
+
type PortalPanelClientProps = PortalPanelProps & {
|
|
55
|
+
/** Test seam: the initial state, so the view is renderable without effects. */
|
|
56
|
+
initialState?: PortalPanelState;
|
|
57
|
+
/** Test seam: the fetch used for whoami/summary/magic-link. Defaults to global. */
|
|
58
|
+
fetchImpl?: FetchLike;
|
|
59
|
+
};
|
|
60
|
+
declare function PortalPanelClient({ surface, portalUrl, initialState, fetchImpl, }: PortalPanelClientProps): React.ReactElement;
|
|
61
|
+
|
|
62
|
+
declare function StoreClient({ categoryHandle, title, columns }: StoreProps): React.ReactElement;
|
|
63
|
+
|
|
64
|
+
declare function CartClient(_props: CartProps): React.ReactElement;
|
|
65
|
+
|
|
66
|
+
declare function CheckoutClient(_props: CheckoutProps): React.ReactElement;
|
|
67
|
+
|
|
68
|
+
export { BookingBlockClient, CartClient, CertificateFunnelClient, CheckoutClient, PortalPanelClient, StoreClient };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BookingBlockClient,
|
|
3
|
+
CartClient,
|
|
4
|
+
CertificateFunnelClient,
|
|
5
|
+
CheckoutClient,
|
|
6
|
+
PortalAccountClient,
|
|
7
|
+
PortalPanelClient,
|
|
8
|
+
StoreClient
|
|
9
|
+
} from "./chunk-TOPPLOH2.js";
|
|
10
|
+
import "./chunk-ZOF22TJA.js";
|
|
11
|
+
import "./chunk-QDO3SJWR.js";
|
|
12
|
+
import "./chunk-THMT43MV.js";
|
|
13
|
+
import "./chunk-G3U6EXJM.js";
|
|
14
|
+
import "./chunk-MLKGABMK.js";
|
|
15
|
+
export {
|
|
16
|
+
BookingBlockClient,
|
|
17
|
+
CartClient,
|
|
18
|
+
CertificateFunnelClient,
|
|
19
|
+
CheckoutClient,
|
|
20
|
+
PortalAccountClient,
|
|
21
|
+
PortalPanelClient,
|
|
22
|
+
StoreClient
|
|
23
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { IslandRegistry } from './islands.js';
|
|
3
|
+
|
|
4
|
+
/** island name → implementation. THE allow-list. */
|
|
5
|
+
declare const ISLANDS: IslandRegistry;
|
|
6
|
+
type IslandName = keyof typeof ISLANDS;
|
|
7
|
+
/**
|
|
8
|
+
* Mount every island marker in `root` against the WHOLE registry.
|
|
9
|
+
*
|
|
10
|
+
* A convenience binding of `mountIslandsWith` for callers that want the full set (the
|
|
11
|
+
* app, tests). Published sites use the bundler's pruned per-site registry instead.
|
|
12
|
+
*/
|
|
13
|
+
declare function mountIslands(root: ParentNode, createRoot: (el: Element) => {
|
|
14
|
+
render: (node: React.ReactNode) => void;
|
|
15
|
+
}): number;
|
|
16
|
+
|
|
17
|
+
export { ISLANDS, type IslandName, mountIslands };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ISLANDS,
|
|
3
|
+
mountIslands
|
|
4
|
+
} from "./chunk-Q4URDZAK.js";
|
|
5
|
+
import "./chunk-TOPPLOH2.js";
|
|
6
|
+
import "./chunk-ZOF22TJA.js";
|
|
7
|
+
import "./chunk-QDO3SJWR.js";
|
|
8
|
+
import "./chunk-THMT43MV.js";
|
|
9
|
+
import "./chunk-G3U6EXJM.js";
|
|
10
|
+
import "./chunk-MLKGABMK.js";
|
|
11
|
+
export {
|
|
12
|
+
ISLANDS,
|
|
13
|
+
mountIslands
|
|
14
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/** Attribute carrying the island name on a build-emitted mount point. */
|
|
4
|
+
declare const ISLAND_ATTR = "data-pos-island";
|
|
5
|
+
/** Attribute carrying the island's JSON-serialised props. */
|
|
6
|
+
declare const ISLAND_PROPS_ATTR = "data-pos-island-props";
|
|
7
|
+
/** name → implementation. The per-site registries the bundler emits have this shape. */
|
|
8
|
+
type IslandRegistry = Record<string, React.ComponentType<never>>;
|
|
9
|
+
interface IslandProps {
|
|
10
|
+
/** Registry key. Must exist in the mounting registry or the marker mounts nothing. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** JSON-safe configuration handed to the implementation at mount time. */
|
|
13
|
+
props?: Record<string, unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* Static content rendered INSIDE the mount point at build time. It ships in the
|
|
16
|
+
* prerendered HTML and is replaced when the island mounts, so it is the right
|
|
17
|
+
* home for a no-JS fallback ("call the clinic to book") or a skeleton — the page
|
|
18
|
+
* is SEO-indexed and read by clients that never run the island.
|
|
19
|
+
*/
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Inline style on the mount point, resolved at BUILD time. Its only sanctioned use
|
|
24
|
+
* is the patient-surface theme vars (`patientSurfaceTheme`): the island's CSS reads
|
|
25
|
+
* them off its own mount point, so the clinic's brand reaches our components without
|
|
26
|
+
* the clinic being able to restyle them. Not a general styling hook.
|
|
27
|
+
*/
|
|
28
|
+
style?: React.CSSProperties;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The build-time half of an island. Renders an empty, inert mount point.
|
|
32
|
+
*
|
|
33
|
+
* Clinic pages never use this directly — each island exports its own thin wrapper
|
|
34
|
+
* (e.g. `BookingBlock`) so the clinic writes a named component with typed props.
|
|
35
|
+
*/
|
|
36
|
+
declare function Island({ name, props, children, className, style, }: IslandProps): React.ReactElement;
|
|
37
|
+
/** Parse a marker's props attribute. A malformed value yields `{}`, never a throw. */
|
|
38
|
+
declare function readIslandProps(el: Element): Record<string, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Mount every island marker in `root` using the given registry. Called by the
|
|
41
|
+
* bundler-generated client entry; this is the entire client-side lifecycle of a
|
|
42
|
+
* published clinic site.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately TOTAL: an unknown island name or a bad props blob is skipped with a
|
|
45
|
+
* console warning, never thrown. A published brochure must not white-screen because one
|
|
46
|
+
* island was renamed or because this build's registry was pruned to the islands its
|
|
47
|
+
* pages mount — the rest of the page is static HTML that is already correct on screen.
|
|
48
|
+
*
|
|
49
|
+
* `createRoot` (not `hydrateRoot`): the marker ships EMPTY of island output, so there is
|
|
50
|
+
* no server-rendered tree to reconcile against. Any `children` fallback is replaced on
|
|
51
|
+
* mount, which is the intent.
|
|
52
|
+
*/
|
|
53
|
+
declare function mountIslandsWith(registry: IslandRegistry, root: ParentNode, createRoot: (el: Element) => {
|
|
54
|
+
render: (node: React.ReactNode) => void;
|
|
55
|
+
}): number;
|
|
56
|
+
|
|
57
|
+
export { ISLAND_ATTR, ISLAND_PROPS_ATTR, Island, type IslandProps, type IslandRegistry, mountIslandsWith, readIslandProps };
|
package/dist/islands.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ISLAND_ATTR,
|
|
3
|
+
ISLAND_PROPS_ATTR,
|
|
4
|
+
Island,
|
|
5
|
+
mountIslandsWith,
|
|
6
|
+
readIslandProps
|
|
7
|
+
} from "./chunk-THMT43MV.js";
|
|
8
|
+
import "./chunk-MLKGABMK.js";
|
|
9
|
+
export {
|
|
10
|
+
ISLAND_ATTR,
|
|
11
|
+
ISLAND_PROPS_ATTR,
|
|
12
|
+
Island,
|
|
13
|
+
mountIslandsWith,
|
|
14
|
+
readIslandProps
|
|
15
|
+
};
|