@ohhwells/bridge 0.1.14 → 0.1.16-next.12
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/README.md +198 -70
- package/dist/index.cjs +1309 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +1296 -111
- package/dist/index.js.map +1 -1
- package/dist/styles.css +536 -55
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,752 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/OhhwellsBridge.tsx
|
|
4
|
-
import
|
|
4
|
+
import React3, { useCallback, useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
5
|
+
import { createRoot } from "react-dom/client";
|
|
6
|
+
import { flushSync } from "react-dom";
|
|
7
|
+
|
|
8
|
+
// src/ui/SchedulingWidget.tsx
|
|
9
|
+
import { useEffect, useMemo, useRef, useState as useState2 } from "react";
|
|
10
|
+
|
|
11
|
+
// src/ui/EmailCaptureModal.tsx
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { Dialog } from "radix-ui";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
function Spinner() {
|
|
16
|
+
return /* @__PURE__ */ jsxs(
|
|
17
|
+
"svg",
|
|
18
|
+
{
|
|
19
|
+
width: "32",
|
|
20
|
+
height: "32",
|
|
21
|
+
viewBox: "0 0 32 32",
|
|
22
|
+
fill: "none",
|
|
23
|
+
style: { animation: "ohw-spin 0.75s linear infinite" },
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ jsx("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
|
|
26
|
+
/* @__PURE__ */ jsx("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
|
|
27
|
+
/* @__PURE__ */ jsx(
|
|
28
|
+
"path",
|
|
29
|
+
{
|
|
30
|
+
d: "M16 4a12 12 0 0 1 12 12",
|
|
31
|
+
stroke: "#3b82f6",
|
|
32
|
+
strokeWidth: "3",
|
|
33
|
+
strokeLinecap: "round"
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
41
|
+
const [email, setEmail] = useState("");
|
|
42
|
+
const [submittedEmail, setSubmittedEmail] = useState("");
|
|
43
|
+
const [loading, setLoading] = useState(false);
|
|
44
|
+
const [success, setSuccess] = useState(false);
|
|
45
|
+
const [error, setError] = useState(null);
|
|
46
|
+
const isBook = title === "Confirm your spot";
|
|
47
|
+
const handleSubmit = async () => {
|
|
48
|
+
if (!email.trim()) return;
|
|
49
|
+
setLoading(true);
|
|
50
|
+
setError(null);
|
|
51
|
+
try {
|
|
52
|
+
await onSubmit(email.trim());
|
|
53
|
+
setSubmittedEmail(email.trim());
|
|
54
|
+
setSuccess(true);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
57
|
+
} finally {
|
|
58
|
+
setLoading(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const handleKeyDown = (e) => {
|
|
62
|
+
if (e.key === "Enter" && !loading) handleSubmit();
|
|
63
|
+
};
|
|
64
|
+
return /* @__PURE__ */ jsx(Dialog.Root, { open: true, onOpenChange: (open) => {
|
|
65
|
+
if (!open) onClose();
|
|
66
|
+
}, children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
Dialog.Overlay,
|
|
69
|
+
{
|
|
70
|
+
className: "fixed inset-0 z-50",
|
|
71
|
+
style: { background: "rgba(0,0,0,0.45)" }
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsxs(
|
|
75
|
+
Dialog.Content,
|
|
76
|
+
{
|
|
77
|
+
className: "fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 bg-white rounded-xl shadow-xl outline-none font-body box-border overflow-hidden",
|
|
78
|
+
style: { maxWidth: 400 },
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 p-6", children: [
|
|
81
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
82
|
+
/* @__PURE__ */ jsx(
|
|
83
|
+
Dialog.Title,
|
|
84
|
+
{
|
|
85
|
+
className: "font-body text-xl font-bold m-0 leading-tight",
|
|
86
|
+
style: { color: "var(--color-dark,#200C02)" },
|
|
87
|
+
children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
|
|
88
|
+
}
|
|
89
|
+
),
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
Dialog.Description,
|
|
92
|
+
{
|
|
93
|
+
className: "font-body text-sm m-0",
|
|
94
|
+
style: { color: "#6B7280" },
|
|
95
|
+
children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
] }),
|
|
99
|
+
/* @__PURE__ */ jsx(
|
|
100
|
+
Dialog.Close,
|
|
101
|
+
{
|
|
102
|
+
className: "shrink-0 w-7 h-7 flex items-center justify-center rounded-full bg-transparent border-none cursor-pointer mt-0.5 absolute top-2.5 right-1.5",
|
|
103
|
+
style: { color: "#6B7280" },
|
|
104
|
+
"aria-label": "Close",
|
|
105
|
+
children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
106
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
107
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
108
|
+
] })
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
] }),
|
|
112
|
+
!success && (loading ? (
|
|
113
|
+
/* Loading state — full body replaced */
|
|
114
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
|
|
115
|
+
/* @__PURE__ */ jsx(Spinner, {}),
|
|
116
|
+
/* @__PURE__ */ jsx("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
|
|
117
|
+
] })
|
|
118
|
+
) : (
|
|
119
|
+
/* Email form */
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { className: "px-7 pt-2 pb-6", children: [
|
|
121
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 mb-8", children: [
|
|
122
|
+
/* @__PURE__ */ jsx("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
|
|
123
|
+
/* @__PURE__ */ jsx(
|
|
124
|
+
"input",
|
|
125
|
+
{
|
|
126
|
+
type: "email",
|
|
127
|
+
value: email,
|
|
128
|
+
onChange: (e) => setEmail(e.target.value),
|
|
129
|
+
onKeyDown: handleKeyDown,
|
|
130
|
+
placeholder: "hello@gmail.com",
|
|
131
|
+
autoFocus: true,
|
|
132
|
+
className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
|
|
133
|
+
style: {
|
|
134
|
+
borderColor: "#E7E5E4",
|
|
135
|
+
background: "#fff",
|
|
136
|
+
color: "var(--color-dark,#200C02)"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
error && /* @__PURE__ */ jsx("p", { className: "font-body text-xs text-red-500 m-0", children: error })
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
143
|
+
"button",
|
|
144
|
+
{
|
|
145
|
+
onClick: handleSubmit,
|
|
146
|
+
disabled: !email.trim(),
|
|
147
|
+
className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
|
|
148
|
+
style: {
|
|
149
|
+
background: "var(--color-primary,#3D312B)",
|
|
150
|
+
color: "var(--color-light,#FEFFF0)"
|
|
151
|
+
},
|
|
152
|
+
children: "Send"
|
|
153
|
+
}
|
|
154
|
+
) })
|
|
155
|
+
] })
|
|
156
|
+
))
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
] }) });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/ui/SchedulingWidget.tsx
|
|
164
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
165
|
+
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
166
|
+
var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
167
|
+
function getApiDomain() {
|
|
168
|
+
const h = window.location.hostname;
|
|
169
|
+
for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
|
|
170
|
+
if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
|
|
171
|
+
}
|
|
172
|
+
return h;
|
|
173
|
+
}
|
|
174
|
+
function classRunsOnDate(cls, date, type) {
|
|
175
|
+
if (type === "FIXED") {
|
|
176
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
177
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
178
|
+
return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
|
|
179
|
+
}
|
|
180
|
+
return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
|
|
181
|
+
}
|
|
182
|
+
function formatClassTime(cls) {
|
|
183
|
+
let h = parseInt(cls.startTime.hour, 10);
|
|
184
|
+
const m = parseInt(cls.startTime.minutes, 10);
|
|
185
|
+
if (cls.startTime.time === "PM" && h !== 12) h += 12;
|
|
186
|
+
if (cls.startTime.time === "AM" && h === 12) h = 0;
|
|
187
|
+
const endTotal = h * 60 + m + cls.duration;
|
|
188
|
+
const eh = Math.floor(endTotal / 60) % 24;
|
|
189
|
+
const em = endTotal % 60;
|
|
190
|
+
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
191
|
+
}
|
|
192
|
+
function getBookingsOnDate(cls, date) {
|
|
193
|
+
if (!cls.bookings?.length) return 0;
|
|
194
|
+
const ds = date.toISOString().split("T")[0];
|
|
195
|
+
return cls.bookings.filter((b) => {
|
|
196
|
+
try {
|
|
197
|
+
return new Date(b.classDate).toISOString().split("T")[0] === ds;
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}).length;
|
|
202
|
+
}
|
|
203
|
+
function buildTimezoneLabel(tz) {
|
|
204
|
+
try {
|
|
205
|
+
const now = /* @__PURE__ */ new Date();
|
|
206
|
+
const timeStr = new Intl.DateTimeFormat("en-US", {
|
|
207
|
+
hour: "2-digit",
|
|
208
|
+
minute: "2-digit",
|
|
209
|
+
hour12: false,
|
|
210
|
+
timeZone: tz
|
|
211
|
+
}).format(now);
|
|
212
|
+
const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
213
|
+
const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
|
|
214
|
+
return `${timeStr} (${offsetPart}) ${city} time`;
|
|
215
|
+
} catch {
|
|
216
|
+
return tz;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function EmptyState({ inEditor }) {
|
|
220
|
+
const handleAddSchedule = () => {
|
|
221
|
+
if (inEditor) {
|
|
222
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
return /* @__PURE__ */ jsxs2("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: [
|
|
226
|
+
/* @__PURE__ */ jsx2("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ jsxs2(
|
|
227
|
+
"svg",
|
|
228
|
+
{
|
|
229
|
+
width: "20",
|
|
230
|
+
height: "20",
|
|
231
|
+
viewBox: "0 0 24 24",
|
|
232
|
+
fill: "none",
|
|
233
|
+
stroke: "var(--color-accent, #A89B83)",
|
|
234
|
+
strokeWidth: "1.5",
|
|
235
|
+
strokeLinecap: "round",
|
|
236
|
+
strokeLinejoin: "round",
|
|
237
|
+
children: [
|
|
238
|
+
/* @__PURE__ */ jsx2("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
|
|
239
|
+
/* @__PURE__ */ jsx2("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
|
|
240
|
+
/* @__PURE__ */ jsx2("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
|
|
241
|
+
/* @__PURE__ */ jsx2("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
|
|
242
|
+
/* @__PURE__ */ jsx2("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
|
|
243
|
+
/* @__PURE__ */ jsx2("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
) }),
|
|
247
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-2", children: [
|
|
248
|
+
/* @__PURE__ */ jsx2("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
|
|
249
|
+
/* @__PURE__ */ jsx2("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." })
|
|
250
|
+
] }),
|
|
251
|
+
/* @__PURE__ */ jsx2(
|
|
252
|
+
"button",
|
|
253
|
+
{
|
|
254
|
+
onClick: handleAddSchedule,
|
|
255
|
+
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",
|
|
256
|
+
children: "Add Schedule"
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
] });
|
|
260
|
+
}
|
|
261
|
+
function LoadingSkeleton() {
|
|
262
|
+
const shimmer = {
|
|
263
|
+
background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
|
|
264
|
+
backgroundSize: "200% 100%",
|
|
265
|
+
animation: "ohw-shimmer 1.5s infinite"
|
|
266
|
+
};
|
|
267
|
+
return /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col gap-10", children: [
|
|
268
|
+
/* @__PURE__ */ jsx2("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
|
|
269
|
+
/* @__PURE__ */ jsx2("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
|
|
270
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
|
|
271
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
|
|
272
|
+
] }, i)) }),
|
|
273
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ jsxs2("div", { children: [
|
|
274
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:hidden", children: [
|
|
275
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0", children: [
|
|
276
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
|
|
277
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
|
|
278
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
|
|
279
|
+
] }),
|
|
280
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2", children: [
|
|
281
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
|
|
282
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
|
|
283
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
|
|
284
|
+
] })
|
|
285
|
+
] }),
|
|
286
|
+
/* @__PURE__ */ jsxs2("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
|
|
287
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
|
|
288
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2", children: [
|
|
289
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
|
|
290
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
|
|
293
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
|
|
294
|
+
] }),
|
|
295
|
+
i < 2 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
|
|
296
|
+
] }, i))
|
|
297
|
+
] });
|
|
298
|
+
}
|
|
299
|
+
function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
|
|
300
|
+
const todayMs = useMemo(() => {
|
|
301
|
+
const d = /* @__PURE__ */ new Date();
|
|
302
|
+
d.setHours(0, 0, 0, 0);
|
|
303
|
+
return d.getTime();
|
|
304
|
+
}, []);
|
|
305
|
+
const scrollRef = useRef(null);
|
|
306
|
+
const [canScrollLeft, setCanScrollLeft] = useState2(false);
|
|
307
|
+
const [canScrollRight, setCanScrollRight] = useState2(false);
|
|
308
|
+
const updateScrollState = () => {
|
|
309
|
+
const el = scrollRef.current;
|
|
310
|
+
if (!el) return;
|
|
311
|
+
setCanScrollLeft(el.scrollLeft > 0);
|
|
312
|
+
setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
313
|
+
};
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
const el = scrollRef.current;
|
|
316
|
+
if (!el) return;
|
|
317
|
+
updateScrollState();
|
|
318
|
+
el.addEventListener("scroll", updateScrollState);
|
|
319
|
+
const ro = new ResizeObserver(updateScrollState);
|
|
320
|
+
ro.observe(el);
|
|
321
|
+
return () => {
|
|
322
|
+
el.removeEventListener("scroll", updateScrollState);
|
|
323
|
+
ro.disconnect();
|
|
324
|
+
};
|
|
325
|
+
}, [dates]);
|
|
326
|
+
const scroll = (dir) => {
|
|
327
|
+
scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
|
|
328
|
+
};
|
|
329
|
+
const isDragging = useRef(false);
|
|
330
|
+
const dragStartX = useRef(0);
|
|
331
|
+
const dragStartScrollLeft = useRef(0);
|
|
332
|
+
const onDragStart = (e) => {
|
|
333
|
+
if (!scrollRef.current) return;
|
|
334
|
+
isDragging.current = true;
|
|
335
|
+
dragStartX.current = e.clientX;
|
|
336
|
+
dragStartScrollLeft.current = scrollRef.current.scrollLeft;
|
|
337
|
+
scrollRef.current.style.cursor = "grabbing";
|
|
338
|
+
scrollRef.current.style.userSelect = "none";
|
|
339
|
+
};
|
|
340
|
+
const onDragMove = (e) => {
|
|
341
|
+
if (!isDragging.current || !scrollRef.current) return;
|
|
342
|
+
const delta = e.clientX - dragStartX.current;
|
|
343
|
+
scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
|
|
344
|
+
};
|
|
345
|
+
const onDragEnd = () => {
|
|
346
|
+
if (!scrollRef.current) return;
|
|
347
|
+
isDragging.current = false;
|
|
348
|
+
scrollRef.current.style.cursor = "grab";
|
|
349
|
+
scrollRef.current.style.userSelect = "";
|
|
350
|
+
};
|
|
351
|
+
const datesWithClasses = useMemo(
|
|
352
|
+
() => new Set(dates.map(
|
|
353
|
+
(d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
|
|
354
|
+
).filter((i) => i >= 0)),
|
|
355
|
+
[dates, schedule]
|
|
356
|
+
);
|
|
357
|
+
const selectedDate = dates[selectedIdx];
|
|
358
|
+
const selectedClasses = useMemo(
|
|
359
|
+
() => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
|
|
360
|
+
[schedule, selectedDate]
|
|
361
|
+
);
|
|
362
|
+
return /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col gap-10", children: [
|
|
363
|
+
/* @__PURE__ */ jsxs2("div", { className: "relative w-full", children: [
|
|
364
|
+
canScrollLeft && /* @__PURE__ */ jsx2(
|
|
365
|
+
"button",
|
|
366
|
+
{
|
|
367
|
+
onClick: () => scroll("left"),
|
|
368
|
+
className: "hidden sm:flex absolute left-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
|
|
369
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
370
|
+
"aria-label": "Scroll left",
|
|
371
|
+
children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx2("polyline", { points: "15 18 9 12 15 6" }) })
|
|
372
|
+
}
|
|
373
|
+
),
|
|
374
|
+
canScrollRight && /* @__PURE__ */ jsx2(
|
|
375
|
+
"button",
|
|
376
|
+
{
|
|
377
|
+
onClick: () => scroll("right"),
|
|
378
|
+
className: "hidden sm:flex absolute right-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
|
|
379
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
380
|
+
"aria-label": "Scroll right",
|
|
381
|
+
children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx2("polyline", { points: "9 18 15 12 9 6" }) })
|
|
382
|
+
}
|
|
383
|
+
),
|
|
384
|
+
canScrollLeft && /* @__PURE__ */ jsx2(
|
|
385
|
+
"div",
|
|
386
|
+
{
|
|
387
|
+
className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
388
|
+
style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
|
|
389
|
+
}
|
|
390
|
+
),
|
|
391
|
+
canScrollRight && /* @__PURE__ */ jsx2(
|
|
392
|
+
"div",
|
|
393
|
+
{
|
|
394
|
+
className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
395
|
+
style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
|
|
396
|
+
}
|
|
397
|
+
),
|
|
398
|
+
/* @__PURE__ */ jsx2(
|
|
399
|
+
"div",
|
|
400
|
+
{
|
|
401
|
+
ref: scrollRef,
|
|
402
|
+
className: "overflow-x-auto w-full",
|
|
403
|
+
style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
|
|
404
|
+
onMouseDown: onDragStart,
|
|
405
|
+
onMouseMove: onDragMove,
|
|
406
|
+
onMouseUp: onDragEnd,
|
|
407
|
+
onMouseLeave: onDragEnd,
|
|
408
|
+
children: /* @__PURE__ */ jsx2("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
|
|
409
|
+
const isToday = date.getTime() === todayMs;
|
|
410
|
+
const isSelected = i === selectedIdx;
|
|
411
|
+
const clickable = datesWithClasses.has(i);
|
|
412
|
+
const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
|
|
413
|
+
return /* @__PURE__ */ jsxs2(
|
|
414
|
+
"div",
|
|
415
|
+
{
|
|
416
|
+
onClick: () => clickable && onSelectDate(i),
|
|
417
|
+
className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
|
|
418
|
+
children: [
|
|
419
|
+
/* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
|
|
420
|
+
/* @__PURE__ */ jsx2(
|
|
421
|
+
"div",
|
|
422
|
+
{
|
|
423
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center",
|
|
424
|
+
style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
|
|
425
|
+
children: /* @__PURE__ */ jsx2(
|
|
426
|
+
"span",
|
|
427
|
+
{
|
|
428
|
+
className: "font-body text-xl font-bold text-center tracking-display-tight",
|
|
429
|
+
style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
|
|
430
|
+
children: date.getDate()
|
|
431
|
+
}
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
]
|
|
436
|
+
},
|
|
437
|
+
i
|
|
438
|
+
);
|
|
439
|
+
}) })
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
] }),
|
|
443
|
+
selectedClasses.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ jsx2("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
|
|
444
|
+
const booked = getBookingsOnDate(cls, selectedDate);
|
|
445
|
+
const available = cls.maxParticipants - booked;
|
|
446
|
+
const isFull = available <= 0;
|
|
447
|
+
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
448
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
|
|
449
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
|
|
450
|
+
/* @__PURE__ */ jsx2("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
|
|
451
|
+
/* @__PURE__ */ jsx2(
|
|
452
|
+
"span",
|
|
453
|
+
{
|
|
454
|
+
className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
|
|
455
|
+
style: { borderColor: "#0885FE", color: "#0885FE" },
|
|
456
|
+
children: "GROUP"
|
|
457
|
+
}
|
|
458
|
+
),
|
|
459
|
+
/* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
460
|
+
/* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
461
|
+
available,
|
|
462
|
+
"/",
|
|
463
|
+
cls.maxParticipants
|
|
464
|
+
] }),
|
|
465
|
+
/* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
466
|
+
] }) })
|
|
467
|
+
] }),
|
|
468
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
|
|
469
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
|
|
470
|
+
/* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
471
|
+
cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
|
|
472
|
+
] }),
|
|
473
|
+
/* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
474
|
+
/* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
475
|
+
available,
|
|
476
|
+
"/",
|
|
477
|
+
cls.maxParticipants
|
|
478
|
+
] }),
|
|
479
|
+
/* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
480
|
+
] }) }),
|
|
481
|
+
/* @__PURE__ */ jsx2(
|
|
482
|
+
"button",
|
|
483
|
+
{
|
|
484
|
+
className: "w-full sm:w-[200px] sm:shrink-0 px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
|
|
485
|
+
style: isFull ? {
|
|
486
|
+
background: "transparent",
|
|
487
|
+
border: "1px solid var(--color-dark, #200C02)",
|
|
488
|
+
color: "var(--color-dark, #200C02)"
|
|
489
|
+
} : {
|
|
490
|
+
background: "var(--color-primary, #3D312B)",
|
|
491
|
+
border: "none",
|
|
492
|
+
color: "var(--color-light, #FEFFF0)"
|
|
493
|
+
},
|
|
494
|
+
onClick: () => {
|
|
495
|
+
if (!cls.id) return;
|
|
496
|
+
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
497
|
+
},
|
|
498
|
+
children: isFull ? "Join Waitlist" : "Book Now"
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
] })
|
|
502
|
+
] }),
|
|
503
|
+
i < selectedClasses.length - 1 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
|
|
504
|
+
] }, cls.id ?? i);
|
|
505
|
+
}) })
|
|
506
|
+
] });
|
|
507
|
+
}
|
|
508
|
+
function CalendarFoldIcon() {
|
|
509
|
+
return /* @__PURE__ */ jsxs2(
|
|
510
|
+
"svg",
|
|
511
|
+
{
|
|
512
|
+
width: "16",
|
|
513
|
+
height: "16",
|
|
514
|
+
viewBox: "0 0 24 24",
|
|
515
|
+
fill: "none",
|
|
516
|
+
stroke: "currentColor",
|
|
517
|
+
strokeWidth: "2",
|
|
518
|
+
strokeLinecap: "round",
|
|
519
|
+
strokeLinejoin: "round",
|
|
520
|
+
style: { flexShrink: 0 },
|
|
521
|
+
children: [
|
|
522
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 2v4" }),
|
|
523
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 2v4" }),
|
|
524
|
+
/* @__PURE__ */ jsx2("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
|
|
525
|
+
/* @__PURE__ */ jsx2("path", { d: "M3 10h18" }),
|
|
526
|
+
/* @__PURE__ */ jsx2("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
|
|
527
|
+
]
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
|
|
532
|
+
const [schedule, setSchedule] = useState2(null);
|
|
533
|
+
const [loading, setLoading] = useState2(true);
|
|
534
|
+
const [inEditor, setInEditor] = useState2(false);
|
|
535
|
+
const [isHovered, setIsHovered] = useState2(false);
|
|
536
|
+
const [modalState, setModalState] = useState2(null);
|
|
537
|
+
const switchScheduleIdRef = useRef(null);
|
|
538
|
+
const dates = useMemo(() => {
|
|
539
|
+
const today = /* @__PURE__ */ new Date();
|
|
540
|
+
today.setHours(0, 0, 0, 0);
|
|
541
|
+
return Array.from({ length: 14 }, (_, i) => {
|
|
542
|
+
const d = new Date(today);
|
|
543
|
+
d.setDate(today.getDate() + i);
|
|
544
|
+
return d;
|
|
545
|
+
});
|
|
546
|
+
}, []);
|
|
547
|
+
const [selectedIdx, setSelectedIdx] = useState2(0);
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
if (!schedule?.classes) return;
|
|
550
|
+
for (let i = 0; i < dates.length; i++) {
|
|
551
|
+
if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
|
|
552
|
+
setSelectedIdx(i);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}, [schedule, dates]);
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
if (typeof window === "undefined") return;
|
|
559
|
+
const isInEditor = window.self !== window.top;
|
|
560
|
+
setInEditor(isInEditor);
|
|
561
|
+
if (isInEditor) {
|
|
562
|
+
const startEmpty = initialScheduleId === null;
|
|
563
|
+
if (startEmpty) setLoading(false);
|
|
564
|
+
let initialized = startEmpty;
|
|
565
|
+
const timer = !startEmpty ? setTimeout(() => {
|
|
566
|
+
if (!initialized) {
|
|
567
|
+
initialized = true;
|
|
568
|
+
setLoading(false);
|
|
569
|
+
}
|
|
570
|
+
}, 5e3) : null;
|
|
571
|
+
const handler = (e) => {
|
|
572
|
+
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
573
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
574
|
+
setSchedule(null);
|
|
575
|
+
setLoading(false);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (e.data?.type === "ow:switch-schedule") {
|
|
579
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
580
|
+
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
581
|
+
initialized = false;
|
|
582
|
+
setLoading(true);
|
|
583
|
+
window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
587
|
+
if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
|
|
588
|
+
if (initialized && switchScheduleIdRef.current === null) return;
|
|
589
|
+
initialized = true;
|
|
590
|
+
if (timer) clearTimeout(timer);
|
|
591
|
+
const schedules = e.data.schedules ?? [];
|
|
592
|
+
const isSwitching = switchScheduleIdRef.current !== null;
|
|
593
|
+
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;
|
|
594
|
+
switchScheduleIdRef.current = null;
|
|
595
|
+
setSchedule(target);
|
|
596
|
+
setLoading(false);
|
|
597
|
+
if (notifyOnConnect && target && !isSwitching) {
|
|
598
|
+
window.parent.postMessage({
|
|
599
|
+
type: "ow:schedule-connected",
|
|
600
|
+
schedule: { id: target.id, name: target.name },
|
|
601
|
+
insertAfter
|
|
602
|
+
}, "*");
|
|
603
|
+
window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
window.addEventListener("message", handler);
|
|
607
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
608
|
+
return () => {
|
|
609
|
+
window.removeEventListener("message", handler);
|
|
610
|
+
if (timer) clearTimeout(timer);
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (initialScheduleId === null) {
|
|
614
|
+
setLoading(false);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
;
|
|
618
|
+
(async () => {
|
|
619
|
+
try {
|
|
620
|
+
if (initialScheduleId) {
|
|
621
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
|
|
622
|
+
const data = await res.json();
|
|
623
|
+
setSchedule(data?.id ? data : null);
|
|
624
|
+
} else {
|
|
625
|
+
const domain = getApiDomain();
|
|
626
|
+
const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
|
|
627
|
+
const slugs = await sitemapRes.json();
|
|
628
|
+
if (!Array.isArray(slugs) || !slugs.length) {
|
|
629
|
+
setLoading(false);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const { slug } = [...slugs].sort(
|
|
633
|
+
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
634
|
+
)[0];
|
|
635
|
+
const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
|
|
636
|
+
const data = await res.json();
|
|
637
|
+
setSchedule(data?.id ? data : null);
|
|
638
|
+
}
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
setLoading(false);
|
|
642
|
+
})();
|
|
643
|
+
}, []);
|
|
644
|
+
const timezoneLabel = schedule?.timezone ? (() => {
|
|
645
|
+
try {
|
|
646
|
+
return buildTimezoneLabel(schedule.timezone);
|
|
647
|
+
} catch {
|
|
648
|
+
return void 0;
|
|
649
|
+
}
|
|
650
|
+
})() : void 0;
|
|
651
|
+
const handleModalSubmit = async (email) => {
|
|
652
|
+
if (!modalState) return;
|
|
653
|
+
const { mode, classId, classDate } = modalState;
|
|
654
|
+
const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
|
|
655
|
+
const res = await fetch(endpoint, {
|
|
656
|
+
method: "POST",
|
|
657
|
+
headers: { "Content-Type": "application/json" },
|
|
658
|
+
body: JSON.stringify({ classDate: classDate.toISOString(), email })
|
|
659
|
+
});
|
|
660
|
+
if (!res.ok) {
|
|
661
|
+
const data = await res.json().catch(() => ({}));
|
|
662
|
+
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
const handleReplaceSchedule = () => {
|
|
666
|
+
setIsHovered(false);
|
|
667
|
+
if (schedule) {
|
|
668
|
+
window.parent.postMessage({
|
|
669
|
+
type: "ow:schedule-connected",
|
|
670
|
+
schedule: { id: schedule.id, name: schedule.name },
|
|
671
|
+
insertAfter
|
|
672
|
+
}, "*");
|
|
673
|
+
} else {
|
|
674
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
const sectionId = `scheduling-${insertAfter}`;
|
|
678
|
+
return /* @__PURE__ */ jsxs2(
|
|
679
|
+
"section",
|
|
680
|
+
{
|
|
681
|
+
"data-ohw-section": sectionId,
|
|
682
|
+
"data-ohw-scheduling-anchor": insertAfter,
|
|
683
|
+
className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
|
|
684
|
+
onMouseEnter: () => inEditor && setIsHovered(true),
|
|
685
|
+
onMouseLeave: () => setIsHovered(false),
|
|
686
|
+
children: [
|
|
687
|
+
inEditor && isHovered && !!schedule && /* @__PURE__ */ jsxs2(
|
|
688
|
+
"div",
|
|
689
|
+
{
|
|
690
|
+
className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
|
|
691
|
+
onClick: handleReplaceSchedule,
|
|
692
|
+
children: [
|
|
693
|
+
/* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
|
|
694
|
+
/* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
|
|
695
|
+
/* @__PURE__ */ jsx2("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs2(
|
|
696
|
+
"div",
|
|
697
|
+
{
|
|
698
|
+
className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
|
|
699
|
+
style: {
|
|
700
|
+
fontSize: 14,
|
|
701
|
+
lineHeight: "24px",
|
|
702
|
+
fontFamily: "'Figtree', system-ui, sans-serif",
|
|
703
|
+
fontWeight: 500,
|
|
704
|
+
color: "#0C0A09"
|
|
705
|
+
},
|
|
706
|
+
children: [
|
|
707
|
+
/* @__PURE__ */ jsx2(CalendarFoldIcon, {}),
|
|
708
|
+
"Replace schedule"
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
) })
|
|
712
|
+
]
|
|
713
|
+
}
|
|
714
|
+
),
|
|
715
|
+
/* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
716
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
|
|
717
|
+
/* @__PURE__ */ jsx2("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
|
|
718
|
+
schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
|
|
719
|
+
] }),
|
|
720
|
+
/* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
721
|
+
timezoneLabel && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
722
|
+
/* @__PURE__ */ jsx2("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ jsx2(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx2(EmptyState, { inEditor }) : /* @__PURE__ */ jsx2(
|
|
723
|
+
ScheduleView,
|
|
724
|
+
{
|
|
725
|
+
schedule,
|
|
726
|
+
dates,
|
|
727
|
+
selectedIdx,
|
|
728
|
+
onSelectDate: setSelectedIdx,
|
|
729
|
+
onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
|
|
730
|
+
}
|
|
731
|
+
) })
|
|
732
|
+
] })
|
|
733
|
+
] }),
|
|
734
|
+
modalState && /* @__PURE__ */ jsx2(
|
|
735
|
+
EmailCaptureModal,
|
|
736
|
+
{
|
|
737
|
+
title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
|
|
738
|
+
subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
|
|
739
|
+
onSubmit: handleModalSubmit,
|
|
740
|
+
onClose: () => setModalState(null)
|
|
741
|
+
}
|
|
742
|
+
)
|
|
743
|
+
]
|
|
744
|
+
}
|
|
745
|
+
);
|
|
746
|
+
}
|
|
5
747
|
|
|
6
748
|
// src/ui/toggle-group.tsx
|
|
7
|
-
import * as
|
|
749
|
+
import * as React2 from "react";
|
|
8
750
|
|
|
9
751
|
// node_modules/clsx/dist/clsx.mjs
|
|
10
752
|
function r(e) {
|
|
@@ -3324,7 +4066,7 @@ var cva = (base, config) => (props) => {
|
|
|
3324
4066
|
|
|
3325
4067
|
// src/ui/toggle.tsx
|
|
3326
4068
|
import { Toggle as TogglePrimitive } from "radix-ui";
|
|
3327
|
-
import { jsx } from "react/jsx-runtime";
|
|
4069
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
3328
4070
|
var toggleVariants = cva(
|
|
3329
4071
|
"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
4072
|
{
|
|
@@ -3351,7 +4093,7 @@ function Toggle({
|
|
|
3351
4093
|
size,
|
|
3352
4094
|
...props
|
|
3353
4095
|
}) {
|
|
3354
|
-
return /* @__PURE__ */
|
|
4096
|
+
return /* @__PURE__ */ jsx3(
|
|
3355
4097
|
TogglePrimitive.Root,
|
|
3356
4098
|
{
|
|
3357
4099
|
"data-slot": "toggle",
|
|
@@ -3362,8 +4104,8 @@ function Toggle({
|
|
|
3362
4104
|
}
|
|
3363
4105
|
|
|
3364
4106
|
// src/ui/toggle-group.tsx
|
|
3365
|
-
import { jsx as
|
|
3366
|
-
var ToggleGroupContext =
|
|
4107
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
4108
|
+
var ToggleGroupContext = React2.createContext({
|
|
3367
4109
|
value: "",
|
|
3368
4110
|
onValueChange: () => {
|
|
3369
4111
|
}
|
|
@@ -3375,13 +4117,13 @@ function ToggleGroup({
|
|
|
3375
4117
|
children,
|
|
3376
4118
|
...props
|
|
3377
4119
|
}) {
|
|
3378
|
-
return /* @__PURE__ */
|
|
4120
|
+
return /* @__PURE__ */ jsx4(
|
|
3379
4121
|
"div",
|
|
3380
4122
|
{
|
|
3381
4123
|
role: "group",
|
|
3382
4124
|
className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
|
|
3383
4125
|
...props,
|
|
3384
|
-
children: /* @__PURE__ */
|
|
4126
|
+
children: /* @__PURE__ */ jsx4(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
|
|
3385
4127
|
}
|
|
3386
4128
|
);
|
|
3387
4129
|
}
|
|
@@ -3391,12 +4133,14 @@ function ToggleGroupItem({
|
|
|
3391
4133
|
children,
|
|
3392
4134
|
...props
|
|
3393
4135
|
}) {
|
|
3394
|
-
const { value: groupValue, onValueChange } =
|
|
3395
|
-
return /* @__PURE__ */
|
|
4136
|
+
const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
|
|
4137
|
+
return /* @__PURE__ */ jsx4(
|
|
3396
4138
|
Toggle,
|
|
3397
4139
|
{
|
|
3398
4140
|
pressed: groupValue === value,
|
|
3399
|
-
onPressedChange: () =>
|
|
4141
|
+
onPressedChange: (pressed) => {
|
|
4142
|
+
if (pressed) onValueChange(value);
|
|
4143
|
+
},
|
|
3400
4144
|
className: cn(
|
|
3401
4145
|
"text-foreground cursor-pointer hover:bg-transparent hover:text-foreground",
|
|
3402
4146
|
"data-[state=on]:bg-background data-[state=on]:shadow-sm",
|
|
@@ -3431,8 +4175,30 @@ function isEditSessionActive() {
|
|
|
3431
4175
|
return new URLSearchParams(q).get("mode") === "edit";
|
|
3432
4176
|
}
|
|
3433
4177
|
|
|
4178
|
+
// src/ui/badge.tsx
|
|
4179
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
4180
|
+
var badgeVariants = cva(
|
|
4181
|
+
"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",
|
|
4182
|
+
{
|
|
4183
|
+
variants: {
|
|
4184
|
+
variant: {
|
|
4185
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
4186
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
4187
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
4188
|
+
outline: "text-foreground"
|
|
4189
|
+
}
|
|
4190
|
+
},
|
|
4191
|
+
defaultVariants: {
|
|
4192
|
+
variant: "default"
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
);
|
|
4196
|
+
function Badge({ className, variant, ...props }) {
|
|
4197
|
+
return /* @__PURE__ */ jsx5("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
4198
|
+
}
|
|
4199
|
+
|
|
3434
4200
|
// src/OhhwellsBridge.tsx
|
|
3435
|
-
import { Fragment, jsx as
|
|
4201
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
3436
4202
|
var PRIMARY = "#0885FE";
|
|
3437
4203
|
var IMAGE_FADE_MS = 300;
|
|
3438
4204
|
function runOpacityFade(el, onDone) {
|
|
@@ -3483,6 +4249,163 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
3483
4249
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
3484
4250
|
});
|
|
3485
4251
|
}
|
|
4252
|
+
function getSectionsTracker() {
|
|
4253
|
+
let el = document.querySelector("[data-ohw-sections-tracker]");
|
|
4254
|
+
if (!el) {
|
|
4255
|
+
el = document.createElement("div");
|
|
4256
|
+
el.setAttribute("data-ohw-sections-tracker", "");
|
|
4257
|
+
el.style.display = "none";
|
|
4258
|
+
document.body.appendChild(el);
|
|
4259
|
+
}
|
|
4260
|
+
return el;
|
|
4261
|
+
}
|
|
4262
|
+
function updateSectionScheduleId(insertAfter, scheduleId) {
|
|
4263
|
+
const tracker = getSectionsTracker();
|
|
4264
|
+
let sections = [];
|
|
4265
|
+
try {
|
|
4266
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
4267
|
+
} catch {
|
|
4268
|
+
}
|
|
4269
|
+
const currentPath = window.location.pathname;
|
|
4270
|
+
const updated = sections.map((s) => {
|
|
4271
|
+
if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
|
|
4272
|
+
if (s.pagePath && s.pagePath !== currentPath) return s;
|
|
4273
|
+
return { ...s, scheduleId };
|
|
4274
|
+
});
|
|
4275
|
+
tracker.textContent = JSON.stringify(updated);
|
|
4276
|
+
return tracker.textContent ?? "[]";
|
|
4277
|
+
}
|
|
4278
|
+
function schedulingSectionId(insertAfter) {
|
|
4279
|
+
return `scheduling-${insertAfter}`;
|
|
4280
|
+
}
|
|
4281
|
+
var INSERT_BEFORE_MARKER = ":before:";
|
|
4282
|
+
function parseSchedulingInsertAfter(insertAfter) {
|
|
4283
|
+
const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
|
|
4284
|
+
if (idx < 0) return { anchor: insertAfter, insertBefore: null };
|
|
4285
|
+
return {
|
|
4286
|
+
anchor: insertAfter.slice(0, idx),
|
|
4287
|
+
insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
|
|
4291
|
+
const parsed = parseSchedulingInsertAfter(insertAfter);
|
|
4292
|
+
const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
|
|
4293
|
+
const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
|
|
4294
|
+
return { effectiveInsertAfter, insertBefore };
|
|
4295
|
+
}
|
|
4296
|
+
function getSchedulingMountPoint(insertAfter) {
|
|
4297
|
+
const { anchor } = parseSchedulingInsertAfter(insertAfter);
|
|
4298
|
+
let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
|
|
4299
|
+
if (!anchorEl && anchor === "scheduling") {
|
|
4300
|
+
const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
|
|
4301
|
+
anchorEl = widgets.at(-1) ?? null;
|
|
4302
|
+
}
|
|
4303
|
+
if (!anchorEl) return null;
|
|
4304
|
+
return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
|
|
4305
|
+
}
|
|
4306
|
+
function schedulingMountDepth(insertAfter) {
|
|
4307
|
+
if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
|
|
4308
|
+
return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
|
|
4309
|
+
}
|
|
4310
|
+
function getPageSchedulingEntries(raw) {
|
|
4311
|
+
if (!raw) return [];
|
|
4312
|
+
try {
|
|
4313
|
+
const entries = JSON.parse(raw);
|
|
4314
|
+
const currentPath = window.location.pathname;
|
|
4315
|
+
return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
|
|
4316
|
+
} catch {
|
|
4317
|
+
return [];
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
function isSchedulingWidgetMissing(entry) {
|
|
4321
|
+
const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
|
|
4322
|
+
return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
|
|
4323
|
+
}
|
|
4324
|
+
function hasMissingSchedulingWidgets(entries) {
|
|
4325
|
+
return entries.some(isSchedulingWidgetMissing);
|
|
4326
|
+
}
|
|
4327
|
+
function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
|
|
4328
|
+
if (!hasMissingSchedulingWidgets(entries)) return;
|
|
4329
|
+
mountSchedulingEntries(entries, notifyOnConnect);
|
|
4330
|
+
}
|
|
4331
|
+
function initSectionsFromContent(content, removeExisting = false) {
|
|
4332
|
+
const raw = content["__ohw_sections"];
|
|
4333
|
+
if (!raw) return;
|
|
4334
|
+
try {
|
|
4335
|
+
if (removeExisting) {
|
|
4336
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
|
|
4337
|
+
}
|
|
4338
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
4339
|
+
if (inEditor) getSectionsTracker().textContent = raw;
|
|
4340
|
+
const pageEntries = getPageSchedulingEntries(raw);
|
|
4341
|
+
mountSchedulingEntries(pageEntries, false);
|
|
4342
|
+
requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
|
|
4343
|
+
setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
|
|
4344
|
+
} catch {
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
|
|
4348
|
+
const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
|
|
4349
|
+
const sectionId = schedulingSectionId(effectiveInsertAfter);
|
|
4350
|
+
if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
|
|
4351
|
+
const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
|
|
4352
|
+
if (!mountPoint) return false;
|
|
4353
|
+
const container = document.createElement("div");
|
|
4354
|
+
container.dataset.ohwSectionContainer = "scheduling";
|
|
4355
|
+
if (insertBefore) {
|
|
4356
|
+
const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
|
|
4357
|
+
const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
|
|
4358
|
+
if (!beforePoint) return false;
|
|
4359
|
+
beforePoint.insertAdjacentElement("beforebegin", container);
|
|
4360
|
+
} else {
|
|
4361
|
+
let tail = mountPoint;
|
|
4362
|
+
while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
|
|
4363
|
+
tail = tail.nextElementSibling;
|
|
4364
|
+
}
|
|
4365
|
+
tail.insertAdjacentElement("afterend", container);
|
|
4366
|
+
}
|
|
4367
|
+
const root = createRoot(container);
|
|
4368
|
+
flushSync(() => {
|
|
4369
|
+
root.render(
|
|
4370
|
+
/* @__PURE__ */ jsx6(
|
|
4371
|
+
SchedulingWidget,
|
|
4372
|
+
{
|
|
4373
|
+
notifyOnConnect,
|
|
4374
|
+
initialScheduleId: scheduleId,
|
|
4375
|
+
insertAfter: effectiveInsertAfter
|
|
4376
|
+
}
|
|
4377
|
+
)
|
|
4378
|
+
);
|
|
4379
|
+
});
|
|
4380
|
+
const tracker = getSectionsTracker();
|
|
4381
|
+
let sections = [];
|
|
4382
|
+
try {
|
|
4383
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
4384
|
+
} catch {
|
|
4385
|
+
}
|
|
4386
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
4387
|
+
if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
|
|
4388
|
+
sections.push({
|
|
4389
|
+
type: "scheduling",
|
|
4390
|
+
insertAfter: effectiveInsertAfter,
|
|
4391
|
+
pagePath: window.location.pathname,
|
|
4392
|
+
...scheduleId ? { scheduleId } : {}
|
|
4393
|
+
});
|
|
4394
|
+
tracker.textContent = JSON.stringify(sections);
|
|
4395
|
+
}
|
|
4396
|
+
return true;
|
|
4397
|
+
}
|
|
4398
|
+
function mountSchedulingEntries(entries, notifyOnConnect = false) {
|
|
4399
|
+
const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
|
|
4400
|
+
for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
|
|
4401
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
4402
|
+
const { insertAfter, scheduleId } = pending[i];
|
|
4403
|
+
if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
|
|
4404
|
+
pending.splice(i, 1);
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
3486
4409
|
function collectEditableNodes() {
|
|
3487
4410
|
return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
3488
4411
|
if (el.dataset.ohwEditable === "image") {
|
|
@@ -3640,7 +4563,7 @@ var TOOLBAR_GROUPS = [
|
|
|
3640
4563
|
];
|
|
3641
4564
|
function GlowFrame({ rect, elRef }) {
|
|
3642
4565
|
const GAP = 6;
|
|
3643
|
-
return /* @__PURE__ */
|
|
4566
|
+
return /* @__PURE__ */ jsx6(
|
|
3644
4567
|
"div",
|
|
3645
4568
|
{
|
|
3646
4569
|
ref: elRef,
|
|
@@ -3691,7 +4614,7 @@ function FloatingToolbar({
|
|
|
3691
4614
|
activeCommands
|
|
3692
4615
|
}) {
|
|
3693
4616
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
3694
|
-
return /* @__PURE__ */
|
|
4617
|
+
return /* @__PURE__ */ jsx6(
|
|
3695
4618
|
"div",
|
|
3696
4619
|
{
|
|
3697
4620
|
ref: elRef,
|
|
@@ -3714,11 +4637,11 @@ function FloatingToolbar({
|
|
|
3714
4637
|
pointerEvents: "auto",
|
|
3715
4638
|
whiteSpace: "nowrap"
|
|
3716
4639
|
},
|
|
3717
|
-
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */
|
|
3718
|
-
gi > 0 && /* @__PURE__ */
|
|
4640
|
+
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
|
|
4641
|
+
gi > 0 && /* @__PURE__ */ jsx6("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
|
|
3719
4642
|
btns.map((btn) => {
|
|
3720
4643
|
const isActive = activeCommands.has(btn.cmd);
|
|
3721
|
-
return /* @__PURE__ */
|
|
4644
|
+
return /* @__PURE__ */ jsx6(
|
|
3722
4645
|
"button",
|
|
3723
4646
|
{
|
|
3724
4647
|
title: btn.title,
|
|
@@ -3744,7 +4667,7 @@ function FloatingToolbar({
|
|
|
3744
4667
|
flexShrink: 0,
|
|
3745
4668
|
padding: 6
|
|
3746
4669
|
},
|
|
3747
|
-
children: /* @__PURE__ */
|
|
4670
|
+
children: /* @__PURE__ */ jsx6(
|
|
3748
4671
|
"svg",
|
|
3749
4672
|
{
|
|
3750
4673
|
width: "16",
|
|
@@ -3777,7 +4700,7 @@ function StateToggle({
|
|
|
3777
4700
|
states,
|
|
3778
4701
|
onStateChange
|
|
3779
4702
|
}) {
|
|
3780
|
-
return /* @__PURE__ */
|
|
4703
|
+
return /* @__PURE__ */ jsx6(
|
|
3781
4704
|
ToggleGroup,
|
|
3782
4705
|
{
|
|
3783
4706
|
"data-ohw-state-toggle": "",
|
|
@@ -3791,30 +4714,46 @@ function StateToggle({
|
|
|
3791
4714
|
left: rect.right - 8,
|
|
3792
4715
|
transform: "translateX(-100%)"
|
|
3793
4716
|
},
|
|
3794
|
-
children: states.map((state) => /* @__PURE__ */
|
|
4717
|
+
children: states.map((state) => /* @__PURE__ */ jsx6(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
|
|
3795
4718
|
}
|
|
3796
4719
|
);
|
|
3797
4720
|
}
|
|
3798
4721
|
var contentCache = /* @__PURE__ */ new Map();
|
|
4722
|
+
function resolveSubdomain(subdomainFromQuery) {
|
|
4723
|
+
if (subdomainFromQuery) return subdomainFromQuery;
|
|
4724
|
+
if (typeof window !== "undefined") {
|
|
4725
|
+
const parts = window.location.hostname.split(".");
|
|
4726
|
+
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
4727
|
+
}
|
|
4728
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
|
|
4729
|
+
if (siteUrl) {
|
|
4730
|
+
try {
|
|
4731
|
+
const host = new URL(siteUrl).hostname;
|
|
4732
|
+
const siteParts = host.split(".");
|
|
4733
|
+
if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
|
|
4734
|
+
} catch {
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
return "";
|
|
4738
|
+
}
|
|
3799
4739
|
function OhhwellsBridge() {
|
|
3800
4740
|
const pathname = usePathname();
|
|
3801
4741
|
const router = useRouter();
|
|
3802
4742
|
const searchParams = useSearchParams();
|
|
3803
4743
|
const isEditMode = isEditSessionActive();
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
const
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
useEffect(() => {
|
|
4744
|
+
const [bridgeRoot, setBridgeRoot] = useState3(null);
|
|
4745
|
+
useEffect2(() => {
|
|
4746
|
+
const figtreeFontId = "ohw-figtree-font";
|
|
4747
|
+
if (!document.getElementById(figtreeFontId)) {
|
|
4748
|
+
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
4749
|
+
const preconnect2 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" });
|
|
4750
|
+
const stylesheet = Object.assign(document.createElement("link"), {
|
|
4751
|
+
id: figtreeFontId,
|
|
4752
|
+
rel: "stylesheet",
|
|
4753
|
+
href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
|
|
4754
|
+
});
|
|
4755
|
+
document.head.append(preconnect1, preconnect2, stylesheet);
|
|
4756
|
+
}
|
|
3818
4757
|
const el = document.createElement("div");
|
|
3819
4758
|
el.setAttribute("data-ohw-bridge-root", "");
|
|
3820
4759
|
el.setAttribute("data-ohw-bridge", "");
|
|
@@ -3826,39 +4765,40 @@ function OhhwellsBridge() {
|
|
|
3826
4765
|
};
|
|
3827
4766
|
}, []);
|
|
3828
4767
|
const subdomainFromQuery = searchParams.get("subdomain");
|
|
3829
|
-
const subdomain = subdomainFromQuery
|
|
3830
|
-
if (typeof window === "undefined") return "";
|
|
3831
|
-
const parts = window.location.hostname.split(".");
|
|
3832
|
-
return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
|
|
3833
|
-
})();
|
|
4768
|
+
const subdomain = resolveSubdomain(subdomainFromQuery);
|
|
3834
4769
|
const postToParent = useCallback((data) => {
|
|
3835
4770
|
if (typeof window !== "undefined" && window.parent !== window) {
|
|
3836
4771
|
window.parent.postMessage(data, "*");
|
|
3837
4772
|
}
|
|
3838
4773
|
}, []);
|
|
3839
|
-
const [fetchState, setFetchState] =
|
|
3840
|
-
const autoSaveTimers =
|
|
3841
|
-
const activeElRef =
|
|
3842
|
-
const originalContentRef =
|
|
3843
|
-
const activeStateElRef =
|
|
3844
|
-
const parentScrollRef =
|
|
3845
|
-
const toolbarElRef =
|
|
3846
|
-
const glowElRef =
|
|
3847
|
-
const hoveredImageRef =
|
|
3848
|
-
const
|
|
3849
|
-
const
|
|
4774
|
+
const [fetchState, setFetchState] = useState3("idle");
|
|
4775
|
+
const autoSaveTimers = useRef2(/* @__PURE__ */ new Map());
|
|
4776
|
+
const activeElRef = useRef2(null);
|
|
4777
|
+
const originalContentRef = useRef2(null);
|
|
4778
|
+
const activeStateElRef = useRef2(null);
|
|
4779
|
+
const parentScrollRef = useRef2(null);
|
|
4780
|
+
const toolbarElRef = useRef2(null);
|
|
4781
|
+
const glowElRef = useRef2(null);
|
|
4782
|
+
const hoveredImageRef = useRef2(null);
|
|
4783
|
+
const hoveredImageHasTextOverlapRef = useRef2(false);
|
|
4784
|
+
const hoveredGapRef = useRef2(null);
|
|
4785
|
+
const imageUnhoverTimerRef = useRef2(null);
|
|
4786
|
+
const imageShowTimerRef = useRef2(null);
|
|
4787
|
+
const editStylesRef = useRef2(null);
|
|
4788
|
+
const activateRef = useRef2(() => {
|
|
3850
4789
|
});
|
|
3851
|
-
const deactivateRef =
|
|
4790
|
+
const deactivateRef = useRef2(() => {
|
|
3852
4791
|
});
|
|
3853
|
-
const refreshActiveCommandsRef =
|
|
4792
|
+
const refreshActiveCommandsRef = useRef2(() => {
|
|
3854
4793
|
});
|
|
3855
|
-
const postToParentRef =
|
|
4794
|
+
const postToParentRef = useRef2(postToParent);
|
|
3856
4795
|
postToParentRef.current = postToParent;
|
|
3857
|
-
const [toolbarRect, setToolbarRect] =
|
|
3858
|
-
const [toggleState, setToggleState] =
|
|
3859
|
-
const [maxBadge, setMaxBadge] =
|
|
3860
|
-
const [activeCommands, setActiveCommands] =
|
|
3861
|
-
|
|
4796
|
+
const [toolbarRect, setToolbarRect] = useState3(null);
|
|
4797
|
+
const [toggleState, setToggleState] = useState3(null);
|
|
4798
|
+
const [maxBadge, setMaxBadge] = useState3(null);
|
|
4799
|
+
const [activeCommands, setActiveCommands] = useState3(/* @__PURE__ */ new Set());
|
|
4800
|
+
const [sectionGap, setSectionGap] = useState3(null);
|
|
4801
|
+
useEffect2(() => {
|
|
3862
4802
|
const update = () => {
|
|
3863
4803
|
const el = activeElRef.current;
|
|
3864
4804
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -3866,6 +4806,12 @@ function OhhwellsBridge() {
|
|
|
3866
4806
|
if (!prev || !activeStateElRef.current) return prev;
|
|
3867
4807
|
return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
|
|
3868
4808
|
});
|
|
4809
|
+
setSectionGap((prev) => {
|
|
4810
|
+
if (!prev) return prev;
|
|
4811
|
+
const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
|
|
4812
|
+
if (!anchor) return prev;
|
|
4813
|
+
return { ...prev, y: anchor.getBoundingClientRect().bottom };
|
|
4814
|
+
});
|
|
3869
4815
|
};
|
|
3870
4816
|
const vvp = window.visualViewport;
|
|
3871
4817
|
if (!vvp) return;
|
|
@@ -3907,6 +4853,10 @@ function OhhwellsBridge() {
|
|
|
3907
4853
|
const activate = useCallback((el) => {
|
|
3908
4854
|
if (activeElRef.current === el) return;
|
|
3909
4855
|
deactivate();
|
|
4856
|
+
if (hoveredImageRef.current) {
|
|
4857
|
+
hoveredImageRef.current = null;
|
|
4858
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4859
|
+
}
|
|
3910
4860
|
el.setAttribute("contenteditable", "true");
|
|
3911
4861
|
el.removeAttribute("data-ohw-hovered");
|
|
3912
4862
|
activeElRef.current = el;
|
|
@@ -3926,6 +4876,7 @@ function OhhwellsBridge() {
|
|
|
3926
4876
|
const applyContent = (content) => {
|
|
3927
4877
|
const imageLoads = [];
|
|
3928
4878
|
for (const [key, val] of Object.entries(content)) {
|
|
4879
|
+
if (key === "__ohw_sections") continue;
|
|
3929
4880
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3930
4881
|
if (el.dataset.ohwEditable === "image") {
|
|
3931
4882
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -3944,6 +4895,7 @@ function OhhwellsBridge() {
|
|
|
3944
4895
|
}
|
|
3945
4896
|
});
|
|
3946
4897
|
}
|
|
4898
|
+
initSectionsFromContent(content, true);
|
|
3947
4899
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
3948
4900
|
}) : Promise.resolve();
|
|
3949
4901
|
};
|
|
@@ -3954,7 +4906,7 @@ function OhhwellsBridge() {
|
|
|
3954
4906
|
}
|
|
3955
4907
|
let cancelled = false;
|
|
3956
4908
|
setFetchState("loading");
|
|
3957
|
-
const apiUrl =
|
|
4909
|
+
const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
3958
4910
|
fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
|
|
3959
4911
|
if (cancelled) return;
|
|
3960
4912
|
const content = data?.content ?? {};
|
|
@@ -3968,12 +4920,15 @@ function OhhwellsBridge() {
|
|
|
3968
4920
|
cancelled = true;
|
|
3969
4921
|
};
|
|
3970
4922
|
}, [subdomain, isEditMode, pathname]);
|
|
3971
|
-
|
|
4923
|
+
useEffect2(() => {
|
|
3972
4924
|
if (!subdomain || isEditMode) return;
|
|
4925
|
+
let debounceTimer = null;
|
|
3973
4926
|
const applyFromCache = () => {
|
|
3974
4927
|
const content = contentCache.get(subdomain);
|
|
3975
4928
|
if (!content) return;
|
|
4929
|
+
retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
|
|
3976
4930
|
for (const [key, val] of Object.entries(content)) {
|
|
4931
|
+
if (key === "__ohw_sections") continue;
|
|
3977
4932
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3978
4933
|
if (el.dataset.ohwEditable === "image") {
|
|
3979
4934
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -3987,21 +4942,28 @@ function OhhwellsBridge() {
|
|
|
3987
4942
|
});
|
|
3988
4943
|
}
|
|
3989
4944
|
};
|
|
3990
|
-
|
|
3991
|
-
|
|
4945
|
+
const scheduleApply = () => {
|
|
4946
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4947
|
+
debounceTimer = setTimeout(applyFromCache, 150);
|
|
4948
|
+
};
|
|
4949
|
+
scheduleApply();
|
|
4950
|
+
const observer = new MutationObserver(scheduleApply);
|
|
3992
4951
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
3993
|
-
return () =>
|
|
3994
|
-
|
|
4952
|
+
return () => {
|
|
4953
|
+
observer.disconnect();
|
|
4954
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4955
|
+
};
|
|
4956
|
+
}, [subdomain, isEditMode, pathname]);
|
|
3995
4957
|
useLayoutEffect(() => {
|
|
3996
4958
|
const el = document.getElementById("ohw-loader");
|
|
3997
4959
|
if (!el) return;
|
|
3998
4960
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
3999
4961
|
el.style.display = visible ? "flex" : "none";
|
|
4000
4962
|
}, [subdomain, fetchState]);
|
|
4001
|
-
|
|
4963
|
+
useEffect2(() => {
|
|
4002
4964
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
4003
4965
|
}, [pathname, postToParent]);
|
|
4004
|
-
|
|
4966
|
+
useEffect2(() => {
|
|
4005
4967
|
if (!isEditMode) return;
|
|
4006
4968
|
const measure = () => {
|
|
4007
4969
|
const h = document.body.scrollHeight;
|
|
@@ -4025,7 +4987,7 @@ function OhhwellsBridge() {
|
|
|
4025
4987
|
window.removeEventListener("resize", handleResize);
|
|
4026
4988
|
};
|
|
4027
4989
|
}, [pathname, isEditMode, postToParent]);
|
|
4028
|
-
|
|
4990
|
+
useEffect2(() => {
|
|
4029
4991
|
if (!subdomainFromQuery || isEditMode) return;
|
|
4030
4992
|
const handleClick = (e) => {
|
|
4031
4993
|
const anchor = e.target.closest("a");
|
|
@@ -4041,7 +5003,7 @@ function OhhwellsBridge() {
|
|
|
4041
5003
|
document.addEventListener("click", handleClick, true);
|
|
4042
5004
|
return () => document.removeEventListener("click", handleClick, true);
|
|
4043
5005
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
4044
|
-
|
|
5006
|
+
useEffect2(() => {
|
|
4045
5007
|
if (!isEditMode) {
|
|
4046
5008
|
editStylesRef.current?.base.remove();
|
|
4047
5009
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -4081,6 +5043,7 @@ function OhhwellsBridge() {
|
|
|
4081
5043
|
[data-ohw-editable][contenteditable] *::selection { background: ${PRIMARY}59 !important; }
|
|
4082
5044
|
[data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
|
|
4083
5045
|
[data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
|
|
5046
|
+
[data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
|
|
4084
5047
|
`;
|
|
4085
5048
|
const forceHover = document.createElement("style");
|
|
4086
5049
|
forceHover.setAttribute("data-ohw-active-state-style", "");
|
|
@@ -4107,10 +5070,12 @@ function OhhwellsBridge() {
|
|
|
4107
5070
|
if (editable) {
|
|
4108
5071
|
if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
|
|
4109
5072
|
e.preventDefault();
|
|
5073
|
+
e.stopPropagation();
|
|
4110
5074
|
postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
|
|
4111
5075
|
return;
|
|
4112
5076
|
}
|
|
4113
5077
|
e.preventDefault();
|
|
5078
|
+
e.stopPropagation();
|
|
4114
5079
|
activateRef.current(editable);
|
|
4115
5080
|
return;
|
|
4116
5081
|
}
|
|
@@ -4173,12 +5138,15 @@ function OhhwellsBridge() {
|
|
|
4173
5138
|
}
|
|
4174
5139
|
return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
|
|
4175
5140
|
};
|
|
4176
|
-
const postImageHover = (imgEl, isDragOver = false) => {
|
|
5141
|
+
const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
|
|
4177
5142
|
const r2 = getVisibleRect(imgEl);
|
|
5143
|
+
const hasTextOverlap = forceTextOverlap ?? false;
|
|
5144
|
+
hoveredImageHasTextOverlapRef.current = hasTextOverlap;
|
|
4178
5145
|
postToParentRef.current({
|
|
4179
5146
|
type: "ow:image-hover",
|
|
4180
5147
|
key: imgEl.dataset.ohwKey ?? "",
|
|
4181
5148
|
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
|
|
5149
|
+
hasTextOverlap,
|
|
4182
5150
|
...isDragOver ? { isDragOver: true } : {}
|
|
4183
5151
|
});
|
|
4184
5152
|
const track = imgEl.closest("[data-ohw-hover-pause]");
|
|
@@ -4225,7 +5193,7 @@ function OhhwellsBridge() {
|
|
|
4225
5193
|
}
|
|
4226
5194
|
return smallest;
|
|
4227
5195
|
};
|
|
4228
|
-
const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false
|
|
5196
|
+
const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
|
|
4229
5197
|
const toggleEl = document.querySelector("[data-ohw-state-toggle]");
|
|
4230
5198
|
if (toggleEl) {
|
|
4231
5199
|
const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
@@ -4251,33 +5219,61 @@ function OhhwellsBridge() {
|
|
|
4251
5219
|
}
|
|
4252
5220
|
return;
|
|
4253
5221
|
}
|
|
4254
|
-
const
|
|
4255
|
-
|
|
4256
|
-
|
|
5222
|
+
const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
|
|
5223
|
+
const textEditable = Array.from(
|
|
5224
|
+
document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
|
|
5225
|
+
).find((el) => {
|
|
5226
|
+
const er = el.getBoundingClientRect();
|
|
5227
|
+
return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
|
|
5228
|
+
});
|
|
5229
|
+
if (imageUnhoverTimerRef.current) {
|
|
5230
|
+
clearTimeout(imageUnhoverTimerRef.current);
|
|
5231
|
+
imageUnhoverTimerRef.current = null;
|
|
5232
|
+
}
|
|
5233
|
+
if (imageShowTimerRef.current) {
|
|
5234
|
+
clearTimeout(imageShowTimerRef.current);
|
|
5235
|
+
imageShowTimerRef.current = null;
|
|
5236
|
+
}
|
|
5237
|
+
if (textEditable) {
|
|
5238
|
+
if (textEditable.hasAttribute("contenteditable")) {
|
|
5239
|
+
if (hoveredImageRef.current) {
|
|
5240
|
+
hoveredImageRef.current = null;
|
|
5241
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
5242
|
+
resumeAnimTracks();
|
|
5243
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
5244
|
+
}
|
|
5245
|
+
return;
|
|
5246
|
+
}
|
|
5247
|
+
if (isStateCardImage) {
|
|
5248
|
+
if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
|
|
5249
|
+
hoveredImageRef.current = imgEl;
|
|
5250
|
+
postImageHover(imgEl, isDragOver, true);
|
|
5251
|
+
}
|
|
5252
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5253
|
+
textEditable.setAttribute("data-ohw-hovered", "");
|
|
5254
|
+
return;
|
|
5255
|
+
}
|
|
4257
5256
|
if (hoveredImageRef.current) {
|
|
4258
5257
|
hoveredImageRef.current = null;
|
|
5258
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
4259
5259
|
resumeAnimTracks();
|
|
4260
5260
|
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4261
5261
|
}
|
|
4262
5262
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4263
|
-
|
|
5263
|
+
textEditable.setAttribute("data-ohw-hovered", "");
|
|
4264
5264
|
return;
|
|
4265
5265
|
}
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
if (hoveredImageRef.current) {
|
|
4272
|
-
hoveredImageRef.current = null;
|
|
4273
|
-
resumeAnimTracks();
|
|
4274
|
-
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4275
|
-
}
|
|
4276
|
-
return;
|
|
5266
|
+
if (hoveredGapRef.current) {
|
|
5267
|
+
if (hoveredImageRef.current) {
|
|
5268
|
+
hoveredImageRef.current = null;
|
|
5269
|
+
resumeAnimTracks();
|
|
5270
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4277
5271
|
}
|
|
5272
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5273
|
+
return;
|
|
4278
5274
|
}
|
|
4279
5275
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4280
|
-
if (imgEl !== hoveredImageRef.current) {
|
|
5276
|
+
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
4281
5277
|
hoveredImageRef.current = imgEl;
|
|
4282
5278
|
postImageHover(imgEl, isDragOver);
|
|
4283
5279
|
} else if (isDragOver) {
|
|
@@ -4286,9 +5282,29 @@ function OhhwellsBridge() {
|
|
|
4286
5282
|
} else {
|
|
4287
5283
|
const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
|
|
4288
5284
|
if (!inIframeView) return;
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
5285
|
+
if (imageUnhoverTimerRef.current) {
|
|
5286
|
+
clearTimeout(imageUnhoverTimerRef.current);
|
|
5287
|
+
imageUnhoverTimerRef.current = null;
|
|
5288
|
+
}
|
|
5289
|
+
if (imageShowTimerRef.current) {
|
|
5290
|
+
clearTimeout(imageShowTimerRef.current);
|
|
5291
|
+
imageShowTimerRef.current = null;
|
|
5292
|
+
}
|
|
5293
|
+
if (hoveredImageRef.current) {
|
|
5294
|
+
hoveredImageRef.current = null;
|
|
5295
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
5296
|
+
resumeAnimTracks();
|
|
5297
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
5298
|
+
}
|
|
5299
|
+
const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
5300
|
+
const textEl = Array.from(
|
|
5301
|
+
document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
|
|
5302
|
+
).find((el) => {
|
|
5303
|
+
const er = el.getBoundingClientRect();
|
|
5304
|
+
return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
|
|
5305
|
+
});
|
|
5306
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5307
|
+
if (textEl && !textEl.hasAttribute("contenteditable")) textEl.setAttribute("data-ohw-hovered", "");
|
|
4292
5308
|
}
|
|
4293
5309
|
};
|
|
4294
5310
|
const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
|
|
@@ -4307,27 +5323,53 @@ function OhhwellsBridge() {
|
|
|
4307
5323
|
}) ?? null;
|
|
4308
5324
|
const currentLocked = activeStateElRef.current?.hasAttribute("data-ohw-active-state") ? activeStateElRef.current : null;
|
|
4309
5325
|
const active = found ?? currentLocked;
|
|
5326
|
+
const prev = activeStateElRef.current;
|
|
4310
5327
|
activeStateElRef.current = active;
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
if (active
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
5328
|
+
if (active !== prev) {
|
|
5329
|
+
if (prev) prev.removeAttribute("data-ohw-state-hovered");
|
|
5330
|
+
if (active) {
|
|
5331
|
+
if (active.querySelector("[data-ohw-state-view]")) active.setAttribute("data-ohw-state-hovered", "");
|
|
5332
|
+
const states = deriveStates(active.dataset.ohwEditableState ?? "");
|
|
5333
|
+
const activeState = active.hasAttribute("data-ohw-active-state") ? (active.getAttribute("data-ohw-active-state") ?? "Default").charAt(0).toUpperCase() + (active.getAttribute("data-ohw-active-state") ?? "").slice(1) : "Default";
|
|
5334
|
+
setToggleState({ rect: active.getBoundingClientRect(), activeState, states });
|
|
5335
|
+
} else {
|
|
5336
|
+
setToggleState(null);
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
};
|
|
5340
|
+
const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
|
|
5341
|
+
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
5342
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
5343
|
+
const ZONE = 20;
|
|
5344
|
+
for (let i = 0; i < sections.length; i++) {
|
|
5345
|
+
const a = sections[i];
|
|
5346
|
+
const b = sections[i + 1] ?? null;
|
|
5347
|
+
const boundaryY = a.getBoundingClientRect().bottom;
|
|
5348
|
+
if (Math.abs(y - boundaryY) <= ZONE) {
|
|
5349
|
+
const insertAfter = a.dataset.ohwSection ?? "";
|
|
5350
|
+
const insertBefore = b?.dataset.ohwSection ?? null;
|
|
5351
|
+
hoveredGapRef.current = { insertAfter, insertBefore };
|
|
5352
|
+
setSectionGap({ insertAfter, insertBefore, y: boundaryY });
|
|
5353
|
+
return;
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
if (hoveredGapRef.current) {
|
|
5357
|
+
hoveredGapRef.current = null;
|
|
5358
|
+
setSectionGap(null);
|
|
4319
5359
|
}
|
|
4320
5360
|
};
|
|
4321
5361
|
const handleMouseMove = (e) => {
|
|
4322
5362
|
const { clientX, clientY } = e;
|
|
5363
|
+
probeSectionGapAt(clientX, clientY);
|
|
4323
5364
|
probeImageAt(clientX, clientY);
|
|
4324
5365
|
probeHoverCardsAt(clientX, clientY);
|
|
4325
5366
|
};
|
|
4326
5367
|
const handlePointerSync = (e) => {
|
|
4327
5368
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
4328
|
-
const { clientX, clientY
|
|
5369
|
+
const { clientX, clientY } = e.data;
|
|
4329
5370
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
4330
|
-
|
|
5371
|
+
probeSectionGapAt(clientX, clientY);
|
|
5372
|
+
probeImageAt(clientX, clientY);
|
|
4331
5373
|
probeHoverCardsAt(clientX, clientY);
|
|
4332
5374
|
};
|
|
4333
5375
|
const handleDragOver = (e) => {
|
|
@@ -4340,7 +5382,8 @@ function OhhwellsBridge() {
|
|
|
4340
5382
|
e.dataTransfer.dropEffect = "copy";
|
|
4341
5383
|
if (hoveredImageRef.current !== el) {
|
|
4342
5384
|
hoveredImageRef.current = el;
|
|
4343
|
-
|
|
5385
|
+
const isStateCard = !!el.closest("[data-ohw-editable-state]");
|
|
5386
|
+
postImageHover(el, true, isStateCard ? true : void 0);
|
|
4344
5387
|
}
|
|
4345
5388
|
};
|
|
4346
5389
|
const handleDragLeave = (e) => {
|
|
@@ -4483,7 +5526,12 @@ function OhhwellsBridge() {
|
|
|
4483
5526
|
if (e.data?.type !== "ow:hydrate") return;
|
|
4484
5527
|
const content = e.data.content;
|
|
4485
5528
|
if (!content) return;
|
|
5529
|
+
let sectionsJson = null;
|
|
4486
5530
|
for (const [key, val] of Object.entries(content)) {
|
|
5531
|
+
if (key === "__ohw_sections") {
|
|
5532
|
+
sectionsJson = val;
|
|
5533
|
+
continue;
|
|
5534
|
+
}
|
|
4487
5535
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4488
5536
|
if (el.dataset.ohwEditable === "image") {
|
|
4489
5537
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4495,6 +5543,9 @@ function OhhwellsBridge() {
|
|
|
4495
5543
|
}
|
|
4496
5544
|
});
|
|
4497
5545
|
}
|
|
5546
|
+
if (sectionsJson) {
|
|
5547
|
+
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
5548
|
+
}
|
|
4498
5549
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
4499
5550
|
};
|
|
4500
5551
|
window.addEventListener("message", handleHydrate);
|
|
@@ -4526,17 +5577,75 @@ function OhhwellsBridge() {
|
|
|
4526
5577
|
setToggleState((prev) => prev ? { ...prev, rect } : null);
|
|
4527
5578
|
}
|
|
4528
5579
|
if (hoveredImageRef.current) {
|
|
4529
|
-
const
|
|
5580
|
+
const el = hoveredImageRef.current;
|
|
5581
|
+
const r2 = el.getBoundingClientRect();
|
|
4530
5582
|
postToParentRef.current({
|
|
4531
5583
|
type: "ow:image-hover",
|
|
4532
|
-
key:
|
|
4533
|
-
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height }
|
|
5584
|
+
key: el.dataset.ohwKey ?? "",
|
|
5585
|
+
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
|
|
5586
|
+
hasTextOverlap: hoveredImageHasTextOverlapRef.current
|
|
4534
5587
|
});
|
|
4535
5588
|
}
|
|
4536
5589
|
};
|
|
4537
5590
|
const handleSave = (e) => {
|
|
4538
5591
|
if (e.data?.type !== "ow:save") return;
|
|
4539
|
-
|
|
5592
|
+
const nodes = collectEditableNodes();
|
|
5593
|
+
const tracker = document.querySelector("[data-ohw-sections-tracker]");
|
|
5594
|
+
if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
|
|
5595
|
+
postToParentRef.current({ type: "ow:save-result", nodes });
|
|
5596
|
+
};
|
|
5597
|
+
const handleInsertSection = (e) => {
|
|
5598
|
+
if (e.data?.type !== "ow:insert-section") return;
|
|
5599
|
+
const { widgetType, insertAfter, insertBefore } = e.data;
|
|
5600
|
+
if (widgetType !== "scheduling") return;
|
|
5601
|
+
const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
|
|
5602
|
+
if (inserted) {
|
|
5603
|
+
const tracker = getSectionsTracker();
|
|
5604
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
|
|
5605
|
+
const h = document.documentElement.scrollHeight;
|
|
5606
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
5607
|
+
}
|
|
5608
|
+
};
|
|
5609
|
+
const handleSwitchSchedule = (e) => {
|
|
5610
|
+
if (e.data?.type !== "ow:switch-schedule") return;
|
|
5611
|
+
const scheduleId = e.data.scheduleId;
|
|
5612
|
+
const insertAfter = e.data.insertAfter;
|
|
5613
|
+
if (!scheduleId || !insertAfter) return;
|
|
5614
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
5615
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
5616
|
+
};
|
|
5617
|
+
const handleScheduleLinked = (e) => {
|
|
5618
|
+
if (e.data?.type !== "ow:schedule-linked") return;
|
|
5619
|
+
const scheduleId = e.data.scheduleId;
|
|
5620
|
+
const insertAfter = e.data.insertAfter;
|
|
5621
|
+
if (!scheduleId || !insertAfter) return;
|
|
5622
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
5623
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
5624
|
+
};
|
|
5625
|
+
const handleClearSchedulingWidget = (e) => {
|
|
5626
|
+
if (e.data?.type !== "ow:clear-scheduling-widget") return;
|
|
5627
|
+
const insertAfter = e.data.insertAfter;
|
|
5628
|
+
if (!insertAfter) return;
|
|
5629
|
+
const text = updateSectionScheduleId(insertAfter, null);
|
|
5630
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
5631
|
+
};
|
|
5632
|
+
const handleRemoveSchedulingSection = (e) => {
|
|
5633
|
+
if (e.data?.type !== "ow:remove-scheduling-section") return;
|
|
5634
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
|
|
5635
|
+
el.remove();
|
|
5636
|
+
});
|
|
5637
|
+
const tracker = getSectionsTracker();
|
|
5638
|
+
let sections = [];
|
|
5639
|
+
try {
|
|
5640
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5641
|
+
} catch {
|
|
5642
|
+
}
|
|
5643
|
+
const currentPath = window.location.pathname;
|
|
5644
|
+
const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
|
|
5645
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5646
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
5647
|
+
const h = document.documentElement.scrollHeight;
|
|
5648
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
4540
5649
|
};
|
|
4541
5650
|
const handleSelectionChange = () => {
|
|
4542
5651
|
if (!activeElRef.current) return;
|
|
@@ -4604,12 +5713,54 @@ function OhhwellsBridge() {
|
|
|
4604
5713
|
parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
|
|
4605
5714
|
if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
|
|
4606
5715
|
};
|
|
5716
|
+
const handleClickAt = (e) => {
|
|
5717
|
+
if (e.data?.type !== "ow:click-at") return;
|
|
5718
|
+
const { clientX, clientY } = e.data;
|
|
5719
|
+
const allImages = Array.from(
|
|
5720
|
+
document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
|
|
5721
|
+
).filter((el) => !!el.closest("[data-ohw-editable-state]"));
|
|
5722
|
+
const stateCardImage = allImages.find((el) => {
|
|
5723
|
+
const ownerCard = el.closest("[data-ohw-editable-state]");
|
|
5724
|
+
if (ownerCard) {
|
|
5725
|
+
const inHoverState = ownerCard.getAttribute("data-ohw-active-state") === "hover";
|
|
5726
|
+
const elState = el.dataset.ohwState;
|
|
5727
|
+
if (el === ownerCard && inHoverState) return false;
|
|
5728
|
+
if (elState === "default" && inHoverState) return false;
|
|
5729
|
+
if (elState === "hover" && !inHoverState) return false;
|
|
5730
|
+
}
|
|
5731
|
+
const r2 = el.getBoundingClientRect();
|
|
5732
|
+
return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
|
|
5733
|
+
});
|
|
5734
|
+
if (stateCardImage) {
|
|
5735
|
+
postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
|
|
5736
|
+
return;
|
|
5737
|
+
}
|
|
5738
|
+
const textEditable = Array.from(
|
|
5739
|
+
document.querySelectorAll(
|
|
5740
|
+
'[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
|
|
5741
|
+
)
|
|
5742
|
+
).find((el) => {
|
|
5743
|
+
const r2 = el.getBoundingClientRect();
|
|
5744
|
+
return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
|
|
5745
|
+
});
|
|
5746
|
+
if (textEditable) {
|
|
5747
|
+
activateRef.current(textEditable);
|
|
5748
|
+
return;
|
|
5749
|
+
}
|
|
5750
|
+
deactivateRef.current();
|
|
5751
|
+
};
|
|
4607
5752
|
window.addEventListener("message", handleSave);
|
|
5753
|
+
window.addEventListener("message", handleInsertSection);
|
|
5754
|
+
window.addEventListener("message", handleSwitchSchedule);
|
|
5755
|
+
window.addEventListener("message", handleScheduleLinked);
|
|
5756
|
+
window.addEventListener("message", handleClearSchedulingWidget);
|
|
5757
|
+
window.addEventListener("message", handleRemoveSchedulingSection);
|
|
4608
5758
|
window.addEventListener("message", handleImageUrl);
|
|
4609
5759
|
window.addEventListener("message", handleAnimLock);
|
|
4610
5760
|
window.addEventListener("message", handleCanvasHeight);
|
|
4611
5761
|
window.addEventListener("message", handleParentScroll);
|
|
4612
5762
|
window.addEventListener("message", handlePointerSync);
|
|
5763
|
+
window.addEventListener("message", handleClickAt);
|
|
4613
5764
|
document.addEventListener("click", handleClick, true);
|
|
4614
5765
|
document.addEventListener("paste", handlePaste, true);
|
|
4615
5766
|
document.addEventListener("input", handleInput, true);
|
|
@@ -4638,18 +5789,26 @@ function OhhwellsBridge() {
|
|
|
4638
5789
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
4639
5790
|
window.removeEventListener("scroll", handleScroll, true);
|
|
4640
5791
|
window.removeEventListener("message", handleSave);
|
|
5792
|
+
window.removeEventListener("message", handleInsertSection);
|
|
5793
|
+
window.removeEventListener("message", handleSwitchSchedule);
|
|
5794
|
+
window.removeEventListener("message", handleScheduleLinked);
|
|
5795
|
+
window.removeEventListener("message", handleClearSchedulingWidget);
|
|
5796
|
+
window.removeEventListener("message", handleRemoveSchedulingSection);
|
|
4641
5797
|
window.removeEventListener("message", handleImageUrl);
|
|
4642
5798
|
window.removeEventListener("message", handleAnimLock);
|
|
4643
5799
|
window.removeEventListener("message", handleCanvasHeight);
|
|
4644
5800
|
window.removeEventListener("message", handleParentScroll);
|
|
4645
5801
|
window.removeEventListener("message", handlePointerSync);
|
|
5802
|
+
window.removeEventListener("message", handleClickAt);
|
|
4646
5803
|
window.removeEventListener("message", handleHydrate);
|
|
4647
5804
|
window.removeEventListener("message", handleDeactivate);
|
|
4648
5805
|
autoSaveTimers.current.forEach(clearTimeout);
|
|
4649
5806
|
autoSaveTimers.current.clear();
|
|
5807
|
+
if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
|
|
5808
|
+
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
4650
5809
|
};
|
|
4651
5810
|
}, [isEditMode, refreshStateRules]);
|
|
4652
|
-
|
|
5811
|
+
useEffect2(() => {
|
|
4653
5812
|
if (!isEditMode) return;
|
|
4654
5813
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
4655
5814
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -4691,12 +5850,12 @@ function OhhwellsBridge() {
|
|
|
4691
5850
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
4692
5851
|
}, [deactivate]);
|
|
4693
5852
|
return bridgeRoot ? createPortal(
|
|
4694
|
-
/* @__PURE__ */
|
|
4695
|
-
toolbarRect && /* @__PURE__ */
|
|
4696
|
-
/* @__PURE__ */
|
|
4697
|
-
/* @__PURE__ */
|
|
5853
|
+
/* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
5854
|
+
toolbarRect && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
5855
|
+
/* @__PURE__ */ jsx6(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
5856
|
+
/* @__PURE__ */ jsx6(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
|
|
4698
5857
|
] }),
|
|
4699
|
-
maxBadge && /* @__PURE__ */
|
|
5858
|
+
maxBadge && /* @__PURE__ */ jsxs3(
|
|
4700
5859
|
"div",
|
|
4701
5860
|
{
|
|
4702
5861
|
"data-ohw-max-badge": "",
|
|
@@ -4722,7 +5881,7 @@ function OhhwellsBridge() {
|
|
|
4722
5881
|
]
|
|
4723
5882
|
}
|
|
4724
5883
|
),
|
|
4725
|
-
toggleState && /* @__PURE__ */
|
|
5884
|
+
toggleState && /* @__PURE__ */ jsx6(
|
|
4726
5885
|
StateToggle,
|
|
4727
5886
|
{
|
|
4728
5887
|
rect: toggleState.rect,
|
|
@@ -4730,6 +5889,31 @@ function OhhwellsBridge() {
|
|
|
4730
5889
|
states: toggleState.states,
|
|
4731
5890
|
onStateChange: handleStateChange
|
|
4732
5891
|
}
|
|
5892
|
+
),
|
|
5893
|
+
sectionGap && /* @__PURE__ */ jsxs3(
|
|
5894
|
+
"div",
|
|
5895
|
+
{
|
|
5896
|
+
"data-ohw-section-insert-line": "",
|
|
5897
|
+
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
5898
|
+
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
5899
|
+
children: [
|
|
5900
|
+
/* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
5901
|
+
/* @__PURE__ */ jsx6(
|
|
5902
|
+
Badge,
|
|
5903
|
+
{
|
|
5904
|
+
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",
|
|
5905
|
+
onClick: () => {
|
|
5906
|
+
window.parent.postMessage(
|
|
5907
|
+
{ type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
|
|
5908
|
+
"*"
|
|
5909
|
+
);
|
|
5910
|
+
},
|
|
5911
|
+
children: "Add Section"
|
|
5912
|
+
}
|
|
5913
|
+
),
|
|
5914
|
+
/* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
5915
|
+
]
|
|
5916
|
+
}
|
|
4733
5917
|
)
|
|
4734
5918
|
] }),
|
|
4735
5919
|
bridgeRoot
|
|
@@ -4737,6 +5921,7 @@ function OhhwellsBridge() {
|
|
|
4737
5921
|
}
|
|
4738
5922
|
export {
|
|
4739
5923
|
OhhwellsBridge,
|
|
5924
|
+
SchedulingWidget,
|
|
4740
5925
|
Toggle,
|
|
4741
5926
|
ToggleGroup,
|
|
4742
5927
|
ToggleGroupItem,
|