@ohhwells/bridge 0.1.17 → 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 +714 -63
- 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 +690 -40
- package/dist/index.js.map +1 -1
- package/dist/styles.css +382 -2
- package/package.json +2 -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,
|
|
@@ -3433,8 +3851,30 @@ function isEditSessionActive() {
|
|
|
3433
3851
|
return new URLSearchParams(q).get("mode") === "edit";
|
|
3434
3852
|
}
|
|
3435
3853
|
|
|
3854
|
+
// src/ui/badge.tsx
|
|
3855
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
3856
|
+
var badgeVariants = cva(
|
|
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",
|
|
3858
|
+
{
|
|
3859
|
+
variants: {
|
|
3860
|
+
variant: {
|
|
3861
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
3862
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
3863
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
3864
|
+
outline: "text-foreground"
|
|
3865
|
+
}
|
|
3866
|
+
},
|
|
3867
|
+
defaultVariants: {
|
|
3868
|
+
variant: "default"
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
);
|
|
3872
|
+
function Badge({ className, variant, ...props }) {
|
|
3873
|
+
return /* @__PURE__ */ jsx4("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3436
3876
|
// src/OhhwellsBridge.tsx
|
|
3437
|
-
import { Fragment, jsx as
|
|
3877
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
3438
3878
|
var PRIMARY = "#0885FE";
|
|
3439
3879
|
var IMAGE_FADE_MS = 300;
|
|
3440
3880
|
function runOpacityFade(el, onDone) {
|
|
@@ -3485,6 +3925,41 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
3485
3925
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
3486
3926
|
});
|
|
3487
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
|
+
}
|
|
3488
3963
|
function collectEditableNodes() {
|
|
3489
3964
|
return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
3490
3965
|
if (el.dataset.ohwEditable === "image") {
|
|
@@ -3642,7 +4117,7 @@ var TOOLBAR_GROUPS = [
|
|
|
3642
4117
|
];
|
|
3643
4118
|
function GlowFrame({ rect, elRef }) {
|
|
3644
4119
|
const GAP = 6;
|
|
3645
|
-
return /* @__PURE__ */
|
|
4120
|
+
return /* @__PURE__ */ jsx5(
|
|
3646
4121
|
"div",
|
|
3647
4122
|
{
|
|
3648
4123
|
ref: elRef,
|
|
@@ -3693,7 +4168,7 @@ function FloatingToolbar({
|
|
|
3693
4168
|
activeCommands
|
|
3694
4169
|
}) {
|
|
3695
4170
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
3696
|
-
return /* @__PURE__ */
|
|
4171
|
+
return /* @__PURE__ */ jsx5(
|
|
3697
4172
|
"div",
|
|
3698
4173
|
{
|
|
3699
4174
|
ref: elRef,
|
|
@@ -3716,11 +4191,11 @@ function FloatingToolbar({
|
|
|
3716
4191
|
pointerEvents: "auto",
|
|
3717
4192
|
whiteSpace: "nowrap"
|
|
3718
4193
|
},
|
|
3719
|
-
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */
|
|
3720
|
-
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 } }),
|
|
3721
4196
|
btns.map((btn) => {
|
|
3722
4197
|
const isActive = activeCommands.has(btn.cmd);
|
|
3723
|
-
return /* @__PURE__ */
|
|
4198
|
+
return /* @__PURE__ */ jsx5(
|
|
3724
4199
|
"button",
|
|
3725
4200
|
{
|
|
3726
4201
|
title: btn.title,
|
|
@@ -3746,7 +4221,7 @@ function FloatingToolbar({
|
|
|
3746
4221
|
flexShrink: 0,
|
|
3747
4222
|
padding: 6
|
|
3748
4223
|
},
|
|
3749
|
-
children: /* @__PURE__ */
|
|
4224
|
+
children: /* @__PURE__ */ jsx5(
|
|
3750
4225
|
"svg",
|
|
3751
4226
|
{
|
|
3752
4227
|
width: "16",
|
|
@@ -3779,7 +4254,7 @@ function StateToggle({
|
|
|
3779
4254
|
states,
|
|
3780
4255
|
onStateChange
|
|
3781
4256
|
}) {
|
|
3782
|
-
return /* @__PURE__ */
|
|
4257
|
+
return /* @__PURE__ */ jsx5(
|
|
3783
4258
|
ToggleGroup,
|
|
3784
4259
|
{
|
|
3785
4260
|
"data-ohw-state-toggle": "",
|
|
@@ -3793,7 +4268,7 @@ function StateToggle({
|
|
|
3793
4268
|
left: rect.right - 8,
|
|
3794
4269
|
transform: "translateX(-100%)"
|
|
3795
4270
|
},
|
|
3796
|
-
children: states.map((state) => /* @__PURE__ */
|
|
4271
|
+
children: states.map((state) => /* @__PURE__ */ jsx5(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
|
|
3797
4272
|
}
|
|
3798
4273
|
);
|
|
3799
4274
|
}
|
|
@@ -3803,8 +4278,8 @@ function OhhwellsBridge() {
|
|
|
3803
4278
|
const router = useRouter();
|
|
3804
4279
|
const searchParams = useSearchParams();
|
|
3805
4280
|
const isEditMode = isEditSessionActive();
|
|
3806
|
-
const [bridgeRoot, setBridgeRoot] =
|
|
3807
|
-
|
|
4281
|
+
const [bridgeRoot, setBridgeRoot] = useState2(null);
|
|
4282
|
+
useEffect2(() => {
|
|
3808
4283
|
const figtreeFontId = "ohw-figtree-font";
|
|
3809
4284
|
if (!document.getElementById(figtreeFontId)) {
|
|
3810
4285
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -3837,7 +4312,7 @@ function OhhwellsBridge() {
|
|
|
3837
4312
|
window.parent.postMessage(data, "*");
|
|
3838
4313
|
}
|
|
3839
4314
|
}, []);
|
|
3840
|
-
const [fetchState, setFetchState] =
|
|
4315
|
+
const [fetchState, setFetchState] = useState2("idle");
|
|
3841
4316
|
const autoSaveTimers = useRef(/* @__PURE__ */ new Map());
|
|
3842
4317
|
const activeElRef = useRef(null);
|
|
3843
4318
|
const originalContentRef = useRef(null);
|
|
@@ -3847,6 +4322,7 @@ function OhhwellsBridge() {
|
|
|
3847
4322
|
const glowElRef = useRef(null);
|
|
3848
4323
|
const hoveredImageRef = useRef(null);
|
|
3849
4324
|
const hoveredImageHasTextOverlapRef = useRef(false);
|
|
4325
|
+
const hoveredGapRef = useRef(null);
|
|
3850
4326
|
const imageUnhoverTimerRef = useRef(null);
|
|
3851
4327
|
const imageShowTimerRef = useRef(null);
|
|
3852
4328
|
const editStylesRef = useRef(null);
|
|
@@ -3858,11 +4334,12 @@ function OhhwellsBridge() {
|
|
|
3858
4334
|
});
|
|
3859
4335
|
const postToParentRef = useRef(postToParent);
|
|
3860
4336
|
postToParentRef.current = postToParent;
|
|
3861
|
-
const [toolbarRect, setToolbarRect] =
|
|
3862
|
-
const [toggleState, setToggleState] =
|
|
3863
|
-
const [maxBadge, setMaxBadge] =
|
|
3864
|
-
const [activeCommands, setActiveCommands] =
|
|
3865
|
-
|
|
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(() => {
|
|
3866
4343
|
const update = () => {
|
|
3867
4344
|
const el = activeElRef.current;
|
|
3868
4345
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -3870,6 +4347,12 @@ function OhhwellsBridge() {
|
|
|
3870
4347
|
if (!prev || !activeStateElRef.current) return prev;
|
|
3871
4348
|
return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
|
|
3872
4349
|
});
|
|
4350
|
+
setSectionGap((prev) => {
|
|
4351
|
+
if (!prev) return prev;
|
|
4352
|
+
const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
|
|
4353
|
+
if (!anchor) return prev;
|
|
4354
|
+
return { ...prev, y: anchor.getBoundingClientRect().bottom };
|
|
4355
|
+
});
|
|
3873
4356
|
};
|
|
3874
4357
|
const vvp = window.visualViewport;
|
|
3875
4358
|
if (!vvp) return;
|
|
@@ -3933,7 +4416,20 @@ function OhhwellsBridge() {
|
|
|
3933
4416
|
}
|
|
3934
4417
|
const applyContent = (content) => {
|
|
3935
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
|
+
}
|
|
3936
4431
|
for (const [key, val] of Object.entries(content)) {
|
|
4432
|
+
if (key === "__ohw_sections") continue;
|
|
3937
4433
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3938
4434
|
if (el.dataset.ohwEditable === "image") {
|
|
3939
4435
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -3976,12 +4472,24 @@ function OhhwellsBridge() {
|
|
|
3976
4472
|
cancelled = true;
|
|
3977
4473
|
};
|
|
3978
4474
|
}, [subdomain, isEditMode, pathname]);
|
|
3979
|
-
|
|
4475
|
+
useEffect2(() => {
|
|
3980
4476
|
if (!subdomain || isEditMode) return;
|
|
3981
4477
|
const applyFromCache = () => {
|
|
3982
4478
|
const content = contentCache.get(subdomain);
|
|
3983
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
|
+
}
|
|
3984
4491
|
for (const [key, val] of Object.entries(content)) {
|
|
4492
|
+
if (key === "__ohw_sections") continue;
|
|
3985
4493
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3986
4494
|
if (el.dataset.ohwEditable === "image") {
|
|
3987
4495
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4006,10 +4514,10 @@ function OhhwellsBridge() {
|
|
|
4006
4514
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
4007
4515
|
el.style.display = visible ? "flex" : "none";
|
|
4008
4516
|
}, [subdomain, fetchState]);
|
|
4009
|
-
|
|
4517
|
+
useEffect2(() => {
|
|
4010
4518
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
4011
4519
|
}, [pathname, postToParent]);
|
|
4012
|
-
|
|
4520
|
+
useEffect2(() => {
|
|
4013
4521
|
if (!isEditMode) return;
|
|
4014
4522
|
const measure = () => {
|
|
4015
4523
|
const h = document.body.scrollHeight;
|
|
@@ -4033,7 +4541,7 @@ function OhhwellsBridge() {
|
|
|
4033
4541
|
window.removeEventListener("resize", handleResize);
|
|
4034
4542
|
};
|
|
4035
4543
|
}, [pathname, isEditMode, postToParent]);
|
|
4036
|
-
|
|
4544
|
+
useEffect2(() => {
|
|
4037
4545
|
if (!subdomainFromQuery || isEditMode) return;
|
|
4038
4546
|
const handleClick = (e) => {
|
|
4039
4547
|
const anchor = e.target.closest("a");
|
|
@@ -4049,7 +4557,7 @@ function OhhwellsBridge() {
|
|
|
4049
4557
|
document.addEventListener("click", handleClick, true);
|
|
4050
4558
|
return () => document.removeEventListener("click", handleClick, true);
|
|
4051
4559
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
4052
|
-
|
|
4560
|
+
useEffect2(() => {
|
|
4053
4561
|
if (!isEditMode) {
|
|
4054
4562
|
editStylesRef.current?.base.remove();
|
|
4055
4563
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -4309,6 +4817,15 @@ function OhhwellsBridge() {
|
|
|
4309
4817
|
textEditable.setAttribute("data-ohw-hovered", "");
|
|
4310
4818
|
return;
|
|
4311
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
|
+
}
|
|
4312
4829
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4313
4830
|
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
4314
4831
|
hoveredImageRef.current = imgEl;
|
|
@@ -4374,8 +4891,30 @@ function OhhwellsBridge() {
|
|
|
4374
4891
|
}
|
|
4375
4892
|
}
|
|
4376
4893
|
};
|
|
4894
|
+
const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
|
|
4895
|
+
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
4896
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
4897
|
+
const ZONE = 20;
|
|
4898
|
+
for (let i = 0; i < sections.length; i++) {
|
|
4899
|
+
const a = sections[i];
|
|
4900
|
+
const b = sections[i + 1] ?? null;
|
|
4901
|
+
const boundaryY = a.getBoundingClientRect().bottom;
|
|
4902
|
+
if (Math.abs(y - boundaryY) <= ZONE) {
|
|
4903
|
+
const insertAfter = a.dataset.ohwSection ?? "";
|
|
4904
|
+
const insertBefore = b?.dataset.ohwSection ?? null;
|
|
4905
|
+
hoveredGapRef.current = { insertAfter, insertBefore };
|
|
4906
|
+
setSectionGap({ insertAfter, insertBefore, y: boundaryY });
|
|
4907
|
+
return;
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
if (hoveredGapRef.current) {
|
|
4911
|
+
hoveredGapRef.current = null;
|
|
4912
|
+
setSectionGap(null);
|
|
4913
|
+
}
|
|
4914
|
+
};
|
|
4377
4915
|
const handleMouseMove = (e) => {
|
|
4378
4916
|
const { clientX, clientY } = e;
|
|
4917
|
+
probeSectionGapAt(clientX, clientY);
|
|
4379
4918
|
probeImageAt(clientX, clientY);
|
|
4380
4919
|
probeHoverCardsAt(clientX, clientY);
|
|
4381
4920
|
};
|
|
@@ -4383,6 +4922,7 @@ function OhhwellsBridge() {
|
|
|
4383
4922
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
4384
4923
|
const { clientX, clientY } = e.data;
|
|
4385
4924
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
4925
|
+
probeSectionGapAt(clientX, clientY);
|
|
4386
4926
|
probeImageAt(clientX, clientY);
|
|
4387
4927
|
probeHoverCardsAt(clientX, clientY);
|
|
4388
4928
|
};
|
|
@@ -4541,6 +5081,18 @@ function OhhwellsBridge() {
|
|
|
4541
5081
|
const content = e.data.content;
|
|
4542
5082
|
if (!content) return;
|
|
4543
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
|
+
}
|
|
4544
5096
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4545
5097
|
if (el.dataset.ohwEditable === "image") {
|
|
4546
5098
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4595,7 +5147,71 @@ function OhhwellsBridge() {
|
|
|
4595
5147
|
};
|
|
4596
5148
|
const handleSave = (e) => {
|
|
4597
5149
|
if (e.data?.type !== "ow:save") return;
|
|
4598
|
-
|
|
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 });
|
|
4599
5215
|
};
|
|
4600
5216
|
const handleSelectionChange = () => {
|
|
4601
5217
|
if (!activeElRef.current) return;
|
|
@@ -4700,6 +5316,10 @@ function OhhwellsBridge() {
|
|
|
4700
5316
|
deactivateRef.current();
|
|
4701
5317
|
};
|
|
4702
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);
|
|
4703
5323
|
window.addEventListener("message", handleImageUrl);
|
|
4704
5324
|
window.addEventListener("message", handleAnimLock);
|
|
4705
5325
|
window.addEventListener("message", handleCanvasHeight);
|
|
@@ -4734,6 +5354,10 @@ function OhhwellsBridge() {
|
|
|
4734
5354
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
4735
5355
|
window.removeEventListener("scroll", handleScroll, true);
|
|
4736
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);
|
|
4737
5361
|
window.removeEventListener("message", handleImageUrl);
|
|
4738
5362
|
window.removeEventListener("message", handleAnimLock);
|
|
4739
5363
|
window.removeEventListener("message", handleCanvasHeight);
|
|
@@ -4748,7 +5372,7 @@ function OhhwellsBridge() {
|
|
|
4748
5372
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
4749
5373
|
};
|
|
4750
5374
|
}, [isEditMode, refreshStateRules]);
|
|
4751
|
-
|
|
5375
|
+
useEffect2(() => {
|
|
4752
5376
|
if (!isEditMode) return;
|
|
4753
5377
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
4754
5378
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -4790,12 +5414,12 @@ function OhhwellsBridge() {
|
|
|
4790
5414
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
4791
5415
|
}, [deactivate]);
|
|
4792
5416
|
return bridgeRoot ? createPortal(
|
|
4793
|
-
/* @__PURE__ */
|
|
4794
|
-
toolbarRect && /* @__PURE__ */
|
|
4795
|
-
/* @__PURE__ */
|
|
4796
|
-
/* @__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 })
|
|
4797
5421
|
] }),
|
|
4798
|
-
maxBadge && /* @__PURE__ */
|
|
5422
|
+
maxBadge && /* @__PURE__ */ jsxs2(
|
|
4799
5423
|
"div",
|
|
4800
5424
|
{
|
|
4801
5425
|
"data-ohw-max-badge": "",
|
|
@@ -4821,7 +5445,7 @@ function OhhwellsBridge() {
|
|
|
4821
5445
|
]
|
|
4822
5446
|
}
|
|
4823
5447
|
),
|
|
4824
|
-
toggleState && /* @__PURE__ */
|
|
5448
|
+
toggleState && /* @__PURE__ */ jsx5(
|
|
4825
5449
|
StateToggle,
|
|
4826
5450
|
{
|
|
4827
5451
|
rect: toggleState.rect,
|
|
@@ -4829,6 +5453,31 @@ function OhhwellsBridge() {
|
|
|
4829
5453
|
states: toggleState.states,
|
|
4830
5454
|
onStateChange: handleStateChange
|
|
4831
5455
|
}
|
|
5456
|
+
),
|
|
5457
|
+
sectionGap && /* @__PURE__ */ jsxs2(
|
|
5458
|
+
"div",
|
|
5459
|
+
{
|
|
5460
|
+
"data-ohw-section-insert-line": "",
|
|
5461
|
+
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
5462
|
+
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
5463
|
+
children: [
|
|
5464
|
+
/* @__PURE__ */ jsx5("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
5465
|
+
/* @__PURE__ */ jsx5(
|
|
5466
|
+
Badge,
|
|
5467
|
+
{
|
|
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",
|
|
5469
|
+
onClick: () => {
|
|
5470
|
+
window.parent.postMessage(
|
|
5471
|
+
{ type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
|
|
5472
|
+
"*"
|
|
5473
|
+
);
|
|
5474
|
+
},
|
|
5475
|
+
children: "Add Section"
|
|
5476
|
+
}
|
|
5477
|
+
),
|
|
5478
|
+
/* @__PURE__ */ jsx5("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
5479
|
+
]
|
|
5480
|
+
}
|
|
4832
5481
|
)
|
|
4833
5482
|
] }),
|
|
4834
5483
|
bridgeRoot
|
|
@@ -4836,6 +5485,7 @@ function OhhwellsBridge() {
|
|
|
4836
5485
|
}
|
|
4837
5486
|
export {
|
|
4838
5487
|
OhhwellsBridge,
|
|
5488
|
+
SchedulingWidget,
|
|
4839
5489
|
Toggle,
|
|
4840
5490
|
ToggleGroup,
|
|
4841
5491
|
ToggleGroupItem,
|