@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 +646 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +621 -49
- package/dist/index.js.map +1 -1
- package/dist/styles.css +262 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,428 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/OhhwellsBridge.tsx
|
|
4
|
-
import
|
|
4
|
+
import React3, { useCallback, useEffect as useEffect2, useLayoutEffect, useRef, useState as useState2 } from "react";
|
|
5
|
+
import { createRoot } from "react-dom/client";
|
|
6
|
+
|
|
7
|
+
// src/ui/SchedulingWidget.tsx
|
|
8
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
11
|
+
var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
12
|
+
function getApiDomain() {
|
|
13
|
+
const h = window.location.hostname;
|
|
14
|
+
for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
|
|
15
|
+
if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
|
|
16
|
+
}
|
|
17
|
+
return h;
|
|
18
|
+
}
|
|
19
|
+
function classRunsOnDate(cls, date, type) {
|
|
20
|
+
if (type === "FIXED") {
|
|
21
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
22
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
23
|
+
return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
|
|
24
|
+
}
|
|
25
|
+
return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
|
|
26
|
+
}
|
|
27
|
+
function formatClassTime(cls) {
|
|
28
|
+
let h = parseInt(cls.startTime.hour, 10);
|
|
29
|
+
const m = parseInt(cls.startTime.minutes, 10);
|
|
30
|
+
if (cls.startTime.time === "PM" && h !== 12) h += 12;
|
|
31
|
+
if (cls.startTime.time === "AM" && h === 12) h = 0;
|
|
32
|
+
const endTotal = h * 60 + m + cls.duration;
|
|
33
|
+
const eh = Math.floor(endTotal / 60) % 24;
|
|
34
|
+
const em = endTotal % 60;
|
|
35
|
+
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
36
|
+
}
|
|
37
|
+
function getBookingsOnDate(cls, date) {
|
|
38
|
+
if (!cls.bookings?.length) return 0;
|
|
39
|
+
const ds = date.toISOString().split("T")[0];
|
|
40
|
+
return cls.bookings.filter((b) => {
|
|
41
|
+
try {
|
|
42
|
+
return new Date(b.classDate).toISOString().split("T")[0] === ds;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}).length;
|
|
47
|
+
}
|
|
48
|
+
function buildTimezoneLabel(tz) {
|
|
49
|
+
try {
|
|
50
|
+
const now = /* @__PURE__ */ new Date();
|
|
51
|
+
const timeStr = new Intl.DateTimeFormat("en-US", {
|
|
52
|
+
hour: "2-digit",
|
|
53
|
+
minute: "2-digit",
|
|
54
|
+
hour12: false,
|
|
55
|
+
timeZone: tz
|
|
56
|
+
}).format(now);
|
|
57
|
+
const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
58
|
+
const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
|
|
59
|
+
return `${timeStr} (${offsetPart}) ${city} time`;
|
|
60
|
+
} catch {
|
|
61
|
+
return tz;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function EmptyState({ inEditor }) {
|
|
65
|
+
const handleAddSchedule = () => {
|
|
66
|
+
if (inEditor) {
|
|
67
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
return /* @__PURE__ */ 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: [
|
|
71
|
+
/* @__PURE__ */ jsx("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ jsxs(
|
|
72
|
+
"svg",
|
|
73
|
+
{
|
|
74
|
+
width: "20",
|
|
75
|
+
height: "20",
|
|
76
|
+
viewBox: "0 0 24 24",
|
|
77
|
+
fill: "none",
|
|
78
|
+
stroke: "var(--color-accent, #A89B83)",
|
|
79
|
+
strokeWidth: "1.5",
|
|
80
|
+
strokeLinecap: "round",
|
|
81
|
+
strokeLinejoin: "round",
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
|
|
84
|
+
/* @__PURE__ */ jsx("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
|
|
85
|
+
/* @__PURE__ */ jsx("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
|
|
86
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
|
|
87
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
|
|
88
|
+
/* @__PURE__ */ jsx("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
) }),
|
|
92
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
|
|
93
|
+
/* @__PURE__ */ jsx("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
|
|
94
|
+
/* @__PURE__ */ 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." })
|
|
95
|
+
] }),
|
|
96
|
+
/* @__PURE__ */ jsx(
|
|
97
|
+
"button",
|
|
98
|
+
{
|
|
99
|
+
onClick: handleAddSchedule,
|
|
100
|
+
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",
|
|
101
|
+
children: "Add Schedule"
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] });
|
|
105
|
+
}
|
|
106
|
+
function LoadingSkeleton() {
|
|
107
|
+
const shimmer = {
|
|
108
|
+
background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
|
|
109
|
+
backgroundSize: "200% 100%",
|
|
110
|
+
animation: "ohw-shimmer 1.5s infinite"
|
|
111
|
+
};
|
|
112
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-10", children: [
|
|
113
|
+
/* @__PURE__ */ jsx("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
|
|
114
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
|
|
115
|
+
/* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
|
|
116
|
+
/* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
|
|
117
|
+
] }, i)) }),
|
|
118
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { children: [
|
|
119
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[60px] py-4", children: [
|
|
120
|
+
/* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
|
|
121
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-2", children: [
|
|
122
|
+
/* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
|
|
123
|
+
/* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
|
|
124
|
+
] }),
|
|
125
|
+
/* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
|
|
126
|
+
/* @__PURE__ */ jsx("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
|
|
127
|
+
] }),
|
|
128
|
+
i < 2 && /* @__PURE__ */ jsx("div", { className: "h-px bg-black/20" })
|
|
129
|
+
] }, i))
|
|
130
|
+
] });
|
|
131
|
+
}
|
|
132
|
+
function ScheduleView({ schedule, dates, selectedIdx, onSelectDate }) {
|
|
133
|
+
const todayMs = useMemo(() => {
|
|
134
|
+
const d = /* @__PURE__ */ new Date();
|
|
135
|
+
d.setHours(0, 0, 0, 0);
|
|
136
|
+
return d.getTime();
|
|
137
|
+
}, []);
|
|
138
|
+
const datesWithClasses = useMemo(
|
|
139
|
+
() => new Set(dates.map(
|
|
140
|
+
(d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
|
|
141
|
+
).filter((i) => i >= 0)),
|
|
142
|
+
[dates, schedule]
|
|
143
|
+
);
|
|
144
|
+
const selectedDate = dates[selectedIdx];
|
|
145
|
+
const selectedClasses = useMemo(
|
|
146
|
+
() => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
|
|
147
|
+
[schedule, selectedDate]
|
|
148
|
+
);
|
|
149
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-10", children: [
|
|
150
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto w-full", children: /* @__PURE__ */ jsx("div", { className: "flex min-w-max", children: dates.map((date, i) => {
|
|
151
|
+
const isToday = date.getTime() === todayMs;
|
|
152
|
+
const isSelected = i === selectedIdx;
|
|
153
|
+
const clickable = datesWithClasses.has(i);
|
|
154
|
+
const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
|
|
155
|
+
return /* @__PURE__ */ jsxs(
|
|
156
|
+
"div",
|
|
157
|
+
{
|
|
158
|
+
onClick: () => clickable && onSelectDate(i),
|
|
159
|
+
className: `flex-none w-[72px] h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ jsx("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
|
|
162
|
+
/* @__PURE__ */ jsx(
|
|
163
|
+
"div",
|
|
164
|
+
{
|
|
165
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center",
|
|
166
|
+
style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
|
|
167
|
+
children: /* @__PURE__ */ jsx(
|
|
168
|
+
"span",
|
|
169
|
+
{
|
|
170
|
+
className: "font-body text-xl font-bold text-center tracking-display-tight",
|
|
171
|
+
style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
|
|
172
|
+
children: date.getDate()
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
]
|
|
178
|
+
},
|
|
179
|
+
i
|
|
180
|
+
);
|
|
181
|
+
}) }) }),
|
|
182
|
+
selectedClasses.length === 0 ? /* @__PURE__ */ 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__ */ jsx("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
|
|
183
|
+
const booked = getBookingsOnDate(cls, selectedDate);
|
|
184
|
+
const available = cls.maxParticipants - booked;
|
|
185
|
+
const isFull = available <= 0;
|
|
186
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
187
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[60px] py-4 box-border", children: [
|
|
188
|
+
/* @__PURE__ */ jsx("div", { className: "w-[120px] shrink-0", children: /* @__PURE__ */ jsx("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
|
|
189
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-3 min-w-0", children: [
|
|
190
|
+
/* @__PURE__ */ jsx("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
191
|
+
cls.hostName && /* @__PURE__ */ jsx("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
|
|
192
|
+
] }),
|
|
193
|
+
/* @__PURE__ */ jsx("div", { className: "w-14 shrink-0 flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
194
|
+
/* @__PURE__ */ jsxs("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
195
|
+
available,
|
|
196
|
+
"/",
|
|
197
|
+
cls.maxParticipants
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsx("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
200
|
+
] }) }),
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
"button",
|
|
203
|
+
{
|
|
204
|
+
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",
|
|
205
|
+
style: isFull ? {
|
|
206
|
+
background: "transparent",
|
|
207
|
+
border: "1px solid var(--color-dark, #200C02)",
|
|
208
|
+
color: "var(--color-dark, #200C02)"
|
|
209
|
+
} : {
|
|
210
|
+
background: "var(--color-primary, #3D312B)",
|
|
211
|
+
border: "none",
|
|
212
|
+
color: "var(--color-light, #FEFFF0)"
|
|
213
|
+
},
|
|
214
|
+
children: isFull ? "Join Waitlist" : "Book Now"
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
] }),
|
|
218
|
+
i < selectedClasses.length - 1 && /* @__PURE__ */ jsx("div", { className: "h-px bg-black/20" })
|
|
219
|
+
] }, cls.id ?? i);
|
|
220
|
+
}) })
|
|
221
|
+
] });
|
|
222
|
+
}
|
|
223
|
+
function CalendarFoldIcon() {
|
|
224
|
+
return /* @__PURE__ */ jsxs(
|
|
225
|
+
"svg",
|
|
226
|
+
{
|
|
227
|
+
width: "16",
|
|
228
|
+
height: "16",
|
|
229
|
+
viewBox: "0 0 24 24",
|
|
230
|
+
fill: "none",
|
|
231
|
+
stroke: "currentColor",
|
|
232
|
+
strokeWidth: "2",
|
|
233
|
+
strokeLinecap: "round",
|
|
234
|
+
strokeLinejoin: "round",
|
|
235
|
+
style: { flexShrink: 0 },
|
|
236
|
+
children: [
|
|
237
|
+
/* @__PURE__ */ jsx("path", { d: "M8 2v4" }),
|
|
238
|
+
/* @__PURE__ */ jsx("path", { d: "M16 2v4" }),
|
|
239
|
+
/* @__PURE__ */ jsx("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
|
|
240
|
+
/* @__PURE__ */ jsx("path", { d: "M3 10h18" }),
|
|
241
|
+
/* @__PURE__ */ jsx("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
|
|
247
|
+
const [schedule, setSchedule] = useState(null);
|
|
248
|
+
const [loading, setLoading] = useState(true);
|
|
249
|
+
const [inEditor, setInEditor] = useState(false);
|
|
250
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
251
|
+
const switchScheduleIdRef = React.useRef(null);
|
|
252
|
+
const dates = useMemo(() => {
|
|
253
|
+
const today = /* @__PURE__ */ new Date();
|
|
254
|
+
today.setHours(0, 0, 0, 0);
|
|
255
|
+
return Array.from({ length: 14 }, (_, i) => {
|
|
256
|
+
const d = new Date(today);
|
|
257
|
+
d.setDate(today.getDate() + i);
|
|
258
|
+
return d;
|
|
259
|
+
});
|
|
260
|
+
}, []);
|
|
261
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
if (!schedule?.classes) return;
|
|
264
|
+
for (let i = 0; i < dates.length; i++) {
|
|
265
|
+
if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
|
|
266
|
+
setSelectedIdx(i);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}, [schedule, dates]);
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (typeof window === "undefined") return;
|
|
273
|
+
const isInEditor = window.self !== window.top;
|
|
274
|
+
setInEditor(isInEditor);
|
|
275
|
+
if (isInEditor) {
|
|
276
|
+
const startEmpty = initialScheduleId === null;
|
|
277
|
+
if (startEmpty) setLoading(false);
|
|
278
|
+
let initialized = startEmpty;
|
|
279
|
+
const timer = !startEmpty ? setTimeout(() => {
|
|
280
|
+
if (!initialized) {
|
|
281
|
+
initialized = true;
|
|
282
|
+
setLoading(false);
|
|
283
|
+
}
|
|
284
|
+
}, 5e3) : null;
|
|
285
|
+
const handler = (e) => {
|
|
286
|
+
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
287
|
+
setSchedule(null);
|
|
288
|
+
setLoading(false);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (e.data?.type === "ow:switch-schedule") {
|
|
292
|
+
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
293
|
+
initialized = false;
|
|
294
|
+
setLoading(true);
|
|
295
|
+
window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
299
|
+
if (initialized && switchScheduleIdRef.current === null) return;
|
|
300
|
+
initialized = true;
|
|
301
|
+
if (timer) clearTimeout(timer);
|
|
302
|
+
const schedules = e.data.schedules ?? [];
|
|
303
|
+
const isSwitching = switchScheduleIdRef.current !== null;
|
|
304
|
+
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;
|
|
305
|
+
switchScheduleIdRef.current = null;
|
|
306
|
+
setSchedule(target);
|
|
307
|
+
setLoading(false);
|
|
308
|
+
if (notifyOnConnect && target && !isSwitching) {
|
|
309
|
+
window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: target.id, name: target.name } }, "*");
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
window.addEventListener("message", handler);
|
|
313
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
|
|
314
|
+
return () => {
|
|
315
|
+
window.removeEventListener("message", handler);
|
|
316
|
+
if (timer) clearTimeout(timer);
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (initialScheduleId === null) {
|
|
320
|
+
setLoading(false);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
;
|
|
324
|
+
(async () => {
|
|
325
|
+
try {
|
|
326
|
+
if (initialScheduleId) {
|
|
327
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
|
|
328
|
+
const data = await res.json();
|
|
329
|
+
setSchedule(data?.id ? data : null);
|
|
330
|
+
} else {
|
|
331
|
+
const domain = getApiDomain();
|
|
332
|
+
const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
|
|
333
|
+
const slugs = await sitemapRes.json();
|
|
334
|
+
if (!Array.isArray(slugs) || !slugs.length) {
|
|
335
|
+
setLoading(false);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const { slug } = [...slugs].sort(
|
|
339
|
+
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
340
|
+
)[0];
|
|
341
|
+
const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
|
|
342
|
+
const data = await res.json();
|
|
343
|
+
setSchedule(data?.id ? data : null);
|
|
344
|
+
}
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
setLoading(false);
|
|
348
|
+
})();
|
|
349
|
+
}, []);
|
|
350
|
+
const timezoneLabel = schedule?.timezone ? (() => {
|
|
351
|
+
try {
|
|
352
|
+
return buildTimezoneLabel(schedule.timezone);
|
|
353
|
+
} catch {
|
|
354
|
+
return void 0;
|
|
355
|
+
}
|
|
356
|
+
})() : void 0;
|
|
357
|
+
const handleReplaceSchedule = () => {
|
|
358
|
+
setIsHovered(false);
|
|
359
|
+
if (schedule) {
|
|
360
|
+
window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: schedule.id, name: schedule.name } }, "*");
|
|
361
|
+
} else {
|
|
362
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
return /* @__PURE__ */ jsxs(
|
|
366
|
+
"section",
|
|
367
|
+
{
|
|
368
|
+
"data-ohw-section": "scheduling",
|
|
369
|
+
className: "w-full box-border py-20 px-16 font-body bg-(--color-light,#FEFFF0) relative",
|
|
370
|
+
onMouseEnter: () => inEditor && setIsHovered(true),
|
|
371
|
+
onMouseLeave: () => setIsHovered(false),
|
|
372
|
+
children: [
|
|
373
|
+
inEditor && isHovered && !!schedule && /* @__PURE__ */ jsxs(
|
|
374
|
+
"div",
|
|
375
|
+
{
|
|
376
|
+
className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
|
|
377
|
+
onClick: handleReplaceSchedule,
|
|
378
|
+
children: [
|
|
379
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
|
|
380
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
|
|
381
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs(
|
|
382
|
+
"div",
|
|
383
|
+
{
|
|
384
|
+
className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
|
|
385
|
+
style: {
|
|
386
|
+
fontSize: 14,
|
|
387
|
+
lineHeight: "24px",
|
|
388
|
+
fontFamily: "'Figtree', system-ui, sans-serif",
|
|
389
|
+
fontWeight: 500,
|
|
390
|
+
color: "#0C0A09"
|
|
391
|
+
},
|
|
392
|
+
children: [
|
|
393
|
+
/* @__PURE__ */ jsx(CalendarFoldIcon, {}),
|
|
394
|
+
"Replace schedule"
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
) })
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
),
|
|
401
|
+
/* @__PURE__ */ jsxs("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
402
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
403
|
+
/* @__PURE__ */ jsx("h2", { className: "font-display text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
|
|
404
|
+
schedule?.description && /* @__PURE__ */ jsx("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
|
|
405
|
+
] }),
|
|
406
|
+
/* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
407
|
+
timezoneLabel && /* @__PURE__ */ jsx("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
408
|
+
/* @__PURE__ */ jsx("div", { className: "w-full p-10 box-border", children: loading ? /* @__PURE__ */ jsx(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx(EmptyState, { inEditor }) : /* @__PURE__ */ jsx(
|
|
409
|
+
ScheduleView,
|
|
410
|
+
{
|
|
411
|
+
schedule,
|
|
412
|
+
dates,
|
|
413
|
+
selectedIdx,
|
|
414
|
+
onSelectDate: setSelectedIdx
|
|
415
|
+
}
|
|
416
|
+
) })
|
|
417
|
+
] })
|
|
418
|
+
] })
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
}
|
|
5
423
|
|
|
6
424
|
// src/ui/toggle-group.tsx
|
|
7
|
-
import * as
|
|
425
|
+
import * as React2 from "react";
|
|
8
426
|
|
|
9
427
|
// node_modules/clsx/dist/clsx.mjs
|
|
10
428
|
function r(e) {
|
|
@@ -3324,7 +3742,7 @@ var cva = (base, config) => (props) => {
|
|
|
3324
3742
|
|
|
3325
3743
|
// src/ui/toggle.tsx
|
|
3326
3744
|
import { Toggle as TogglePrimitive } from "radix-ui";
|
|
3327
|
-
import { jsx } from "react/jsx-runtime";
|
|
3745
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
3328
3746
|
var toggleVariants = cva(
|
|
3329
3747
|
"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",
|
|
3330
3748
|
{
|
|
@@ -3351,7 +3769,7 @@ function Toggle({
|
|
|
3351
3769
|
size,
|
|
3352
3770
|
...props
|
|
3353
3771
|
}) {
|
|
3354
|
-
return /* @__PURE__ */
|
|
3772
|
+
return /* @__PURE__ */ jsx2(
|
|
3355
3773
|
TogglePrimitive.Root,
|
|
3356
3774
|
{
|
|
3357
3775
|
"data-slot": "toggle",
|
|
@@ -3362,8 +3780,8 @@ function Toggle({
|
|
|
3362
3780
|
}
|
|
3363
3781
|
|
|
3364
3782
|
// src/ui/toggle-group.tsx
|
|
3365
|
-
import { jsx as
|
|
3366
|
-
var ToggleGroupContext =
|
|
3783
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
3784
|
+
var ToggleGroupContext = React2.createContext({
|
|
3367
3785
|
value: "",
|
|
3368
3786
|
onValueChange: () => {
|
|
3369
3787
|
}
|
|
@@ -3375,13 +3793,13 @@ function ToggleGroup({
|
|
|
3375
3793
|
children,
|
|
3376
3794
|
...props
|
|
3377
3795
|
}) {
|
|
3378
|
-
return /* @__PURE__ */
|
|
3796
|
+
return /* @__PURE__ */ jsx3(
|
|
3379
3797
|
"div",
|
|
3380
3798
|
{
|
|
3381
3799
|
role: "group",
|
|
3382
3800
|
className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
|
|
3383
3801
|
...props,
|
|
3384
|
-
children: /* @__PURE__ */
|
|
3802
|
+
children: /* @__PURE__ */ jsx3(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
|
|
3385
3803
|
}
|
|
3386
3804
|
);
|
|
3387
3805
|
}
|
|
@@ -3391,8 +3809,8 @@ function ToggleGroupItem({
|
|
|
3391
3809
|
children,
|
|
3392
3810
|
...props
|
|
3393
3811
|
}) {
|
|
3394
|
-
const { value: groupValue, onValueChange } =
|
|
3395
|
-
return /* @__PURE__ */
|
|
3812
|
+
const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
|
|
3813
|
+
return /* @__PURE__ */ jsx3(
|
|
3396
3814
|
Toggle,
|
|
3397
3815
|
{
|
|
3398
3816
|
pressed: groupValue === value,
|
|
@@ -3434,7 +3852,7 @@ function isEditSessionActive() {
|
|
|
3434
3852
|
}
|
|
3435
3853
|
|
|
3436
3854
|
// src/ui/badge.tsx
|
|
3437
|
-
import { jsx as
|
|
3855
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
3438
3856
|
var badgeVariants = cva(
|
|
3439
3857
|
"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",
|
|
3440
3858
|
{
|
|
@@ -3452,11 +3870,11 @@ var badgeVariants = cva(
|
|
|
3452
3870
|
}
|
|
3453
3871
|
);
|
|
3454
3872
|
function Badge({ className, variant, ...props }) {
|
|
3455
|
-
return /* @__PURE__ */
|
|
3873
|
+
return /* @__PURE__ */ jsx4("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
3456
3874
|
}
|
|
3457
3875
|
|
|
3458
3876
|
// src/OhhwellsBridge.tsx
|
|
3459
|
-
import { Fragment, jsx as
|
|
3877
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
3460
3878
|
var PRIMARY = "#0885FE";
|
|
3461
3879
|
var IMAGE_FADE_MS = 300;
|
|
3462
3880
|
function runOpacityFade(el, onDone) {
|
|
@@ -3507,6 +3925,41 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
3507
3925
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
3508
3926
|
});
|
|
3509
3927
|
}
|
|
3928
|
+
function getSectionsTracker() {
|
|
3929
|
+
let el = document.querySelector("[data-ohw-sections-tracker]");
|
|
3930
|
+
if (!el) {
|
|
3931
|
+
el = document.createElement("div");
|
|
3932
|
+
el.setAttribute("data-ohw-sections-tracker", "");
|
|
3933
|
+
el.style.display = "none";
|
|
3934
|
+
document.body.appendChild(el);
|
|
3935
|
+
}
|
|
3936
|
+
return el;
|
|
3937
|
+
}
|
|
3938
|
+
function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId) {
|
|
3939
|
+
const anchor = document.querySelector(`[data-ohw-section="${insertAfter}"]`);
|
|
3940
|
+
if (!anchor) return false;
|
|
3941
|
+
if (anchor.nextElementSibling?.dataset.ohwSectionContainer) return false;
|
|
3942
|
+
const container = document.createElement("div");
|
|
3943
|
+
container.dataset.ohwSectionContainer = "scheduling";
|
|
3944
|
+
anchor.insertAdjacentElement("afterend", container);
|
|
3945
|
+
createRoot(container).render(/* @__PURE__ */ jsx5(SchedulingWidget, { notifyOnConnect, initialScheduleId: scheduleId }));
|
|
3946
|
+
const tracker = getSectionsTracker();
|
|
3947
|
+
let sections = [];
|
|
3948
|
+
try {
|
|
3949
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
3950
|
+
} catch {
|
|
3951
|
+
}
|
|
3952
|
+
if (!sections.find((s) => s.type === "scheduling" && s.insertAfter === insertAfter)) {
|
|
3953
|
+
sections.push({
|
|
3954
|
+
type: "scheduling",
|
|
3955
|
+
insertAfter,
|
|
3956
|
+
pagePath: window.location.pathname,
|
|
3957
|
+
...scheduleId ? { scheduleId } : {}
|
|
3958
|
+
});
|
|
3959
|
+
tracker.textContent = JSON.stringify(sections);
|
|
3960
|
+
}
|
|
3961
|
+
return true;
|
|
3962
|
+
}
|
|
3510
3963
|
function collectEditableNodes() {
|
|
3511
3964
|
return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
3512
3965
|
if (el.dataset.ohwEditable === "image") {
|
|
@@ -3664,7 +4117,7 @@ var TOOLBAR_GROUPS = [
|
|
|
3664
4117
|
];
|
|
3665
4118
|
function GlowFrame({ rect, elRef }) {
|
|
3666
4119
|
const GAP = 6;
|
|
3667
|
-
return /* @__PURE__ */
|
|
4120
|
+
return /* @__PURE__ */ jsx5(
|
|
3668
4121
|
"div",
|
|
3669
4122
|
{
|
|
3670
4123
|
ref: elRef,
|
|
@@ -3715,7 +4168,7 @@ function FloatingToolbar({
|
|
|
3715
4168
|
activeCommands
|
|
3716
4169
|
}) {
|
|
3717
4170
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
3718
|
-
return /* @__PURE__ */
|
|
4171
|
+
return /* @__PURE__ */ jsx5(
|
|
3719
4172
|
"div",
|
|
3720
4173
|
{
|
|
3721
4174
|
ref: elRef,
|
|
@@ -3738,11 +4191,11 @@ function FloatingToolbar({
|
|
|
3738
4191
|
pointerEvents: "auto",
|
|
3739
4192
|
whiteSpace: "nowrap"
|
|
3740
4193
|
},
|
|
3741
|
-
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */
|
|
3742
|
-
gi > 0 && /* @__PURE__ */
|
|
4194
|
+
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
|
|
4195
|
+
gi > 0 && /* @__PURE__ */ jsx5("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
|
|
3743
4196
|
btns.map((btn) => {
|
|
3744
4197
|
const isActive = activeCommands.has(btn.cmd);
|
|
3745
|
-
return /* @__PURE__ */
|
|
4198
|
+
return /* @__PURE__ */ jsx5(
|
|
3746
4199
|
"button",
|
|
3747
4200
|
{
|
|
3748
4201
|
title: btn.title,
|
|
@@ -3768,7 +4221,7 @@ function FloatingToolbar({
|
|
|
3768
4221
|
flexShrink: 0,
|
|
3769
4222
|
padding: 6
|
|
3770
4223
|
},
|
|
3771
|
-
children: /* @__PURE__ */
|
|
4224
|
+
children: /* @__PURE__ */ jsx5(
|
|
3772
4225
|
"svg",
|
|
3773
4226
|
{
|
|
3774
4227
|
width: "16",
|
|
@@ -3801,7 +4254,7 @@ function StateToggle({
|
|
|
3801
4254
|
states,
|
|
3802
4255
|
onStateChange
|
|
3803
4256
|
}) {
|
|
3804
|
-
return /* @__PURE__ */
|
|
4257
|
+
return /* @__PURE__ */ jsx5(
|
|
3805
4258
|
ToggleGroup,
|
|
3806
4259
|
{
|
|
3807
4260
|
"data-ohw-state-toggle": "",
|
|
@@ -3815,7 +4268,7 @@ function StateToggle({
|
|
|
3815
4268
|
left: rect.right - 8,
|
|
3816
4269
|
transform: "translateX(-100%)"
|
|
3817
4270
|
},
|
|
3818
|
-
children: states.map((state) => /* @__PURE__ */
|
|
4271
|
+
children: states.map((state) => /* @__PURE__ */ jsx5(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
|
|
3819
4272
|
}
|
|
3820
4273
|
);
|
|
3821
4274
|
}
|
|
@@ -3825,8 +4278,8 @@ function OhhwellsBridge() {
|
|
|
3825
4278
|
const router = useRouter();
|
|
3826
4279
|
const searchParams = useSearchParams();
|
|
3827
4280
|
const isEditMode = isEditSessionActive();
|
|
3828
|
-
const [bridgeRoot, setBridgeRoot] =
|
|
3829
|
-
|
|
4281
|
+
const [bridgeRoot, setBridgeRoot] = useState2(null);
|
|
4282
|
+
useEffect2(() => {
|
|
3830
4283
|
const figtreeFontId = "ohw-figtree-font";
|
|
3831
4284
|
if (!document.getElementById(figtreeFontId)) {
|
|
3832
4285
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -3859,7 +4312,7 @@ function OhhwellsBridge() {
|
|
|
3859
4312
|
window.parent.postMessage(data, "*");
|
|
3860
4313
|
}
|
|
3861
4314
|
}, []);
|
|
3862
|
-
const [fetchState, setFetchState] =
|
|
4315
|
+
const [fetchState, setFetchState] = useState2("idle");
|
|
3863
4316
|
const autoSaveTimers = useRef(/* @__PURE__ */ new Map());
|
|
3864
4317
|
const activeElRef = useRef(null);
|
|
3865
4318
|
const originalContentRef = useRef(null);
|
|
@@ -3881,12 +4334,12 @@ function OhhwellsBridge() {
|
|
|
3881
4334
|
});
|
|
3882
4335
|
const postToParentRef = useRef(postToParent);
|
|
3883
4336
|
postToParentRef.current = postToParent;
|
|
3884
|
-
const [toolbarRect, setToolbarRect] =
|
|
3885
|
-
const [toggleState, setToggleState] =
|
|
3886
|
-
const [maxBadge, setMaxBadge] =
|
|
3887
|
-
const [activeCommands, setActiveCommands] =
|
|
3888
|
-
const [sectionGap, setSectionGap] =
|
|
3889
|
-
|
|
4337
|
+
const [toolbarRect, setToolbarRect] = useState2(null);
|
|
4338
|
+
const [toggleState, setToggleState] = useState2(null);
|
|
4339
|
+
const [maxBadge, setMaxBadge] = useState2(null);
|
|
4340
|
+
const [activeCommands, setActiveCommands] = useState2(/* @__PURE__ */ new Set());
|
|
4341
|
+
const [sectionGap, setSectionGap] = useState2(null);
|
|
4342
|
+
useEffect2(() => {
|
|
3890
4343
|
const update = () => {
|
|
3891
4344
|
const el = activeElRef.current;
|
|
3892
4345
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -3963,7 +4416,20 @@ function OhhwellsBridge() {
|
|
|
3963
4416
|
}
|
|
3964
4417
|
const applyContent = (content) => {
|
|
3965
4418
|
const imageLoads = [];
|
|
4419
|
+
document.querySelectorAll("[data-ohw-section-container]").forEach((el) => el.remove());
|
|
4420
|
+
if (content["__ohw_sections"]) {
|
|
4421
|
+
try {
|
|
4422
|
+
const entries = JSON.parse(content["__ohw_sections"]);
|
|
4423
|
+
const currentPath = window.location.pathname;
|
|
4424
|
+
entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
|
|
4425
|
+
if (pagePath && pagePath !== currentPath) return;
|
|
4426
|
+
if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
|
|
4427
|
+
});
|
|
4428
|
+
} catch {
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
3966
4431
|
for (const [key, val] of Object.entries(content)) {
|
|
4432
|
+
if (key === "__ohw_sections") continue;
|
|
3967
4433
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3968
4434
|
if (el.dataset.ohwEditable === "image") {
|
|
3969
4435
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4006,12 +4472,24 @@ function OhhwellsBridge() {
|
|
|
4006
4472
|
cancelled = true;
|
|
4007
4473
|
};
|
|
4008
4474
|
}, [subdomain, isEditMode, pathname]);
|
|
4009
|
-
|
|
4475
|
+
useEffect2(() => {
|
|
4010
4476
|
if (!subdomain || isEditMode) return;
|
|
4011
4477
|
const applyFromCache = () => {
|
|
4012
4478
|
const content = contentCache.get(subdomain);
|
|
4013
4479
|
if (!content) return;
|
|
4480
|
+
if (content["__ohw_sections"]) {
|
|
4481
|
+
try {
|
|
4482
|
+
const entries = JSON.parse(content["__ohw_sections"]);
|
|
4483
|
+
const currentPath = window.location.pathname;
|
|
4484
|
+
entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
|
|
4485
|
+
if (pagePath && pagePath !== currentPath) return;
|
|
4486
|
+
if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
|
|
4487
|
+
});
|
|
4488
|
+
} catch {
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4014
4491
|
for (const [key, val] of Object.entries(content)) {
|
|
4492
|
+
if (key === "__ohw_sections") continue;
|
|
4015
4493
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4016
4494
|
if (el.dataset.ohwEditable === "image") {
|
|
4017
4495
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4036,10 +4514,10 @@ function OhhwellsBridge() {
|
|
|
4036
4514
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
4037
4515
|
el.style.display = visible ? "flex" : "none";
|
|
4038
4516
|
}, [subdomain, fetchState]);
|
|
4039
|
-
|
|
4517
|
+
useEffect2(() => {
|
|
4040
4518
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
4041
4519
|
}, [pathname, postToParent]);
|
|
4042
|
-
|
|
4520
|
+
useEffect2(() => {
|
|
4043
4521
|
if (!isEditMode) return;
|
|
4044
4522
|
const measure = () => {
|
|
4045
4523
|
const h = document.body.scrollHeight;
|
|
@@ -4063,7 +4541,7 @@ function OhhwellsBridge() {
|
|
|
4063
4541
|
window.removeEventListener("resize", handleResize);
|
|
4064
4542
|
};
|
|
4065
4543
|
}, [pathname, isEditMode, postToParent]);
|
|
4066
|
-
|
|
4544
|
+
useEffect2(() => {
|
|
4067
4545
|
if (!subdomainFromQuery || isEditMode) return;
|
|
4068
4546
|
const handleClick = (e) => {
|
|
4069
4547
|
const anchor = e.target.closest("a");
|
|
@@ -4079,7 +4557,7 @@ function OhhwellsBridge() {
|
|
|
4079
4557
|
document.addEventListener("click", handleClick, true);
|
|
4080
4558
|
return () => document.removeEventListener("click", handleClick, true);
|
|
4081
4559
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
4082
|
-
|
|
4560
|
+
useEffect2(() => {
|
|
4083
4561
|
if (!isEditMode) {
|
|
4084
4562
|
editStylesRef.current?.base.remove();
|
|
4085
4563
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -4339,6 +4817,15 @@ function OhhwellsBridge() {
|
|
|
4339
4817
|
textEditable.setAttribute("data-ohw-hovered", "");
|
|
4340
4818
|
return;
|
|
4341
4819
|
}
|
|
4820
|
+
if (hoveredGapRef.current) {
|
|
4821
|
+
if (hoveredImageRef.current) {
|
|
4822
|
+
hoveredImageRef.current = null;
|
|
4823
|
+
resumeAnimTracks();
|
|
4824
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4825
|
+
}
|
|
4826
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4827
|
+
return;
|
|
4828
|
+
}
|
|
4342
4829
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4343
4830
|
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
4344
4831
|
hoveredImageRef.current = imgEl;
|
|
@@ -4427,17 +4914,17 @@ function OhhwellsBridge() {
|
|
|
4427
4914
|
};
|
|
4428
4915
|
const handleMouseMove = (e) => {
|
|
4429
4916
|
const { clientX, clientY } = e;
|
|
4917
|
+
probeSectionGapAt(clientX, clientY);
|
|
4430
4918
|
probeImageAt(clientX, clientY);
|
|
4431
4919
|
probeHoverCardsAt(clientX, clientY);
|
|
4432
|
-
probeSectionGapAt(clientX, clientY);
|
|
4433
4920
|
};
|
|
4434
4921
|
const handlePointerSync = (e) => {
|
|
4435
4922
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
4436
4923
|
const { clientX, clientY } = e.data;
|
|
4437
4924
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
4925
|
+
probeSectionGapAt(clientX, clientY);
|
|
4438
4926
|
probeImageAt(clientX, clientY);
|
|
4439
4927
|
probeHoverCardsAt(clientX, clientY);
|
|
4440
|
-
probeSectionGapAt(clientX, clientY);
|
|
4441
4928
|
};
|
|
4442
4929
|
const handleDragOver = (e) => {
|
|
4443
4930
|
e.preventDefault();
|
|
@@ -4594,6 +5081,18 @@ function OhhwellsBridge() {
|
|
|
4594
5081
|
const content = e.data.content;
|
|
4595
5082
|
if (!content) return;
|
|
4596
5083
|
for (const [key, val] of Object.entries(content)) {
|
|
5084
|
+
if (key === "__ohw_sections") {
|
|
5085
|
+
try {
|
|
5086
|
+
const entries = JSON.parse(val);
|
|
5087
|
+
const tracker = getSectionsTracker();
|
|
5088
|
+
tracker.textContent = val;
|
|
5089
|
+
entries.forEach(({ type, insertAfter, scheduleId }) => {
|
|
5090
|
+
if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
|
|
5091
|
+
});
|
|
5092
|
+
} catch {
|
|
5093
|
+
}
|
|
5094
|
+
continue;
|
|
5095
|
+
}
|
|
4597
5096
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4598
5097
|
if (el.dataset.ohwEditable === "image") {
|
|
4599
5098
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4648,7 +5147,71 @@ function OhhwellsBridge() {
|
|
|
4648
5147
|
};
|
|
4649
5148
|
const handleSave = (e) => {
|
|
4650
5149
|
if (e.data?.type !== "ow:save") return;
|
|
4651
|
-
|
|
5150
|
+
const nodes = collectEditableNodes();
|
|
5151
|
+
const tracker = document.querySelector("[data-ohw-sections-tracker]");
|
|
5152
|
+
if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
|
|
5153
|
+
postToParentRef.current({ type: "ow:save-result", nodes });
|
|
5154
|
+
};
|
|
5155
|
+
const handleInsertSection = (e) => {
|
|
5156
|
+
if (e.data?.type !== "ow:insert-section") return;
|
|
5157
|
+
const { widgetType, insertAfter } = e.data;
|
|
5158
|
+
if (widgetType !== "scheduling") return;
|
|
5159
|
+
const inserted = mountSchedulingWidget(insertAfter, true);
|
|
5160
|
+
if (inserted) {
|
|
5161
|
+
const tracker = getSectionsTracker();
|
|
5162
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
|
|
5163
|
+
const h = document.documentElement.scrollHeight;
|
|
5164
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
5165
|
+
}
|
|
5166
|
+
};
|
|
5167
|
+
const handleSwitchSchedule = (e) => {
|
|
5168
|
+
if (e.data?.type !== "ow:switch-schedule") return;
|
|
5169
|
+
const scheduleId = e.data.scheduleId;
|
|
5170
|
+
if (!scheduleId) return;
|
|
5171
|
+
const tracker = getSectionsTracker();
|
|
5172
|
+
let sections = [];
|
|
5173
|
+
try {
|
|
5174
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5175
|
+
} catch {
|
|
5176
|
+
}
|
|
5177
|
+
const currentPath = window.location.pathname;
|
|
5178
|
+
const updated = sections.map(
|
|
5179
|
+
(s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId } : s
|
|
5180
|
+
);
|
|
5181
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5182
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
5183
|
+
};
|
|
5184
|
+
const handleClearSchedulingWidget = (e) => {
|
|
5185
|
+
if (e.data?.type !== "ow:clear-scheduling-widget") return;
|
|
5186
|
+
const tracker = getSectionsTracker();
|
|
5187
|
+
let sections = [];
|
|
5188
|
+
try {
|
|
5189
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5190
|
+
} catch {
|
|
5191
|
+
}
|
|
5192
|
+
const currentPath = window.location.pathname;
|
|
5193
|
+
const updated = sections.map(
|
|
5194
|
+
(s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId: null } : s
|
|
5195
|
+
);
|
|
5196
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5197
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
5198
|
+
};
|
|
5199
|
+
const handleRemoveSchedulingSection = (e) => {
|
|
5200
|
+
if (e.data?.type !== "ow:remove-scheduling-section") return;
|
|
5201
|
+
const els = document.querySelectorAll('[data-ohw-section="scheduling"]');
|
|
5202
|
+
els.forEach((el) => el.parentElement?.removeChild(el));
|
|
5203
|
+
const tracker = getSectionsTracker();
|
|
5204
|
+
let sections = [];
|
|
5205
|
+
try {
|
|
5206
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5207
|
+
} catch {
|
|
5208
|
+
}
|
|
5209
|
+
const currentPath = window.location.pathname;
|
|
5210
|
+
const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
|
|
5211
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5212
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
5213
|
+
const h = document.documentElement.scrollHeight;
|
|
5214
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
4652
5215
|
};
|
|
4653
5216
|
const handleSelectionChange = () => {
|
|
4654
5217
|
if (!activeElRef.current) return;
|
|
@@ -4753,6 +5316,10 @@ function OhhwellsBridge() {
|
|
|
4753
5316
|
deactivateRef.current();
|
|
4754
5317
|
};
|
|
4755
5318
|
window.addEventListener("message", handleSave);
|
|
5319
|
+
window.addEventListener("message", handleInsertSection);
|
|
5320
|
+
window.addEventListener("message", handleSwitchSchedule);
|
|
5321
|
+
window.addEventListener("message", handleClearSchedulingWidget);
|
|
5322
|
+
window.addEventListener("message", handleRemoveSchedulingSection);
|
|
4756
5323
|
window.addEventListener("message", handleImageUrl);
|
|
4757
5324
|
window.addEventListener("message", handleAnimLock);
|
|
4758
5325
|
window.addEventListener("message", handleCanvasHeight);
|
|
@@ -4787,6 +5354,10 @@ function OhhwellsBridge() {
|
|
|
4787
5354
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
4788
5355
|
window.removeEventListener("scroll", handleScroll, true);
|
|
4789
5356
|
window.removeEventListener("message", handleSave);
|
|
5357
|
+
window.removeEventListener("message", handleInsertSection);
|
|
5358
|
+
window.removeEventListener("message", handleSwitchSchedule);
|
|
5359
|
+
window.removeEventListener("message", handleClearSchedulingWidget);
|
|
5360
|
+
window.removeEventListener("message", handleRemoveSchedulingSection);
|
|
4790
5361
|
window.removeEventListener("message", handleImageUrl);
|
|
4791
5362
|
window.removeEventListener("message", handleAnimLock);
|
|
4792
5363
|
window.removeEventListener("message", handleCanvasHeight);
|
|
@@ -4801,7 +5372,7 @@ function OhhwellsBridge() {
|
|
|
4801
5372
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
4802
5373
|
};
|
|
4803
5374
|
}, [isEditMode, refreshStateRules]);
|
|
4804
|
-
|
|
5375
|
+
useEffect2(() => {
|
|
4805
5376
|
if (!isEditMode) return;
|
|
4806
5377
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
4807
5378
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -4843,12 +5414,12 @@ function OhhwellsBridge() {
|
|
|
4843
5414
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
4844
5415
|
}, [deactivate]);
|
|
4845
5416
|
return bridgeRoot ? createPortal(
|
|
4846
|
-
/* @__PURE__ */
|
|
4847
|
-
toolbarRect && /* @__PURE__ */
|
|
4848
|
-
/* @__PURE__ */
|
|
4849
|
-
/* @__PURE__ */
|
|
5417
|
+
/* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
5418
|
+
toolbarRect && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
5419
|
+
/* @__PURE__ */ jsx5(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
5420
|
+
/* @__PURE__ */ jsx5(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
|
|
4850
5421
|
] }),
|
|
4851
|
-
maxBadge && /* @__PURE__ */
|
|
5422
|
+
maxBadge && /* @__PURE__ */ jsxs2(
|
|
4852
5423
|
"div",
|
|
4853
5424
|
{
|
|
4854
5425
|
"data-ohw-max-badge": "",
|
|
@@ -4874,7 +5445,7 @@ function OhhwellsBridge() {
|
|
|
4874
5445
|
]
|
|
4875
5446
|
}
|
|
4876
5447
|
),
|
|
4877
|
-
toggleState && /* @__PURE__ */
|
|
5448
|
+
toggleState && /* @__PURE__ */ jsx5(
|
|
4878
5449
|
StateToggle,
|
|
4879
5450
|
{
|
|
4880
5451
|
rect: toggleState.rect,
|
|
@@ -4883,15 +5454,15 @@ function OhhwellsBridge() {
|
|
|
4883
5454
|
onStateChange: handleStateChange
|
|
4884
5455
|
}
|
|
4885
5456
|
),
|
|
4886
|
-
sectionGap && /* @__PURE__ */
|
|
5457
|
+
sectionGap && /* @__PURE__ */ jsxs2(
|
|
4887
5458
|
"div",
|
|
4888
5459
|
{
|
|
4889
5460
|
"data-ohw-section-insert-line": "",
|
|
4890
5461
|
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
4891
5462
|
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
4892
5463
|
children: [
|
|
4893
|
-
/* @__PURE__ */
|
|
4894
|
-
/* @__PURE__ */
|
|
5464
|
+
/* @__PURE__ */ jsx5("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
5465
|
+
/* @__PURE__ */ jsx5(
|
|
4895
5466
|
Badge,
|
|
4896
5467
|
{
|
|
4897
5468
|
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",
|
|
@@ -4904,7 +5475,7 @@ function OhhwellsBridge() {
|
|
|
4904
5475
|
children: "Add Section"
|
|
4905
5476
|
}
|
|
4906
5477
|
),
|
|
4907
|
-
/* @__PURE__ */
|
|
5478
|
+
/* @__PURE__ */ jsx5("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
4908
5479
|
]
|
|
4909
5480
|
}
|
|
4910
5481
|
)
|
|
@@ -4914,6 +5485,7 @@ function OhhwellsBridge() {
|
|
|
4914
5485
|
}
|
|
4915
5486
|
export {
|
|
4916
5487
|
OhhwellsBridge,
|
|
5488
|
+
SchedulingWidget,
|
|
4917
5489
|
Toggle,
|
|
4918
5490
|
ToggleGroup,
|
|
4919
5491
|
ToggleGroupItem,
|