@ohhwells/bridge 0.1.18 → 0.1.19

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/index.cjs CHANGED
@@ -32,6 +32,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
34
  OhhwellsBridge: () => OhhwellsBridge,
35
+ SchedulingWidget: () => SchedulingWidget,
35
36
  Toggle: () => Toggle,
36
37
  ToggleGroup: () => ToggleGroup,
37
38
  ToggleGroupItem: () => ToggleGroupItem,
@@ -40,10 +41,428 @@ __export(index_exports, {
40
41
  module.exports = __toCommonJS(index_exports);
41
42
 
42
43
  // src/OhhwellsBridge.tsx
44
+ var import_react2 = __toESM(require("react"), 1);
45
+ var import_client = require("react-dom/client");
46
+
47
+ // src/ui/SchedulingWidget.tsx
43
48
  var import_react = __toESM(require("react"), 1);
49
+ var import_jsx_runtime = require("react/jsx-runtime");
50
+ var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
51
+ var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
52
+ function getApiDomain() {
53
+ const h = window.location.hostname;
54
+ for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
55
+ if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
56
+ }
57
+ return h;
58
+ }
59
+ function classRunsOnDate(cls, date, type) {
60
+ if (type === "FIXED") {
61
+ const d = String(date.getDate()).padStart(2, "0");
62
+ const m = String(date.getMonth() + 1).padStart(2, "0");
63
+ return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
64
+ }
65
+ return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
66
+ }
67
+ function formatClassTime(cls) {
68
+ let h = parseInt(cls.startTime.hour, 10);
69
+ const m = parseInt(cls.startTime.minutes, 10);
70
+ if (cls.startTime.time === "PM" && h !== 12) h += 12;
71
+ if (cls.startTime.time === "AM" && h === 12) h = 0;
72
+ const endTotal = h * 60 + m + cls.duration;
73
+ const eh = Math.floor(endTotal / 60) % 24;
74
+ const em = endTotal % 60;
75
+ return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
76
+ }
77
+ function getBookingsOnDate(cls, date) {
78
+ if (!cls.bookings?.length) return 0;
79
+ const ds = date.toISOString().split("T")[0];
80
+ return cls.bookings.filter((b) => {
81
+ try {
82
+ return new Date(b.classDate).toISOString().split("T")[0] === ds;
83
+ } catch {
84
+ return false;
85
+ }
86
+ }).length;
87
+ }
88
+ function buildTimezoneLabel(tz) {
89
+ try {
90
+ const now = /* @__PURE__ */ new Date();
91
+ const timeStr = new Intl.DateTimeFormat("en-US", {
92
+ hour: "2-digit",
93
+ minute: "2-digit",
94
+ hour12: false,
95
+ timeZone: tz
96
+ }).format(now);
97
+ const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
98
+ const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
99
+ return `${timeStr} (${offsetPart}) ${city} time`;
100
+ } catch {
101
+ return tz;
102
+ }
103
+ }
104
+ function EmptyState({ inEditor }) {
105
+ const handleAddSchedule = () => {
106
+ if (inEditor) {
107
+ window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
108
+ }
109
+ };
110
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
112
+ "svg",
113
+ {
114
+ width: "20",
115
+ height: "20",
116
+ viewBox: "0 0 24 24",
117
+ fill: "none",
118
+ stroke: "var(--color-accent, #A89B83)",
119
+ strokeWidth: "1.5",
120
+ strokeLinecap: "round",
121
+ strokeLinejoin: "round",
122
+ children: [
123
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
129
+ ]
130
+ }
131
+ ) }),
132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-base font-normal text-center text-(--color-accent,#A89B83) m-0", children: "Add a scheduling widget to get instant bookings." })
135
+ ] }),
136
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
137
+ "button",
138
+ {
139
+ onClick: handleAddSchedule,
140
+ className: "h-9 px-2 py-1.5 flex items-center justify-center bg-white border border-[#E7E5E4] rounded-md font-body text-sm font-medium text-(--color-dark,#200C02) cursor-pointer",
141
+ children: "Add Schedule"
142
+ }
143
+ )
144
+ ] });
145
+ }
146
+ function LoadingSkeleton() {
147
+ const shimmer = {
148
+ background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
149
+ backgroundSize: "200% 100%",
150
+ animation: "ohw-shimmer 1.5s infinite"
151
+ };
152
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
157
+ ] }, i)) }),
158
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-[60px] py-4", children: [
160
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
161
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
164
+ ] }),
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
167
+ ] }),
168
+ i < 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px bg-black/20" })
169
+ ] }, i))
170
+ ] });
171
+ }
172
+ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate }) {
173
+ const todayMs = (0, import_react.useMemo)(() => {
174
+ const d = /* @__PURE__ */ new Date();
175
+ d.setHours(0, 0, 0, 0);
176
+ return d.getTime();
177
+ }, []);
178
+ const datesWithClasses = (0, import_react.useMemo)(
179
+ () => new Set(dates.map(
180
+ (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
181
+ ).filter((i) => i >= 0)),
182
+ [dates, schedule]
183
+ );
184
+ const selectedDate = dates[selectedIdx];
185
+ const selectedClasses = (0, import_react.useMemo)(
186
+ () => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
187
+ [schedule, selectedDate]
188
+ );
189
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "overflow-x-auto w-full", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex min-w-max", children: dates.map((date, i) => {
191
+ const isToday = date.getTime() === todayMs;
192
+ const isSelected = i === selectedIdx;
193
+ const clickable = datesWithClasses.has(i);
194
+ const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
195
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
196
+ "div",
197
+ {
198
+ onClick: () => clickable && onSelectDate(i),
199
+ className: `flex-none w-[72px] h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
200
+ children: [
201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
203
+ "div",
204
+ {
205
+ className: "w-10 h-10 rounded-full flex items-center justify-center",
206
+ style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
207
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
208
+ "span",
209
+ {
210
+ className: "font-body text-xl font-bold text-center tracking-display-tight",
211
+ style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
212
+ children: date.getDate()
213
+ }
214
+ )
215
+ }
216
+ )
217
+ ]
218
+ },
219
+ i
220
+ );
221
+ }) }) }),
222
+ selectedClasses.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
223
+ const booked = getBookingsOnDate(cls, selectedDate);
224
+ const available = cls.maxParticipants - booked;
225
+ const isFull = available <= 0;
226
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-[60px] py-4 box-border", children: [
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-[120px] shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
229
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 flex flex-col gap-3 min-w-0", children: [
230
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
231
+ cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
232
+ ] }),
233
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-14 shrink-0 flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
234
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
235
+ available,
236
+ "/",
237
+ cls.maxParticipants
238
+ ] }),
239
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
240
+ ] }) }),
241
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
242
+ "button",
243
+ {
244
+ className: "shrink-0 w-[200px] px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
245
+ style: isFull ? {
246
+ background: "transparent",
247
+ border: "1px solid var(--color-dark, #200C02)",
248
+ color: "var(--color-dark, #200C02)"
249
+ } : {
250
+ background: "var(--color-primary, #3D312B)",
251
+ border: "none",
252
+ color: "var(--color-light, #FEFFF0)"
253
+ },
254
+ children: isFull ? "Join Waitlist" : "Book Now"
255
+ }
256
+ )
257
+ ] }),
258
+ i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px bg-black/20" })
259
+ ] }, cls.id ?? i);
260
+ }) })
261
+ ] });
262
+ }
263
+ function CalendarFoldIcon() {
264
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
265
+ "svg",
266
+ {
267
+ width: "16",
268
+ height: "16",
269
+ viewBox: "0 0 24 24",
270
+ fill: "none",
271
+ stroke: "currentColor",
272
+ strokeWidth: "2",
273
+ strokeLinecap: "round",
274
+ strokeLinejoin: "round",
275
+ style: { flexShrink: 0 },
276
+ children: [
277
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 2v4" }),
278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M16 2v4" }),
279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 10h18" }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
282
+ ]
283
+ }
284
+ );
285
+ }
286
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
287
+ const [schedule, setSchedule] = (0, import_react.useState)(null);
288
+ const [loading, setLoading] = (0, import_react.useState)(true);
289
+ const [inEditor, setInEditor] = (0, import_react.useState)(false);
290
+ const [isHovered, setIsHovered] = (0, import_react.useState)(false);
291
+ const switchScheduleIdRef = import_react.default.useRef(null);
292
+ const dates = (0, import_react.useMemo)(() => {
293
+ const today = /* @__PURE__ */ new Date();
294
+ today.setHours(0, 0, 0, 0);
295
+ return Array.from({ length: 14 }, (_, i) => {
296
+ const d = new Date(today);
297
+ d.setDate(today.getDate() + i);
298
+ return d;
299
+ });
300
+ }, []);
301
+ const [selectedIdx, setSelectedIdx] = (0, import_react.useState)(0);
302
+ (0, import_react.useEffect)(() => {
303
+ if (!schedule?.classes) return;
304
+ for (let i = 0; i < dates.length; i++) {
305
+ if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
306
+ setSelectedIdx(i);
307
+ return;
308
+ }
309
+ }
310
+ }, [schedule, dates]);
311
+ (0, import_react.useEffect)(() => {
312
+ if (typeof window === "undefined") return;
313
+ const isInEditor = window.self !== window.top;
314
+ setInEditor(isInEditor);
315
+ if (isInEditor) {
316
+ const startEmpty = initialScheduleId === null;
317
+ if (startEmpty) setLoading(false);
318
+ let initialized = startEmpty;
319
+ const timer = !startEmpty ? setTimeout(() => {
320
+ if (!initialized) {
321
+ initialized = true;
322
+ setLoading(false);
323
+ }
324
+ }, 5e3) : null;
325
+ const handler = (e) => {
326
+ if (e.data?.type === "ow:clear-scheduling-widget") {
327
+ setSchedule(null);
328
+ setLoading(false);
329
+ return;
330
+ }
331
+ if (e.data?.type === "ow:switch-schedule") {
332
+ switchScheduleIdRef.current = e.data.scheduleId ?? null;
333
+ initialized = false;
334
+ setLoading(true);
335
+ window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
336
+ return;
337
+ }
338
+ if (e.data?.type !== "ow:user-schedules-response") return;
339
+ if (initialized && switchScheduleIdRef.current === null) return;
340
+ initialized = true;
341
+ if (timer) clearTimeout(timer);
342
+ const schedules = e.data.schedules ?? [];
343
+ const isSwitching = switchScheduleIdRef.current !== null;
344
+ const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : initialScheduleId ? schedules.find((s) => s.id === initialScheduleId) ?? schedules[0] ?? null : schedules[0] ?? null;
345
+ switchScheduleIdRef.current = null;
346
+ setSchedule(target);
347
+ setLoading(false);
348
+ if (notifyOnConnect && target && !isSwitching) {
349
+ window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: target.id, name: target.name } }, "*");
350
+ }
351
+ };
352
+ window.addEventListener("message", handler);
353
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
354
+ return () => {
355
+ window.removeEventListener("message", handler);
356
+ if (timer) clearTimeout(timer);
357
+ };
358
+ }
359
+ if (initialScheduleId === null) {
360
+ setLoading(false);
361
+ return;
362
+ }
363
+ ;
364
+ (async () => {
365
+ try {
366
+ if (initialScheduleId) {
367
+ const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
368
+ const data = await res.json();
369
+ setSchedule(data?.id ? data : null);
370
+ } else {
371
+ const domain = getApiDomain();
372
+ const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
373
+ const slugs = await sitemapRes.json();
374
+ if (!Array.isArray(slugs) || !slugs.length) {
375
+ setLoading(false);
376
+ return;
377
+ }
378
+ const { slug } = [...slugs].sort(
379
+ (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
380
+ )[0];
381
+ const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
382
+ const data = await res.json();
383
+ setSchedule(data?.id ? data : null);
384
+ }
385
+ } catch {
386
+ }
387
+ setLoading(false);
388
+ })();
389
+ }, []);
390
+ const timezoneLabel = schedule?.timezone ? (() => {
391
+ try {
392
+ return buildTimezoneLabel(schedule.timezone);
393
+ } catch {
394
+ return void 0;
395
+ }
396
+ })() : void 0;
397
+ const handleReplaceSchedule = () => {
398
+ setIsHovered(false);
399
+ if (schedule) {
400
+ window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: schedule.id, name: schedule.name } }, "*");
401
+ } else {
402
+ window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
403
+ }
404
+ };
405
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
406
+ "section",
407
+ {
408
+ "data-ohw-section": "scheduling",
409
+ className: "w-full box-border py-20 px-16 font-body bg-(--color-light,#FEFFF0) relative",
410
+ onMouseEnter: () => inEditor && setIsHovered(true),
411
+ onMouseLeave: () => setIsHovered(false),
412
+ children: [
413
+ inEditor && isHovered && !!schedule && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
414
+ "div",
415
+ {
416
+ className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
417
+ onClick: handleReplaceSchedule,
418
+ children: [
419
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
421
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
422
+ "div",
423
+ {
424
+ className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
425
+ style: {
426
+ fontSize: 14,
427
+ lineHeight: "24px",
428
+ fontFamily: "'Figtree', system-ui, sans-serif",
429
+ fontWeight: 500,
430
+ color: "#0C0A09"
431
+ },
432
+ children: [
433
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarFoldIcon, {}),
434
+ "Replace schedule"
435
+ ]
436
+ }
437
+ ) })
438
+ ]
439
+ }
440
+ ),
441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "font-display text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
444
+ schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
445
+ ] }),
446
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
447
+ timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
448
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-full p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
449
+ ScheduleView,
450
+ {
451
+ schedule,
452
+ dates,
453
+ selectedIdx,
454
+ onSelectDate: setSelectedIdx
455
+ }
456
+ ) })
457
+ ] })
458
+ ] })
459
+ ]
460
+ }
461
+ );
462
+ }
44
463
 
45
464
  // src/ui/toggle-group.tsx
46
- var React = __toESM(require("react"), 1);
465
+ var React2 = __toESM(require("react"), 1);
47
466
 
48
467
  // node_modules/clsx/dist/clsx.mjs
49
468
  function r(e) {
@@ -3363,7 +3782,7 @@ var cva = (base, config) => (props) => {
3363
3782
 
3364
3783
  // src/ui/toggle.tsx
3365
3784
  var import_radix_ui = require("radix-ui");
3366
- var import_jsx_runtime = require("react/jsx-runtime");
3785
+ var import_jsx_runtime2 = require("react/jsx-runtime");
3367
3786
  var toggleVariants = cva(
3368
3787
  "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3369
3788
  {
@@ -3390,7 +3809,7 @@ function Toggle({
3390
3809
  size,
3391
3810
  ...props
3392
3811
  }) {
3393
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3812
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3394
3813
  import_radix_ui.Toggle.Root,
3395
3814
  {
3396
3815
  "data-slot": "toggle",
@@ -3401,8 +3820,8 @@ function Toggle({
3401
3820
  }
3402
3821
 
3403
3822
  // src/ui/toggle-group.tsx
3404
- var import_jsx_runtime2 = require("react/jsx-runtime");
3405
- var ToggleGroupContext = React.createContext({
3823
+ var import_jsx_runtime3 = require("react/jsx-runtime");
3824
+ var ToggleGroupContext = React2.createContext({
3406
3825
  value: "",
3407
3826
  onValueChange: () => {
3408
3827
  }
@@ -3414,13 +3833,13 @@ function ToggleGroup({
3414
3833
  children,
3415
3834
  ...props
3416
3835
  }) {
3417
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3836
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3418
3837
  "div",
3419
3838
  {
3420
3839
  role: "group",
3421
3840
  className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
3422
3841
  ...props,
3423
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3842
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3424
3843
  }
3425
3844
  );
3426
3845
  }
@@ -3430,8 +3849,8 @@ function ToggleGroupItem({
3430
3849
  children,
3431
3850
  ...props
3432
3851
  }) {
3433
- const { value: groupValue, onValueChange } = React.useContext(ToggleGroupContext);
3434
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3852
+ const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
3853
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3435
3854
  Toggle,
3436
3855
  {
3437
3856
  pressed: groupValue === value,
@@ -3473,7 +3892,7 @@ function isEditSessionActive() {
3473
3892
  }
3474
3893
 
3475
3894
  // src/ui/badge.tsx
3476
- var import_jsx_runtime3 = require("react/jsx-runtime");
3895
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3477
3896
  var badgeVariants = cva(
3478
3897
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
3479
3898
  {
@@ -3491,11 +3910,11 @@ var badgeVariants = cva(
3491
3910
  }
3492
3911
  );
3493
3912
  function Badge({ className, variant, ...props }) {
3494
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
3913
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
3495
3914
  }
3496
3915
 
3497
3916
  // src/OhhwellsBridge.tsx
3498
- var import_jsx_runtime4 = require("react/jsx-runtime");
3917
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3499
3918
  var PRIMARY = "#0885FE";
3500
3919
  var IMAGE_FADE_MS = 300;
3501
3920
  function runOpacityFade(el, onDone) {
@@ -3546,6 +3965,41 @@ function fadeInBgImage(el, url, onReady) {
3546
3965
  if (!prevPos || prevPos === "static") el.style.position = prevPos;
3547
3966
  });
3548
3967
  }
3968
+ function getSectionsTracker() {
3969
+ let el = document.querySelector("[data-ohw-sections-tracker]");
3970
+ if (!el) {
3971
+ el = document.createElement("div");
3972
+ el.setAttribute("data-ohw-sections-tracker", "");
3973
+ el.style.display = "none";
3974
+ document.body.appendChild(el);
3975
+ }
3976
+ return el;
3977
+ }
3978
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId) {
3979
+ const anchor = document.querySelector(`[data-ohw-section="${insertAfter}"]`);
3980
+ if (!anchor) return false;
3981
+ if (anchor.nextElementSibling?.dataset.ohwSectionContainer) return false;
3982
+ const container = document.createElement("div");
3983
+ container.dataset.ohwSectionContainer = "scheduling";
3984
+ anchor.insertAdjacentElement("afterend", container);
3985
+ (0, import_client.createRoot)(container).render(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SchedulingWidget, { notifyOnConnect, initialScheduleId: scheduleId }));
3986
+ const tracker = getSectionsTracker();
3987
+ let sections = [];
3988
+ try {
3989
+ sections = JSON.parse(tracker.textContent || "[]");
3990
+ } catch {
3991
+ }
3992
+ if (!sections.find((s) => s.type === "scheduling" && s.insertAfter === insertAfter)) {
3993
+ sections.push({
3994
+ type: "scheduling",
3995
+ insertAfter,
3996
+ pagePath: window.location.pathname,
3997
+ ...scheduleId ? { scheduleId } : {}
3998
+ });
3999
+ tracker.textContent = JSON.stringify(sections);
4000
+ }
4001
+ return true;
4002
+ }
3549
4003
  function collectEditableNodes() {
3550
4004
  return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
3551
4005
  if (el.dataset.ohwEditable === "image") {
@@ -3703,7 +4157,7 @@ var TOOLBAR_GROUPS = [
3703
4157
  ];
3704
4158
  function GlowFrame({ rect, elRef }) {
3705
4159
  const GAP = 6;
3706
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4160
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3707
4161
  "div",
3708
4162
  {
3709
4163
  ref: elRef,
@@ -3754,7 +4208,7 @@ function FloatingToolbar({
3754
4208
  activeCommands
3755
4209
  }) {
3756
4210
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
3757
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4211
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3758
4212
  "div",
3759
4213
  {
3760
4214
  ref: elRef,
@@ -3777,11 +4231,11 @@ function FloatingToolbar({
3777
4231
  pointerEvents: "auto",
3778
4232
  whiteSpace: "nowrap"
3779
4233
  },
3780
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react.default.Fragment, { children: [
3781
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
4234
+ children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react2.default.Fragment, { children: [
4235
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3782
4236
  btns.map((btn) => {
3783
4237
  const isActive = activeCommands.has(btn.cmd);
3784
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4238
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3785
4239
  "button",
3786
4240
  {
3787
4241
  title: btn.title,
@@ -3807,7 +4261,7 @@ function FloatingToolbar({
3807
4261
  flexShrink: 0,
3808
4262
  padding: 6
3809
4263
  },
3810
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4264
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3811
4265
  "svg",
3812
4266
  {
3813
4267
  width: "16",
@@ -3840,7 +4294,7 @@ function StateToggle({
3840
4294
  states,
3841
4295
  onStateChange
3842
4296
  }) {
3843
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4297
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3844
4298
  ToggleGroup,
3845
4299
  {
3846
4300
  "data-ohw-state-toggle": "",
@@ -3854,7 +4308,7 @@ function StateToggle({
3854
4308
  left: rect.right - 8,
3855
4309
  transform: "translateX(-100%)"
3856
4310
  },
3857
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4311
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
3858
4312
  }
3859
4313
  );
3860
4314
  }
@@ -3864,8 +4318,8 @@ function OhhwellsBridge() {
3864
4318
  const router = (0, import_navigation.useRouter)();
3865
4319
  const searchParams = (0, import_navigation.useSearchParams)();
3866
4320
  const isEditMode = isEditSessionActive();
3867
- const [bridgeRoot, setBridgeRoot] = (0, import_react.useState)(null);
3868
- (0, import_react.useEffect)(() => {
4321
+ const [bridgeRoot, setBridgeRoot] = (0, import_react2.useState)(null);
4322
+ (0, import_react2.useEffect)(() => {
3869
4323
  const figtreeFontId = "ohw-figtree-font";
3870
4324
  if (!document.getElementById(figtreeFontId)) {
3871
4325
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -3893,39 +4347,39 @@ function OhhwellsBridge() {
3893
4347
  const parts = window.location.hostname.split(".");
3894
4348
  return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
3895
4349
  })();
3896
- const postToParent = (0, import_react.useCallback)((data) => {
4350
+ const postToParent = (0, import_react2.useCallback)((data) => {
3897
4351
  if (typeof window !== "undefined" && window.parent !== window) {
3898
4352
  window.parent.postMessage(data, "*");
3899
4353
  }
3900
4354
  }, []);
3901
- const [fetchState, setFetchState] = (0, import_react.useState)("idle");
3902
- const autoSaveTimers = (0, import_react.useRef)(/* @__PURE__ */ new Map());
3903
- const activeElRef = (0, import_react.useRef)(null);
3904
- const originalContentRef = (0, import_react.useRef)(null);
3905
- const activeStateElRef = (0, import_react.useRef)(null);
3906
- const parentScrollRef = (0, import_react.useRef)(null);
3907
- const toolbarElRef = (0, import_react.useRef)(null);
3908
- const glowElRef = (0, import_react.useRef)(null);
3909
- const hoveredImageRef = (0, import_react.useRef)(null);
3910
- const hoveredImageHasTextOverlapRef = (0, import_react.useRef)(false);
3911
- const hoveredGapRef = (0, import_react.useRef)(null);
3912
- const imageUnhoverTimerRef = (0, import_react.useRef)(null);
3913
- const imageShowTimerRef = (0, import_react.useRef)(null);
3914
- const editStylesRef = (0, import_react.useRef)(null);
3915
- const activateRef = (0, import_react.useRef)(() => {
4355
+ const [fetchState, setFetchState] = (0, import_react2.useState)("idle");
4356
+ const autoSaveTimers = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
4357
+ const activeElRef = (0, import_react2.useRef)(null);
4358
+ const originalContentRef = (0, import_react2.useRef)(null);
4359
+ const activeStateElRef = (0, import_react2.useRef)(null);
4360
+ const parentScrollRef = (0, import_react2.useRef)(null);
4361
+ const toolbarElRef = (0, import_react2.useRef)(null);
4362
+ const glowElRef = (0, import_react2.useRef)(null);
4363
+ const hoveredImageRef = (0, import_react2.useRef)(null);
4364
+ const hoveredImageHasTextOverlapRef = (0, import_react2.useRef)(false);
4365
+ const hoveredGapRef = (0, import_react2.useRef)(null);
4366
+ const imageUnhoverTimerRef = (0, import_react2.useRef)(null);
4367
+ const imageShowTimerRef = (0, import_react2.useRef)(null);
4368
+ const editStylesRef = (0, import_react2.useRef)(null);
4369
+ const activateRef = (0, import_react2.useRef)(() => {
3916
4370
  });
3917
- const deactivateRef = (0, import_react.useRef)(() => {
4371
+ const deactivateRef = (0, import_react2.useRef)(() => {
3918
4372
  });
3919
- const refreshActiveCommandsRef = (0, import_react.useRef)(() => {
4373
+ const refreshActiveCommandsRef = (0, import_react2.useRef)(() => {
3920
4374
  });
3921
- const postToParentRef = (0, import_react.useRef)(postToParent);
4375
+ const postToParentRef = (0, import_react2.useRef)(postToParent);
3922
4376
  postToParentRef.current = postToParent;
3923
- const [toolbarRect, setToolbarRect] = (0, import_react.useState)(null);
3924
- const [toggleState, setToggleState] = (0, import_react.useState)(null);
3925
- const [maxBadge, setMaxBadge] = (0, import_react.useState)(null);
3926
- const [activeCommands, setActiveCommands] = (0, import_react.useState)(/* @__PURE__ */ new Set());
3927
- const [sectionGap, setSectionGap] = (0, import_react.useState)(null);
3928
- (0, import_react.useEffect)(() => {
4377
+ const [toolbarRect, setToolbarRect] = (0, import_react2.useState)(null);
4378
+ const [toggleState, setToggleState] = (0, import_react2.useState)(null);
4379
+ const [maxBadge, setMaxBadge] = (0, import_react2.useState)(null);
4380
+ const [activeCommands, setActiveCommands] = (0, import_react2.useState)(/* @__PURE__ */ new Set());
4381
+ const [sectionGap, setSectionGap] = (0, import_react2.useState)(null);
4382
+ (0, import_react2.useEffect)(() => {
3929
4383
  const update = () => {
3930
4384
  const el = activeElRef.current;
3931
4385
  if (el) setToolbarRect(el.getBoundingClientRect());
@@ -3949,10 +4403,10 @@ function OhhwellsBridge() {
3949
4403
  vvp.removeEventListener("resize", update);
3950
4404
  };
3951
4405
  }, []);
3952
- const refreshStateRules = (0, import_react.useCallback)(() => {
4406
+ const refreshStateRules = (0, import_react2.useCallback)(() => {
3953
4407
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
3954
4408
  }, []);
3955
- const deactivate = (0, import_react.useCallback)(() => {
4409
+ const deactivate = (0, import_react2.useCallback)(() => {
3956
4410
  const el = activeElRef.current;
3957
4411
  if (!el) return;
3958
4412
  const key = el.dataset.ohwKey;
@@ -3977,7 +4431,7 @@ function OhhwellsBridge() {
3977
4431
  setActiveCommands(/* @__PURE__ */ new Set());
3978
4432
  postToParent({ type: "ow:exit-edit" });
3979
4433
  }, [postToParent]);
3980
- const activate = (0, import_react.useCallback)((el) => {
4434
+ const activate = (0, import_react2.useCallback)((el) => {
3981
4435
  if (activeElRef.current === el) return;
3982
4436
  deactivate();
3983
4437
  if (hoveredImageRef.current) {
@@ -3995,14 +4449,27 @@ function OhhwellsBridge() {
3995
4449
  }, [deactivate, postToParent]);
3996
4450
  activateRef.current = activate;
3997
4451
  deactivateRef.current = deactivate;
3998
- (0, import_react.useLayoutEffect)(() => {
4452
+ (0, import_react2.useLayoutEffect)(() => {
3999
4453
  if (!subdomain || isEditMode) {
4000
4454
  setFetchState("done");
4001
4455
  return;
4002
4456
  }
4003
4457
  const applyContent = (content) => {
4004
4458
  const imageLoads = [];
4459
+ document.querySelectorAll("[data-ohw-section-container]").forEach((el) => el.remove());
4460
+ if (content["__ohw_sections"]) {
4461
+ try {
4462
+ const entries = JSON.parse(content["__ohw_sections"]);
4463
+ const currentPath = window.location.pathname;
4464
+ entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
4465
+ if (pagePath && pagePath !== currentPath) return;
4466
+ if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
4467
+ });
4468
+ } catch {
4469
+ }
4470
+ }
4005
4471
  for (const [key, val] of Object.entries(content)) {
4472
+ if (key === "__ohw_sections") continue;
4006
4473
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
4007
4474
  if (el.dataset.ohwEditable === "image") {
4008
4475
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -4045,12 +4512,24 @@ function OhhwellsBridge() {
4045
4512
  cancelled = true;
4046
4513
  };
4047
4514
  }, [subdomain, isEditMode, pathname]);
4048
- (0, import_react.useEffect)(() => {
4515
+ (0, import_react2.useEffect)(() => {
4049
4516
  if (!subdomain || isEditMode) return;
4050
4517
  const applyFromCache = () => {
4051
4518
  const content = contentCache.get(subdomain);
4052
4519
  if (!content) return;
4520
+ if (content["__ohw_sections"]) {
4521
+ try {
4522
+ const entries = JSON.parse(content["__ohw_sections"]);
4523
+ const currentPath = window.location.pathname;
4524
+ entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
4525
+ if (pagePath && pagePath !== currentPath) return;
4526
+ if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
4527
+ });
4528
+ } catch {
4529
+ }
4530
+ }
4053
4531
  for (const [key, val] of Object.entries(content)) {
4532
+ if (key === "__ohw_sections") continue;
4054
4533
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
4055
4534
  if (el.dataset.ohwEditable === "image") {
4056
4535
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -4069,16 +4548,16 @@ function OhhwellsBridge() {
4069
4548
  observer.observe(document.body, { childList: true, subtree: true });
4070
4549
  return () => observer.disconnect();
4071
4550
  }, [subdomain, isEditMode]);
4072
- (0, import_react.useLayoutEffect)(() => {
4551
+ (0, import_react2.useLayoutEffect)(() => {
4073
4552
  const el = document.getElementById("ohw-loader");
4074
4553
  if (!el) return;
4075
4554
  const visible = Boolean(subdomain) && fetchState !== "done";
4076
4555
  el.style.display = visible ? "flex" : "none";
4077
4556
  }, [subdomain, fetchState]);
4078
- (0, import_react.useEffect)(() => {
4557
+ (0, import_react2.useEffect)(() => {
4079
4558
  postToParent({ type: "ow:navigation", path: pathname });
4080
4559
  }, [pathname, postToParent]);
4081
- (0, import_react.useEffect)(() => {
4560
+ (0, import_react2.useEffect)(() => {
4082
4561
  if (!isEditMode) return;
4083
4562
  const measure = () => {
4084
4563
  const h = document.body.scrollHeight;
@@ -4102,7 +4581,7 @@ function OhhwellsBridge() {
4102
4581
  window.removeEventListener("resize", handleResize);
4103
4582
  };
4104
4583
  }, [pathname, isEditMode, postToParent]);
4105
- (0, import_react.useEffect)(() => {
4584
+ (0, import_react2.useEffect)(() => {
4106
4585
  if (!subdomainFromQuery || isEditMode) return;
4107
4586
  const handleClick = (e) => {
4108
4587
  const anchor = e.target.closest("a");
@@ -4118,7 +4597,7 @@ function OhhwellsBridge() {
4118
4597
  document.addEventListener("click", handleClick, true);
4119
4598
  return () => document.removeEventListener("click", handleClick, true);
4120
4599
  }, [subdomainFromQuery, isEditMode, router]);
4121
- (0, import_react.useEffect)(() => {
4600
+ (0, import_react2.useEffect)(() => {
4122
4601
  if (!isEditMode) {
4123
4602
  editStylesRef.current?.base.remove();
4124
4603
  editStylesRef.current?.forceHover.remove();
@@ -4378,6 +4857,15 @@ function OhhwellsBridge() {
4378
4857
  textEditable.setAttribute("data-ohw-hovered", "");
4379
4858
  return;
4380
4859
  }
4860
+ if (hoveredGapRef.current) {
4861
+ if (hoveredImageRef.current) {
4862
+ hoveredImageRef.current = null;
4863
+ resumeAnimTracks();
4864
+ postToParentRef.current({ type: "ow:image-unhover" });
4865
+ }
4866
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4867
+ return;
4868
+ }
4381
4869
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4382
4870
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
4383
4871
  hoveredImageRef.current = imgEl;
@@ -4466,17 +4954,17 @@ function OhhwellsBridge() {
4466
4954
  };
4467
4955
  const handleMouseMove = (e) => {
4468
4956
  const { clientX, clientY } = e;
4957
+ probeSectionGapAt(clientX, clientY);
4469
4958
  probeImageAt(clientX, clientY);
4470
4959
  probeHoverCardsAt(clientX, clientY);
4471
- probeSectionGapAt(clientX, clientY);
4472
4960
  };
4473
4961
  const handlePointerSync = (e) => {
4474
4962
  if (e.data?.type !== "ow:pointer-sync") return;
4475
4963
  const { clientX, clientY } = e.data;
4476
4964
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
4965
+ probeSectionGapAt(clientX, clientY);
4477
4966
  probeImageAt(clientX, clientY);
4478
4967
  probeHoverCardsAt(clientX, clientY);
4479
- probeSectionGapAt(clientX, clientY);
4480
4968
  };
4481
4969
  const handleDragOver = (e) => {
4482
4970
  e.preventDefault();
@@ -4633,6 +5121,18 @@ function OhhwellsBridge() {
4633
5121
  const content = e.data.content;
4634
5122
  if (!content) return;
4635
5123
  for (const [key, val] of Object.entries(content)) {
5124
+ if (key === "__ohw_sections") {
5125
+ try {
5126
+ const entries = JSON.parse(val);
5127
+ const tracker = getSectionsTracker();
5128
+ tracker.textContent = val;
5129
+ entries.forEach(({ type, insertAfter, scheduleId }) => {
5130
+ if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
5131
+ });
5132
+ } catch {
5133
+ }
5134
+ continue;
5135
+ }
4636
5136
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
4637
5137
  if (el.dataset.ohwEditable === "image") {
4638
5138
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -4687,7 +5187,71 @@ function OhhwellsBridge() {
4687
5187
  };
4688
5188
  const handleSave = (e) => {
4689
5189
  if (e.data?.type !== "ow:save") return;
4690
- postToParentRef.current({ type: "ow:save-result", nodes: collectEditableNodes() });
5190
+ const nodes = collectEditableNodes();
5191
+ const tracker = document.querySelector("[data-ohw-sections-tracker]");
5192
+ if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
5193
+ postToParentRef.current({ type: "ow:save-result", nodes });
5194
+ };
5195
+ const handleInsertSection = (e) => {
5196
+ if (e.data?.type !== "ow:insert-section") return;
5197
+ const { widgetType, insertAfter } = e.data;
5198
+ if (widgetType !== "scheduling") return;
5199
+ const inserted = mountSchedulingWidget(insertAfter, true);
5200
+ if (inserted) {
5201
+ const tracker = getSectionsTracker();
5202
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
5203
+ const h = document.documentElement.scrollHeight;
5204
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
5205
+ }
5206
+ };
5207
+ const handleSwitchSchedule = (e) => {
5208
+ if (e.data?.type !== "ow:switch-schedule") return;
5209
+ const scheduleId = e.data.scheduleId;
5210
+ if (!scheduleId) return;
5211
+ const tracker = getSectionsTracker();
5212
+ let sections = [];
5213
+ try {
5214
+ sections = JSON.parse(tracker.textContent || "[]");
5215
+ } catch {
5216
+ }
5217
+ const currentPath = window.location.pathname;
5218
+ const updated = sections.map(
5219
+ (s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId } : s
5220
+ );
5221
+ tracker.textContent = JSON.stringify(updated);
5222
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5223
+ };
5224
+ const handleClearSchedulingWidget = (e) => {
5225
+ if (e.data?.type !== "ow:clear-scheduling-widget") return;
5226
+ const tracker = getSectionsTracker();
5227
+ let sections = [];
5228
+ try {
5229
+ sections = JSON.parse(tracker.textContent || "[]");
5230
+ } catch {
5231
+ }
5232
+ const currentPath = window.location.pathname;
5233
+ const updated = sections.map(
5234
+ (s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId: null } : s
5235
+ );
5236
+ tracker.textContent = JSON.stringify(updated);
5237
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5238
+ };
5239
+ const handleRemoveSchedulingSection = (e) => {
5240
+ if (e.data?.type !== "ow:remove-scheduling-section") return;
5241
+ const els = document.querySelectorAll('[data-ohw-section="scheduling"]');
5242
+ els.forEach((el) => el.parentElement?.removeChild(el));
5243
+ const tracker = getSectionsTracker();
5244
+ let sections = [];
5245
+ try {
5246
+ sections = JSON.parse(tracker.textContent || "[]");
5247
+ } catch {
5248
+ }
5249
+ const currentPath = window.location.pathname;
5250
+ const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
5251
+ tracker.textContent = JSON.stringify(updated);
5252
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5253
+ const h = document.documentElement.scrollHeight;
5254
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
4691
5255
  };
4692
5256
  const handleSelectionChange = () => {
4693
5257
  if (!activeElRef.current) return;
@@ -4792,6 +5356,10 @@ function OhhwellsBridge() {
4792
5356
  deactivateRef.current();
4793
5357
  };
4794
5358
  window.addEventListener("message", handleSave);
5359
+ window.addEventListener("message", handleInsertSection);
5360
+ window.addEventListener("message", handleSwitchSchedule);
5361
+ window.addEventListener("message", handleClearSchedulingWidget);
5362
+ window.addEventListener("message", handleRemoveSchedulingSection);
4795
5363
  window.addEventListener("message", handleImageUrl);
4796
5364
  window.addEventListener("message", handleAnimLock);
4797
5365
  window.addEventListener("message", handleCanvasHeight);
@@ -4826,6 +5394,10 @@ function OhhwellsBridge() {
4826
5394
  document.removeEventListener("selectionchange", handleSelectionChange);
4827
5395
  window.removeEventListener("scroll", handleScroll, true);
4828
5396
  window.removeEventListener("message", handleSave);
5397
+ window.removeEventListener("message", handleInsertSection);
5398
+ window.removeEventListener("message", handleSwitchSchedule);
5399
+ window.removeEventListener("message", handleClearSchedulingWidget);
5400
+ window.removeEventListener("message", handleRemoveSchedulingSection);
4829
5401
  window.removeEventListener("message", handleImageUrl);
4830
5402
  window.removeEventListener("message", handleAnimLock);
4831
5403
  window.removeEventListener("message", handleCanvasHeight);
@@ -4840,7 +5412,7 @@ function OhhwellsBridge() {
4840
5412
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
4841
5413
  };
4842
5414
  }, [isEditMode, refreshStateRules]);
4843
- (0, import_react.useEffect)(() => {
5415
+ (0, import_react2.useEffect)(() => {
4844
5416
  if (!isEditMode) return;
4845
5417
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
4846
5418
  el.removeAttribute("data-ohw-active-state");
@@ -4862,13 +5434,13 @@ function OhhwellsBridge() {
4862
5434
  clearTimeout(timer);
4863
5435
  };
4864
5436
  }, [pathname, isEditMode, refreshStateRules, postToParent]);
4865
- const handleCommand = (0, import_react.useCallback)((cmd) => {
5437
+ const handleCommand = (0, import_react2.useCallback)((cmd) => {
4866
5438
  document.execCommand(cmd, false);
4867
5439
  activeElRef.current?.focus();
4868
5440
  if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
4869
5441
  refreshActiveCommandsRef.current();
4870
5442
  }, []);
4871
- const handleStateChange = (0, import_react.useCallback)((state) => {
5443
+ const handleStateChange = (0, import_react2.useCallback)((state) => {
4872
5444
  if (!activeStateElRef.current) return;
4873
5445
  const el = activeStateElRef.current;
4874
5446
  if (state === "Default") {
@@ -4882,12 +5454,12 @@ function OhhwellsBridge() {
4882
5454
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
4883
5455
  }, [deactivate]);
4884
5456
  return bridgeRoot ? (0, import_react_dom.createPortal)(
4885
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
4886
- toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
4887
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4888
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
5457
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
5458
+ toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
5459
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
5460
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
4889
5461
  ] }),
4890
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
5462
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4891
5463
  "div",
4892
5464
  {
4893
5465
  "data-ohw-max-badge": "",
@@ -4913,7 +5485,7 @@ function OhhwellsBridge() {
4913
5485
  ]
4914
5486
  }
4915
5487
  ),
4916
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
5488
+ toggleState && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4917
5489
  StateToggle,
4918
5490
  {
4919
5491
  rect: toggleState.rect,
@@ -4922,15 +5494,15 @@ function OhhwellsBridge() {
4922
5494
  onStateChange: handleStateChange
4923
5495
  }
4924
5496
  ),
4925
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
5497
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4926
5498
  "div",
4927
5499
  {
4928
5500
  "data-ohw-section-insert-line": "",
4929
5501
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
4930
5502
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
4931
5503
  children: [
4932
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
4933
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
5504
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
5505
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4934
5506
  Badge,
4935
5507
  {
4936
5508
  className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
@@ -4943,7 +5515,7 @@ function OhhwellsBridge() {
4943
5515
  children: "Add Section"
4944
5516
  }
4945
5517
  ),
4946
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
5518
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
4947
5519
  ]
4948
5520
  }
4949
5521
  )
@@ -4954,6 +5526,7 @@ function OhhwellsBridge() {
4954
5526
  // Annotate the CommonJS export names for ESM import in node:
4955
5527
  0 && (module.exports = {
4956
5528
  OhhwellsBridge,
5529
+ SchedulingWidget,
4957
5530
  Toggle,
4958
5531
  ToggleGroup,
4959
5532
  ToggleGroupItem,