@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.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
32
32
|
var index_exports = {};
|
|
33
33
|
__export(index_exports, {
|
|
34
34
|
OhhwellsBridge: () => OhhwellsBridge,
|
|
35
|
+
SchedulingWidget: () => SchedulingWidget,
|
|
35
36
|
Toggle: () => Toggle,
|
|
36
37
|
ToggleGroup: () => ToggleGroup,
|
|
37
38
|
ToggleGroupItem: () => ToggleGroupItem,
|
|
@@ -40,10 +41,752 @@ __export(index_exports, {
|
|
|
40
41
|
module.exports = __toCommonJS(index_exports);
|
|
41
42
|
|
|
42
43
|
// src/OhhwellsBridge.tsx
|
|
43
|
-
var
|
|
44
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
45
|
+
var import_client = require("react-dom/client");
|
|
46
|
+
var import_react_dom = require("react-dom");
|
|
47
|
+
|
|
48
|
+
// src/ui/SchedulingWidget.tsx
|
|
49
|
+
var import_react2 = require("react");
|
|
50
|
+
|
|
51
|
+
// src/ui/EmailCaptureModal.tsx
|
|
52
|
+
var import_react = require("react");
|
|
53
|
+
var import_radix_ui = require("radix-ui");
|
|
54
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
55
|
+
function Spinner() {
|
|
56
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
57
|
+
"svg",
|
|
58
|
+
{
|
|
59
|
+
width: "32",
|
|
60
|
+
height: "32",
|
|
61
|
+
viewBox: "0 0 32 32",
|
|
62
|
+
fill: "none",
|
|
63
|
+
style: { animation: "ohw-spin 0.75s linear infinite" },
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
|
|
66
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
|
|
67
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
68
|
+
"path",
|
|
69
|
+
{
|
|
70
|
+
d: "M16 4a12 12 0 0 1 12 12",
|
|
71
|
+
stroke: "#3b82f6",
|
|
72
|
+
strokeWidth: "3",
|
|
73
|
+
strokeLinecap: "round"
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
81
|
+
const [email, setEmail] = (0, import_react.useState)("");
|
|
82
|
+
const [submittedEmail, setSubmittedEmail] = (0, import_react.useState)("");
|
|
83
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
84
|
+
const [success, setSuccess] = (0, import_react.useState)(false);
|
|
85
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
86
|
+
const isBook = title === "Confirm your spot";
|
|
87
|
+
const handleSubmit = async () => {
|
|
88
|
+
if (!email.trim()) return;
|
|
89
|
+
setLoading(true);
|
|
90
|
+
setError(null);
|
|
91
|
+
try {
|
|
92
|
+
await onSubmit(email.trim());
|
|
93
|
+
setSubmittedEmail(email.trim());
|
|
94
|
+
setSuccess(true);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
97
|
+
} finally {
|
|
98
|
+
setLoading(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const handleKeyDown = (e) => {
|
|
102
|
+
if (e.key === "Enter" && !loading) handleSubmit();
|
|
103
|
+
};
|
|
104
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_radix_ui.Dialog.Root, { open: true, onOpenChange: (open) => {
|
|
105
|
+
if (!open) onClose();
|
|
106
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_radix_ui.Dialog.Portal, { children: [
|
|
107
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
108
|
+
import_radix_ui.Dialog.Overlay,
|
|
109
|
+
{
|
|
110
|
+
className: "fixed inset-0 z-50",
|
|
111
|
+
style: { background: "rgba(0,0,0,0.45)" }
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
115
|
+
import_radix_ui.Dialog.Content,
|
|
116
|
+
{
|
|
117
|
+
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",
|
|
118
|
+
style: { maxWidth: 400 },
|
|
119
|
+
children: [
|
|
120
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3 p-6", children: [
|
|
121
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
122
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
123
|
+
import_radix_ui.Dialog.Title,
|
|
124
|
+
{
|
|
125
|
+
className: "font-body text-xl font-bold m-0 leading-tight",
|
|
126
|
+
style: { color: "var(--color-dark,#200C02)" },
|
|
127
|
+
children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
131
|
+
import_radix_ui.Dialog.Description,
|
|
132
|
+
{
|
|
133
|
+
className: "font-body text-sm m-0",
|
|
134
|
+
style: { color: "#6B7280" },
|
|
135
|
+
children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
] }),
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
140
|
+
import_radix_ui.Dialog.Close,
|
|
141
|
+
{
|
|
142
|
+
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",
|
|
143
|
+
style: { color: "#6B7280" },
|
|
144
|
+
"aria-label": "Close",
|
|
145
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
146
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
147
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
148
|
+
] })
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
] }),
|
|
152
|
+
!success && (loading ? (
|
|
153
|
+
/* Loading state — full body replaced */
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
|
|
155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
156
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
|
|
157
|
+
] })
|
|
158
|
+
) : (
|
|
159
|
+
/* Email form */
|
|
160
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-7 pt-2 pb-6", children: [
|
|
161
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1.5 mb-8", children: [
|
|
162
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
164
|
+
"input",
|
|
165
|
+
{
|
|
166
|
+
type: "email",
|
|
167
|
+
value: email,
|
|
168
|
+
onChange: (e) => setEmail(e.target.value),
|
|
169
|
+
onKeyDown: handleKeyDown,
|
|
170
|
+
placeholder: "hello@gmail.com",
|
|
171
|
+
autoFocus: true,
|
|
172
|
+
className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
|
|
173
|
+
style: {
|
|
174
|
+
borderColor: "#E7E5E4",
|
|
175
|
+
background: "#fff",
|
|
176
|
+
color: "var(--color-dark,#200C02)"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
),
|
|
180
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-xs text-red-500 m-0", children: error })
|
|
181
|
+
] }),
|
|
182
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
183
|
+
"button",
|
|
184
|
+
{
|
|
185
|
+
onClick: handleSubmit,
|
|
186
|
+
disabled: !email.trim(),
|
|
187
|
+
className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
|
|
188
|
+
style: {
|
|
189
|
+
background: "var(--color-primary,#3D312B)",
|
|
190
|
+
color: "var(--color-light,#FEFFF0)"
|
|
191
|
+
},
|
|
192
|
+
children: "Send"
|
|
193
|
+
}
|
|
194
|
+
) })
|
|
195
|
+
] })
|
|
196
|
+
))
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
] }) });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/ui/SchedulingWidget.tsx
|
|
204
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
205
|
+
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
206
|
+
var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
207
|
+
function getApiDomain() {
|
|
208
|
+
const h = window.location.hostname;
|
|
209
|
+
for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
|
|
210
|
+
if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
|
|
211
|
+
}
|
|
212
|
+
return h;
|
|
213
|
+
}
|
|
214
|
+
function classRunsOnDate(cls, date, type) {
|
|
215
|
+
if (type === "FIXED") {
|
|
216
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
217
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
218
|
+
return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
|
|
219
|
+
}
|
|
220
|
+
return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
|
|
221
|
+
}
|
|
222
|
+
function formatClassTime(cls) {
|
|
223
|
+
let h = parseInt(cls.startTime.hour, 10);
|
|
224
|
+
const m = parseInt(cls.startTime.minutes, 10);
|
|
225
|
+
if (cls.startTime.time === "PM" && h !== 12) h += 12;
|
|
226
|
+
if (cls.startTime.time === "AM" && h === 12) h = 0;
|
|
227
|
+
const endTotal = h * 60 + m + cls.duration;
|
|
228
|
+
const eh = Math.floor(endTotal / 60) % 24;
|
|
229
|
+
const em = endTotal % 60;
|
|
230
|
+
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
231
|
+
}
|
|
232
|
+
function getBookingsOnDate(cls, date) {
|
|
233
|
+
if (!cls.bookings?.length) return 0;
|
|
234
|
+
const ds = date.toISOString().split("T")[0];
|
|
235
|
+
return cls.bookings.filter((b) => {
|
|
236
|
+
try {
|
|
237
|
+
return new Date(b.classDate).toISOString().split("T")[0] === ds;
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}).length;
|
|
242
|
+
}
|
|
243
|
+
function buildTimezoneLabel(tz) {
|
|
244
|
+
try {
|
|
245
|
+
const now = /* @__PURE__ */ new Date();
|
|
246
|
+
const timeStr = new Intl.DateTimeFormat("en-US", {
|
|
247
|
+
hour: "2-digit",
|
|
248
|
+
minute: "2-digit",
|
|
249
|
+
hour12: false,
|
|
250
|
+
timeZone: tz
|
|
251
|
+
}).format(now);
|
|
252
|
+
const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
253
|
+
const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
|
|
254
|
+
return `${timeStr} (${offsetPart}) ${city} time`;
|
|
255
|
+
} catch {
|
|
256
|
+
return tz;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function EmptyState({ inEditor }) {
|
|
260
|
+
const handleAddSchedule = () => {
|
|
261
|
+
if (inEditor) {
|
|
262
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.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: [
|
|
266
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
267
|
+
"svg",
|
|
268
|
+
{
|
|
269
|
+
width: "20",
|
|
270
|
+
height: "20",
|
|
271
|
+
viewBox: "0 0 24 24",
|
|
272
|
+
fill: "none",
|
|
273
|
+
stroke: "var(--color-accent, #A89B83)",
|
|
274
|
+
strokeWidth: "1.5",
|
|
275
|
+
strokeLinecap: "round",
|
|
276
|
+
strokeLinejoin: "round",
|
|
277
|
+
children: [
|
|
278
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
|
|
279
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
|
|
280
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
|
|
281
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
|
|
282
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
|
|
283
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
) }),
|
|
287
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
|
|
288
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
|
|
289
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.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." })
|
|
290
|
+
] }),
|
|
291
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
292
|
+
"button",
|
|
293
|
+
{
|
|
294
|
+
onClick: handleAddSchedule,
|
|
295
|
+
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",
|
|
296
|
+
children: "Add Schedule"
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
] });
|
|
300
|
+
}
|
|
301
|
+
function LoadingSkeleton() {
|
|
302
|
+
const shimmer = {
|
|
303
|
+
background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
|
|
304
|
+
backgroundSize: "200% 100%",
|
|
305
|
+
animation: "ohw-shimmer 1.5s infinite"
|
|
306
|
+
};
|
|
307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
|
|
308
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
|
|
309
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
|
|
310
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
|
|
311
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
|
|
312
|
+
] }, i)) }),
|
|
313
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
314
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:hidden", children: [
|
|
315
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0", children: [
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
|
|
317
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
|
|
318
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
|
|
319
|
+
] }),
|
|
320
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2", children: [
|
|
321
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
|
|
322
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
|
|
323
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
|
|
324
|
+
] })
|
|
325
|
+
] }),
|
|
326
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
|
|
327
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
|
|
329
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
|
|
331
|
+
] }),
|
|
332
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
|
|
333
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
|
|
334
|
+
] }),
|
|
335
|
+
i < 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
|
|
336
|
+
] }, i))
|
|
337
|
+
] });
|
|
338
|
+
}
|
|
339
|
+
function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
|
|
340
|
+
const todayMs = (0, import_react2.useMemo)(() => {
|
|
341
|
+
const d = /* @__PURE__ */ new Date();
|
|
342
|
+
d.setHours(0, 0, 0, 0);
|
|
343
|
+
return d.getTime();
|
|
344
|
+
}, []);
|
|
345
|
+
const scrollRef = (0, import_react2.useRef)(null);
|
|
346
|
+
const [canScrollLeft, setCanScrollLeft] = (0, import_react2.useState)(false);
|
|
347
|
+
const [canScrollRight, setCanScrollRight] = (0, import_react2.useState)(false);
|
|
348
|
+
const updateScrollState = () => {
|
|
349
|
+
const el = scrollRef.current;
|
|
350
|
+
if (!el) return;
|
|
351
|
+
setCanScrollLeft(el.scrollLeft > 0);
|
|
352
|
+
setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
353
|
+
};
|
|
354
|
+
(0, import_react2.useEffect)(() => {
|
|
355
|
+
const el = scrollRef.current;
|
|
356
|
+
if (!el) return;
|
|
357
|
+
updateScrollState();
|
|
358
|
+
el.addEventListener("scroll", updateScrollState);
|
|
359
|
+
const ro = new ResizeObserver(updateScrollState);
|
|
360
|
+
ro.observe(el);
|
|
361
|
+
return () => {
|
|
362
|
+
el.removeEventListener("scroll", updateScrollState);
|
|
363
|
+
ro.disconnect();
|
|
364
|
+
};
|
|
365
|
+
}, [dates]);
|
|
366
|
+
const scroll = (dir) => {
|
|
367
|
+
scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
|
|
368
|
+
};
|
|
369
|
+
const isDragging = (0, import_react2.useRef)(false);
|
|
370
|
+
const dragStartX = (0, import_react2.useRef)(0);
|
|
371
|
+
const dragStartScrollLeft = (0, import_react2.useRef)(0);
|
|
372
|
+
const onDragStart = (e) => {
|
|
373
|
+
if (!scrollRef.current) return;
|
|
374
|
+
isDragging.current = true;
|
|
375
|
+
dragStartX.current = e.clientX;
|
|
376
|
+
dragStartScrollLeft.current = scrollRef.current.scrollLeft;
|
|
377
|
+
scrollRef.current.style.cursor = "grabbing";
|
|
378
|
+
scrollRef.current.style.userSelect = "none";
|
|
379
|
+
};
|
|
380
|
+
const onDragMove = (e) => {
|
|
381
|
+
if (!isDragging.current || !scrollRef.current) return;
|
|
382
|
+
const delta = e.clientX - dragStartX.current;
|
|
383
|
+
scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
|
|
384
|
+
};
|
|
385
|
+
const onDragEnd = () => {
|
|
386
|
+
if (!scrollRef.current) return;
|
|
387
|
+
isDragging.current = false;
|
|
388
|
+
scrollRef.current.style.cursor = "grab";
|
|
389
|
+
scrollRef.current.style.userSelect = "";
|
|
390
|
+
};
|
|
391
|
+
const datesWithClasses = (0, import_react2.useMemo)(
|
|
392
|
+
() => new Set(dates.map(
|
|
393
|
+
(d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
|
|
394
|
+
).filter((i) => i >= 0)),
|
|
395
|
+
[dates, schedule]
|
|
396
|
+
);
|
|
397
|
+
const selectedDate = dates[selectedIdx];
|
|
398
|
+
const selectedClasses = (0, import_react2.useMemo)(
|
|
399
|
+
() => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
|
|
400
|
+
[schedule, selectedDate]
|
|
401
|
+
);
|
|
402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
|
|
403
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full", children: [
|
|
404
|
+
canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
405
|
+
"button",
|
|
406
|
+
{
|
|
407
|
+
onClick: () => scroll("left"),
|
|
408
|
+
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",
|
|
409
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
410
|
+
"aria-label": "Scroll left",
|
|
411
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "15 18 9 12 15 6" }) })
|
|
412
|
+
}
|
|
413
|
+
),
|
|
414
|
+
canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
415
|
+
"button",
|
|
416
|
+
{
|
|
417
|
+
onClick: () => scroll("right"),
|
|
418
|
+
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",
|
|
419
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
420
|
+
"aria-label": "Scroll right",
|
|
421
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "9 18 15 12 9 6" }) })
|
|
422
|
+
}
|
|
423
|
+
),
|
|
424
|
+
canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
425
|
+
"div",
|
|
426
|
+
{
|
|
427
|
+
className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
428
|
+
style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
|
|
429
|
+
}
|
|
430
|
+
),
|
|
431
|
+
canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
432
|
+
"div",
|
|
433
|
+
{
|
|
434
|
+
className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
435
|
+
style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
|
|
436
|
+
}
|
|
437
|
+
),
|
|
438
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
439
|
+
"div",
|
|
440
|
+
{
|
|
441
|
+
ref: scrollRef,
|
|
442
|
+
className: "overflow-x-auto w-full",
|
|
443
|
+
style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
|
|
444
|
+
onMouseDown: onDragStart,
|
|
445
|
+
onMouseMove: onDragMove,
|
|
446
|
+
onMouseUp: onDragEnd,
|
|
447
|
+
onMouseLeave: onDragEnd,
|
|
448
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
|
|
449
|
+
const isToday = date.getTime() === todayMs;
|
|
450
|
+
const isSelected = i === selectedIdx;
|
|
451
|
+
const clickable = datesWithClasses.has(i);
|
|
452
|
+
const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
|
|
453
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
454
|
+
"div",
|
|
455
|
+
{
|
|
456
|
+
onClick: () => clickable && onSelectDate(i),
|
|
457
|
+
className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
|
|
458
|
+
children: [
|
|
459
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
|
|
460
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
461
|
+
"div",
|
|
462
|
+
{
|
|
463
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center",
|
|
464
|
+
style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
|
|
465
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
466
|
+
"span",
|
|
467
|
+
{
|
|
468
|
+
className: "font-body text-xl font-bold text-center tracking-display-tight",
|
|
469
|
+
style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
|
|
470
|
+
children: date.getDate()
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
]
|
|
476
|
+
},
|
|
477
|
+
i
|
|
478
|
+
);
|
|
479
|
+
}) })
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
] }),
|
|
483
|
+
selectedClasses.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
|
|
484
|
+
const booked = getBookingsOnDate(cls, selectedDate);
|
|
485
|
+
const available = cls.maxParticipants - booked;
|
|
486
|
+
const isFull = available <= 0;
|
|
487
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
488
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
|
|
489
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
|
|
490
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
|
|
491
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
492
|
+
"span",
|
|
493
|
+
{
|
|
494
|
+
className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
|
|
495
|
+
style: { borderColor: "#0885FE", color: "#0885FE" },
|
|
496
|
+
children: "GROUP"
|
|
497
|
+
}
|
|
498
|
+
),
|
|
499
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
500
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
501
|
+
available,
|
|
502
|
+
"/",
|
|
503
|
+
cls.maxParticipants
|
|
504
|
+
] }),
|
|
505
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
506
|
+
] }) })
|
|
507
|
+
] }),
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
|
|
510
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
511
|
+
cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
|
|
512
|
+
] }),
|
|
513
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
514
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
515
|
+
available,
|
|
516
|
+
"/",
|
|
517
|
+
cls.maxParticipants
|
|
518
|
+
] }),
|
|
519
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
520
|
+
] }) }),
|
|
521
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
522
|
+
"button",
|
|
523
|
+
{
|
|
524
|
+
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",
|
|
525
|
+
style: isFull ? {
|
|
526
|
+
background: "transparent",
|
|
527
|
+
border: "1px solid var(--color-dark, #200C02)",
|
|
528
|
+
color: "var(--color-dark, #200C02)"
|
|
529
|
+
} : {
|
|
530
|
+
background: "var(--color-primary, #3D312B)",
|
|
531
|
+
border: "none",
|
|
532
|
+
color: "var(--color-light, #FEFFF0)"
|
|
533
|
+
},
|
|
534
|
+
onClick: () => {
|
|
535
|
+
if (!cls.id) return;
|
|
536
|
+
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
537
|
+
},
|
|
538
|
+
children: isFull ? "Join Waitlist" : "Book Now"
|
|
539
|
+
}
|
|
540
|
+
)
|
|
541
|
+
] })
|
|
542
|
+
] }),
|
|
543
|
+
i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
|
|
544
|
+
] }, cls.id ?? i);
|
|
545
|
+
}) })
|
|
546
|
+
] });
|
|
547
|
+
}
|
|
548
|
+
function CalendarFoldIcon() {
|
|
549
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
550
|
+
"svg",
|
|
551
|
+
{
|
|
552
|
+
width: "16",
|
|
553
|
+
height: "16",
|
|
554
|
+
viewBox: "0 0 24 24",
|
|
555
|
+
fill: "none",
|
|
556
|
+
stroke: "currentColor",
|
|
557
|
+
strokeWidth: "2",
|
|
558
|
+
strokeLinecap: "round",
|
|
559
|
+
strokeLinejoin: "round",
|
|
560
|
+
style: { flexShrink: 0 },
|
|
561
|
+
children: [
|
|
562
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2v4" }),
|
|
563
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 2v4" }),
|
|
564
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
|
|
565
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 10h18" }),
|
|
566
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
|
|
567
|
+
]
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
|
|
572
|
+
const [schedule, setSchedule] = (0, import_react2.useState)(null);
|
|
573
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
574
|
+
const [inEditor, setInEditor] = (0, import_react2.useState)(false);
|
|
575
|
+
const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
|
|
576
|
+
const [modalState, setModalState] = (0, import_react2.useState)(null);
|
|
577
|
+
const switchScheduleIdRef = (0, import_react2.useRef)(null);
|
|
578
|
+
const dates = (0, import_react2.useMemo)(() => {
|
|
579
|
+
const today = /* @__PURE__ */ new Date();
|
|
580
|
+
today.setHours(0, 0, 0, 0);
|
|
581
|
+
return Array.from({ length: 14 }, (_, i) => {
|
|
582
|
+
const d = new Date(today);
|
|
583
|
+
d.setDate(today.getDate() + i);
|
|
584
|
+
return d;
|
|
585
|
+
});
|
|
586
|
+
}, []);
|
|
587
|
+
const [selectedIdx, setSelectedIdx] = (0, import_react2.useState)(0);
|
|
588
|
+
(0, import_react2.useEffect)(() => {
|
|
589
|
+
if (!schedule?.classes) return;
|
|
590
|
+
for (let i = 0; i < dates.length; i++) {
|
|
591
|
+
if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
|
|
592
|
+
setSelectedIdx(i);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}, [schedule, dates]);
|
|
597
|
+
(0, import_react2.useEffect)(() => {
|
|
598
|
+
if (typeof window === "undefined") return;
|
|
599
|
+
const isInEditor = window.self !== window.top;
|
|
600
|
+
setInEditor(isInEditor);
|
|
601
|
+
if (isInEditor) {
|
|
602
|
+
const startEmpty = initialScheduleId === null;
|
|
603
|
+
if (startEmpty) setLoading(false);
|
|
604
|
+
let initialized = startEmpty;
|
|
605
|
+
const timer = !startEmpty ? setTimeout(() => {
|
|
606
|
+
if (!initialized) {
|
|
607
|
+
initialized = true;
|
|
608
|
+
setLoading(false);
|
|
609
|
+
}
|
|
610
|
+
}, 5e3) : null;
|
|
611
|
+
const handler = (e) => {
|
|
612
|
+
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
613
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
614
|
+
setSchedule(null);
|
|
615
|
+
setLoading(false);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (e.data?.type === "ow:switch-schedule") {
|
|
619
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
620
|
+
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
621
|
+
initialized = false;
|
|
622
|
+
setLoading(true);
|
|
623
|
+
window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
627
|
+
if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
|
|
628
|
+
if (initialized && switchScheduleIdRef.current === null) return;
|
|
629
|
+
initialized = true;
|
|
630
|
+
if (timer) clearTimeout(timer);
|
|
631
|
+
const schedules = e.data.schedules ?? [];
|
|
632
|
+
const isSwitching = switchScheduleIdRef.current !== null;
|
|
633
|
+
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;
|
|
634
|
+
switchScheduleIdRef.current = null;
|
|
635
|
+
setSchedule(target);
|
|
636
|
+
setLoading(false);
|
|
637
|
+
if (notifyOnConnect && target && !isSwitching) {
|
|
638
|
+
window.parent.postMessage({
|
|
639
|
+
type: "ow:schedule-connected",
|
|
640
|
+
schedule: { id: target.id, name: target.name },
|
|
641
|
+
insertAfter
|
|
642
|
+
}, "*");
|
|
643
|
+
window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
window.addEventListener("message", handler);
|
|
647
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
648
|
+
return () => {
|
|
649
|
+
window.removeEventListener("message", handler);
|
|
650
|
+
if (timer) clearTimeout(timer);
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
if (initialScheduleId === null) {
|
|
654
|
+
setLoading(false);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
;
|
|
658
|
+
(async () => {
|
|
659
|
+
try {
|
|
660
|
+
if (initialScheduleId) {
|
|
661
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
|
|
662
|
+
const data = await res.json();
|
|
663
|
+
setSchedule(data?.id ? data : null);
|
|
664
|
+
} else {
|
|
665
|
+
const domain = getApiDomain();
|
|
666
|
+
const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
|
|
667
|
+
const slugs = await sitemapRes.json();
|
|
668
|
+
if (!Array.isArray(slugs) || !slugs.length) {
|
|
669
|
+
setLoading(false);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const { slug } = [...slugs].sort(
|
|
673
|
+
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
674
|
+
)[0];
|
|
675
|
+
const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
|
|
676
|
+
const data = await res.json();
|
|
677
|
+
setSchedule(data?.id ? data : null);
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
setLoading(false);
|
|
682
|
+
})();
|
|
683
|
+
}, []);
|
|
684
|
+
const timezoneLabel = schedule?.timezone ? (() => {
|
|
685
|
+
try {
|
|
686
|
+
return buildTimezoneLabel(schedule.timezone);
|
|
687
|
+
} catch {
|
|
688
|
+
return void 0;
|
|
689
|
+
}
|
|
690
|
+
})() : void 0;
|
|
691
|
+
const handleModalSubmit = async (email) => {
|
|
692
|
+
if (!modalState) return;
|
|
693
|
+
const { mode, classId, classDate } = modalState;
|
|
694
|
+
const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
|
|
695
|
+
const res = await fetch(endpoint, {
|
|
696
|
+
method: "POST",
|
|
697
|
+
headers: { "Content-Type": "application/json" },
|
|
698
|
+
body: JSON.stringify({ classDate: classDate.toISOString(), email })
|
|
699
|
+
});
|
|
700
|
+
if (!res.ok) {
|
|
701
|
+
const data = await res.json().catch(() => ({}));
|
|
702
|
+
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
const handleReplaceSchedule = () => {
|
|
706
|
+
setIsHovered(false);
|
|
707
|
+
if (schedule) {
|
|
708
|
+
window.parent.postMessage({
|
|
709
|
+
type: "ow:schedule-connected",
|
|
710
|
+
schedule: { id: schedule.id, name: schedule.name },
|
|
711
|
+
insertAfter
|
|
712
|
+
}, "*");
|
|
713
|
+
} else {
|
|
714
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
const sectionId = `scheduling-${insertAfter}`;
|
|
718
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
719
|
+
"section",
|
|
720
|
+
{
|
|
721
|
+
"data-ohw-section": sectionId,
|
|
722
|
+
"data-ohw-scheduling-anchor": insertAfter,
|
|
723
|
+
className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
|
|
724
|
+
onMouseEnter: () => inEditor && setIsHovered(true),
|
|
725
|
+
onMouseLeave: () => setIsHovered(false),
|
|
726
|
+
children: [
|
|
727
|
+
inEditor && isHovered && !!schedule && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
728
|
+
"div",
|
|
729
|
+
{
|
|
730
|
+
className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
|
|
731
|
+
onClick: handleReplaceSchedule,
|
|
732
|
+
children: [
|
|
733
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
|
|
734
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
|
|
735
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
736
|
+
"div",
|
|
737
|
+
{
|
|
738
|
+
className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
|
|
739
|
+
style: {
|
|
740
|
+
fontSize: 14,
|
|
741
|
+
lineHeight: "24px",
|
|
742
|
+
fontFamily: "'Figtree', system-ui, sans-serif",
|
|
743
|
+
fontWeight: 500,
|
|
744
|
+
color: "#0C0A09"
|
|
745
|
+
},
|
|
746
|
+
children: [
|
|
747
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CalendarFoldIcon, {}),
|
|
748
|
+
"Replace schedule"
|
|
749
|
+
]
|
|
750
|
+
}
|
|
751
|
+
) })
|
|
752
|
+
]
|
|
753
|
+
}
|
|
754
|
+
),
|
|
755
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
756
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
|
|
757
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
|
|
758
|
+
schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
|
|
759
|
+
] }),
|
|
760
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
761
|
+
timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
762
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
763
|
+
ScheduleView,
|
|
764
|
+
{
|
|
765
|
+
schedule,
|
|
766
|
+
dates,
|
|
767
|
+
selectedIdx,
|
|
768
|
+
onSelectDate: setSelectedIdx,
|
|
769
|
+
onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
|
|
770
|
+
}
|
|
771
|
+
) })
|
|
772
|
+
] })
|
|
773
|
+
] }),
|
|
774
|
+
modalState && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
775
|
+
EmailCaptureModal,
|
|
776
|
+
{
|
|
777
|
+
title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
|
|
778
|
+
subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
|
|
779
|
+
onSubmit: handleModalSubmit,
|
|
780
|
+
onClose: () => setModalState(null)
|
|
781
|
+
}
|
|
782
|
+
)
|
|
783
|
+
]
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
}
|
|
44
787
|
|
|
45
788
|
// src/ui/toggle-group.tsx
|
|
46
|
-
var
|
|
789
|
+
var React2 = __toESM(require("react"), 1);
|
|
47
790
|
|
|
48
791
|
// node_modules/clsx/dist/clsx.mjs
|
|
49
792
|
function r(e) {
|
|
@@ -3362,8 +4105,8 @@ var cva = (base, config) => (props) => {
|
|
|
3362
4105
|
};
|
|
3363
4106
|
|
|
3364
4107
|
// src/ui/toggle.tsx
|
|
3365
|
-
var
|
|
3366
|
-
var
|
|
4108
|
+
var import_radix_ui2 = require("radix-ui");
|
|
4109
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3367
4110
|
var toggleVariants = cva(
|
|
3368
4111
|
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
3369
4112
|
{
|
|
@@ -3390,8 +4133,8 @@ function Toggle({
|
|
|
3390
4133
|
size,
|
|
3391
4134
|
...props
|
|
3392
4135
|
}) {
|
|
3393
|
-
return /* @__PURE__ */ (0,
|
|
3394
|
-
|
|
4136
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
4137
|
+
import_radix_ui2.Toggle.Root,
|
|
3395
4138
|
{
|
|
3396
4139
|
"data-slot": "toggle",
|
|
3397
4140
|
className: cn(toggleVariants({ variant, size, className })),
|
|
@@ -3401,8 +4144,8 @@ function Toggle({
|
|
|
3401
4144
|
}
|
|
3402
4145
|
|
|
3403
4146
|
// src/ui/toggle-group.tsx
|
|
3404
|
-
var
|
|
3405
|
-
var ToggleGroupContext =
|
|
4147
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
4148
|
+
var ToggleGroupContext = React2.createContext({
|
|
3406
4149
|
value: "",
|
|
3407
4150
|
onValueChange: () => {
|
|
3408
4151
|
}
|
|
@@ -3414,13 +4157,13 @@ function ToggleGroup({
|
|
|
3414
4157
|
children,
|
|
3415
4158
|
...props
|
|
3416
4159
|
}) {
|
|
3417
|
-
return /* @__PURE__ */ (0,
|
|
4160
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3418
4161
|
"div",
|
|
3419
4162
|
{
|
|
3420
4163
|
role: "group",
|
|
3421
4164
|
className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
|
|
3422
4165
|
...props,
|
|
3423
|
-
children: /* @__PURE__ */ (0,
|
|
4166
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
|
|
3424
4167
|
}
|
|
3425
4168
|
);
|
|
3426
4169
|
}
|
|
@@ -3430,12 +4173,14 @@ function ToggleGroupItem({
|
|
|
3430
4173
|
children,
|
|
3431
4174
|
...props
|
|
3432
4175
|
}) {
|
|
3433
|
-
const { value: groupValue, onValueChange } =
|
|
3434
|
-
return /* @__PURE__ */ (0,
|
|
4176
|
+
const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
|
|
4177
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3435
4178
|
Toggle,
|
|
3436
4179
|
{
|
|
3437
4180
|
pressed: groupValue === value,
|
|
3438
|
-
onPressedChange: () =>
|
|
4181
|
+
onPressedChange: (pressed) => {
|
|
4182
|
+
if (pressed) onValueChange(value);
|
|
4183
|
+
},
|
|
3439
4184
|
className: cn(
|
|
3440
4185
|
"text-foreground cursor-pointer hover:bg-transparent hover:text-foreground",
|
|
3441
4186
|
"data-[state=on]:bg-background data-[state=on]:shadow-sm",
|
|
@@ -3449,7 +4194,7 @@ function ToggleGroupItem({
|
|
|
3449
4194
|
}
|
|
3450
4195
|
|
|
3451
4196
|
// src/OhhwellsBridge.tsx
|
|
3452
|
-
var
|
|
4197
|
+
var import_react_dom2 = require("react-dom");
|
|
3453
4198
|
var import_navigation = require("next/navigation");
|
|
3454
4199
|
|
|
3455
4200
|
// src/lib/session-search.ts
|
|
@@ -3470,8 +4215,30 @@ function isEditSessionActive() {
|
|
|
3470
4215
|
return new URLSearchParams(q).get("mode") === "edit";
|
|
3471
4216
|
}
|
|
3472
4217
|
|
|
4218
|
+
// src/ui/badge.tsx
|
|
4219
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
4220
|
+
var badgeVariants = cva(
|
|
4221
|
+
"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",
|
|
4222
|
+
{
|
|
4223
|
+
variants: {
|
|
4224
|
+
variant: {
|
|
4225
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
4226
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
4227
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
4228
|
+
outline: "text-foreground"
|
|
4229
|
+
}
|
|
4230
|
+
},
|
|
4231
|
+
defaultVariants: {
|
|
4232
|
+
variant: "default"
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
);
|
|
4236
|
+
function Badge({ className, variant, ...props }) {
|
|
4237
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
4238
|
+
}
|
|
4239
|
+
|
|
3473
4240
|
// src/OhhwellsBridge.tsx
|
|
3474
|
-
var
|
|
4241
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
3475
4242
|
var PRIMARY = "#0885FE";
|
|
3476
4243
|
var IMAGE_FADE_MS = 300;
|
|
3477
4244
|
function runOpacityFade(el, onDone) {
|
|
@@ -3522,6 +4289,163 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
3522
4289
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
3523
4290
|
});
|
|
3524
4291
|
}
|
|
4292
|
+
function getSectionsTracker() {
|
|
4293
|
+
let el = document.querySelector("[data-ohw-sections-tracker]");
|
|
4294
|
+
if (!el) {
|
|
4295
|
+
el = document.createElement("div");
|
|
4296
|
+
el.setAttribute("data-ohw-sections-tracker", "");
|
|
4297
|
+
el.style.display = "none";
|
|
4298
|
+
document.body.appendChild(el);
|
|
4299
|
+
}
|
|
4300
|
+
return el;
|
|
4301
|
+
}
|
|
4302
|
+
function updateSectionScheduleId(insertAfter, scheduleId) {
|
|
4303
|
+
const tracker = getSectionsTracker();
|
|
4304
|
+
let sections = [];
|
|
4305
|
+
try {
|
|
4306
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
4307
|
+
} catch {
|
|
4308
|
+
}
|
|
4309
|
+
const currentPath = window.location.pathname;
|
|
4310
|
+
const updated = sections.map((s) => {
|
|
4311
|
+
if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
|
|
4312
|
+
if (s.pagePath && s.pagePath !== currentPath) return s;
|
|
4313
|
+
return { ...s, scheduleId };
|
|
4314
|
+
});
|
|
4315
|
+
tracker.textContent = JSON.stringify(updated);
|
|
4316
|
+
return tracker.textContent ?? "[]";
|
|
4317
|
+
}
|
|
4318
|
+
function schedulingSectionId(insertAfter) {
|
|
4319
|
+
return `scheduling-${insertAfter}`;
|
|
4320
|
+
}
|
|
4321
|
+
var INSERT_BEFORE_MARKER = ":before:";
|
|
4322
|
+
function parseSchedulingInsertAfter(insertAfter) {
|
|
4323
|
+
const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
|
|
4324
|
+
if (idx < 0) return { anchor: insertAfter, insertBefore: null };
|
|
4325
|
+
return {
|
|
4326
|
+
anchor: insertAfter.slice(0, idx),
|
|
4327
|
+
insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
|
|
4331
|
+
const parsed = parseSchedulingInsertAfter(insertAfter);
|
|
4332
|
+
const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
|
|
4333
|
+
const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
|
|
4334
|
+
return { effectiveInsertAfter, insertBefore };
|
|
4335
|
+
}
|
|
4336
|
+
function getSchedulingMountPoint(insertAfter) {
|
|
4337
|
+
const { anchor } = parseSchedulingInsertAfter(insertAfter);
|
|
4338
|
+
let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
|
|
4339
|
+
if (!anchorEl && anchor === "scheduling") {
|
|
4340
|
+
const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
|
|
4341
|
+
anchorEl = widgets.at(-1) ?? null;
|
|
4342
|
+
}
|
|
4343
|
+
if (!anchorEl) return null;
|
|
4344
|
+
return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
|
|
4345
|
+
}
|
|
4346
|
+
function schedulingMountDepth(insertAfter) {
|
|
4347
|
+
if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
|
|
4348
|
+
return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
|
|
4349
|
+
}
|
|
4350
|
+
function getPageSchedulingEntries(raw) {
|
|
4351
|
+
if (!raw) return [];
|
|
4352
|
+
try {
|
|
4353
|
+
const entries = JSON.parse(raw);
|
|
4354
|
+
const currentPath = window.location.pathname;
|
|
4355
|
+
return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
|
|
4356
|
+
} catch {
|
|
4357
|
+
return [];
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
function isSchedulingWidgetMissing(entry) {
|
|
4361
|
+
const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
|
|
4362
|
+
return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
|
|
4363
|
+
}
|
|
4364
|
+
function hasMissingSchedulingWidgets(entries) {
|
|
4365
|
+
return entries.some(isSchedulingWidgetMissing);
|
|
4366
|
+
}
|
|
4367
|
+
function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
|
|
4368
|
+
if (!hasMissingSchedulingWidgets(entries)) return;
|
|
4369
|
+
mountSchedulingEntries(entries, notifyOnConnect);
|
|
4370
|
+
}
|
|
4371
|
+
function initSectionsFromContent(content, removeExisting = false) {
|
|
4372
|
+
const raw = content["__ohw_sections"];
|
|
4373
|
+
if (!raw) return;
|
|
4374
|
+
try {
|
|
4375
|
+
if (removeExisting) {
|
|
4376
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
|
|
4377
|
+
}
|
|
4378
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
4379
|
+
if (inEditor) getSectionsTracker().textContent = raw;
|
|
4380
|
+
const pageEntries = getPageSchedulingEntries(raw);
|
|
4381
|
+
mountSchedulingEntries(pageEntries, false);
|
|
4382
|
+
requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
|
|
4383
|
+
setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
|
|
4384
|
+
} catch {
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
|
|
4388
|
+
const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
|
|
4389
|
+
const sectionId = schedulingSectionId(effectiveInsertAfter);
|
|
4390
|
+
if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
|
|
4391
|
+
const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
|
|
4392
|
+
if (!mountPoint) return false;
|
|
4393
|
+
const container = document.createElement("div");
|
|
4394
|
+
container.dataset.ohwSectionContainer = "scheduling";
|
|
4395
|
+
if (insertBefore) {
|
|
4396
|
+
const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
|
|
4397
|
+
const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
|
|
4398
|
+
if (!beforePoint) return false;
|
|
4399
|
+
beforePoint.insertAdjacentElement("beforebegin", container);
|
|
4400
|
+
} else {
|
|
4401
|
+
let tail = mountPoint;
|
|
4402
|
+
while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
|
|
4403
|
+
tail = tail.nextElementSibling;
|
|
4404
|
+
}
|
|
4405
|
+
tail.insertAdjacentElement("afterend", container);
|
|
4406
|
+
}
|
|
4407
|
+
const root = (0, import_client.createRoot)(container);
|
|
4408
|
+
(0, import_react_dom.flushSync)(() => {
|
|
4409
|
+
root.render(
|
|
4410
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4411
|
+
SchedulingWidget,
|
|
4412
|
+
{
|
|
4413
|
+
notifyOnConnect,
|
|
4414
|
+
initialScheduleId: scheduleId,
|
|
4415
|
+
insertAfter: effectiveInsertAfter
|
|
4416
|
+
}
|
|
4417
|
+
)
|
|
4418
|
+
);
|
|
4419
|
+
});
|
|
4420
|
+
const tracker = getSectionsTracker();
|
|
4421
|
+
let sections = [];
|
|
4422
|
+
try {
|
|
4423
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
4424
|
+
} catch {
|
|
4425
|
+
}
|
|
4426
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
4427
|
+
if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
|
|
4428
|
+
sections.push({
|
|
4429
|
+
type: "scheduling",
|
|
4430
|
+
insertAfter: effectiveInsertAfter,
|
|
4431
|
+
pagePath: window.location.pathname,
|
|
4432
|
+
...scheduleId ? { scheduleId } : {}
|
|
4433
|
+
});
|
|
4434
|
+
tracker.textContent = JSON.stringify(sections);
|
|
4435
|
+
}
|
|
4436
|
+
return true;
|
|
4437
|
+
}
|
|
4438
|
+
function mountSchedulingEntries(entries, notifyOnConnect = false) {
|
|
4439
|
+
const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
|
|
4440
|
+
for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
|
|
4441
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
4442
|
+
const { insertAfter, scheduleId } = pending[i];
|
|
4443
|
+
if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
|
|
4444
|
+
pending.splice(i, 1);
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
3525
4449
|
function collectEditableNodes() {
|
|
3526
4450
|
return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
3527
4451
|
if (el.dataset.ohwEditable === "image") {
|
|
@@ -3679,7 +4603,7 @@ var TOOLBAR_GROUPS = [
|
|
|
3679
4603
|
];
|
|
3680
4604
|
function GlowFrame({ rect, elRef }) {
|
|
3681
4605
|
const GAP = 6;
|
|
3682
|
-
return /* @__PURE__ */ (0,
|
|
4606
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3683
4607
|
"div",
|
|
3684
4608
|
{
|
|
3685
4609
|
ref: elRef,
|
|
@@ -3730,7 +4654,7 @@ function FloatingToolbar({
|
|
|
3730
4654
|
activeCommands
|
|
3731
4655
|
}) {
|
|
3732
4656
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
3733
|
-
return /* @__PURE__ */ (0,
|
|
4657
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3734
4658
|
"div",
|
|
3735
4659
|
{
|
|
3736
4660
|
ref: elRef,
|
|
@@ -3753,11 +4677,11 @@ function FloatingToolbar({
|
|
|
3753
4677
|
pointerEvents: "auto",
|
|
3754
4678
|
whiteSpace: "nowrap"
|
|
3755
4679
|
},
|
|
3756
|
-
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0,
|
|
3757
|
-
gi > 0 && /* @__PURE__ */ (0,
|
|
4680
|
+
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react3.default.Fragment, { children: [
|
|
4681
|
+
gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
|
|
3758
4682
|
btns.map((btn) => {
|
|
3759
4683
|
const isActive = activeCommands.has(btn.cmd);
|
|
3760
|
-
return /* @__PURE__ */ (0,
|
|
4684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3761
4685
|
"button",
|
|
3762
4686
|
{
|
|
3763
4687
|
title: btn.title,
|
|
@@ -3783,7 +4707,7 @@ function FloatingToolbar({
|
|
|
3783
4707
|
flexShrink: 0,
|
|
3784
4708
|
padding: 6
|
|
3785
4709
|
},
|
|
3786
|
-
children: /* @__PURE__ */ (0,
|
|
4710
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3787
4711
|
"svg",
|
|
3788
4712
|
{
|
|
3789
4713
|
width: "16",
|
|
@@ -3816,7 +4740,7 @@ function StateToggle({
|
|
|
3816
4740
|
states,
|
|
3817
4741
|
onStateChange
|
|
3818
4742
|
}) {
|
|
3819
|
-
return /* @__PURE__ */ (0,
|
|
4743
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3820
4744
|
ToggleGroup,
|
|
3821
4745
|
{
|
|
3822
4746
|
"data-ohw-state-toggle": "",
|
|
@@ -3830,30 +4754,46 @@ function StateToggle({
|
|
|
3830
4754
|
left: rect.right - 8,
|
|
3831
4755
|
transform: "translateX(-100%)"
|
|
3832
4756
|
},
|
|
3833
|
-
children: states.map((state) => /* @__PURE__ */ (0,
|
|
4757
|
+
children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
|
|
3834
4758
|
}
|
|
3835
4759
|
);
|
|
3836
4760
|
}
|
|
3837
4761
|
var contentCache = /* @__PURE__ */ new Map();
|
|
4762
|
+
function resolveSubdomain(subdomainFromQuery) {
|
|
4763
|
+
if (subdomainFromQuery) return subdomainFromQuery;
|
|
4764
|
+
if (typeof window !== "undefined") {
|
|
4765
|
+
const parts = window.location.hostname.split(".");
|
|
4766
|
+
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
4767
|
+
}
|
|
4768
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
|
|
4769
|
+
if (siteUrl) {
|
|
4770
|
+
try {
|
|
4771
|
+
const host = new URL(siteUrl).hostname;
|
|
4772
|
+
const siteParts = host.split(".");
|
|
4773
|
+
if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
|
|
4774
|
+
} catch {
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
return "";
|
|
4778
|
+
}
|
|
3838
4779
|
function OhhwellsBridge() {
|
|
3839
4780
|
const pathname = (0, import_navigation.usePathname)();
|
|
3840
4781
|
const router = (0, import_navigation.useRouter)();
|
|
3841
4782
|
const searchParams = (0, import_navigation.useSearchParams)();
|
|
3842
4783
|
const isEditMode = isEditSessionActive();
|
|
3843
|
-
(0,
|
|
3844
|
-
|
|
3845
|
-
const
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
(0, import_react.useEffect)(() => {
|
|
4784
|
+
const [bridgeRoot, setBridgeRoot] = (0, import_react3.useState)(null);
|
|
4785
|
+
(0, import_react3.useEffect)(() => {
|
|
4786
|
+
const figtreeFontId = "ohw-figtree-font";
|
|
4787
|
+
if (!document.getElementById(figtreeFontId)) {
|
|
4788
|
+
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
4789
|
+
const preconnect2 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" });
|
|
4790
|
+
const stylesheet = Object.assign(document.createElement("link"), {
|
|
4791
|
+
id: figtreeFontId,
|
|
4792
|
+
rel: "stylesheet",
|
|
4793
|
+
href: "https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap"
|
|
4794
|
+
});
|
|
4795
|
+
document.head.append(preconnect1, preconnect2, stylesheet);
|
|
4796
|
+
}
|
|
3857
4797
|
const el = document.createElement("div");
|
|
3858
4798
|
el.setAttribute("data-ohw-bridge-root", "");
|
|
3859
4799
|
el.setAttribute("data-ohw-bridge", "");
|
|
@@ -3865,39 +4805,40 @@ function OhhwellsBridge() {
|
|
|
3865
4805
|
};
|
|
3866
4806
|
}, []);
|
|
3867
4807
|
const subdomainFromQuery = searchParams.get("subdomain");
|
|
3868
|
-
const subdomain = subdomainFromQuery
|
|
3869
|
-
|
|
3870
|
-
const parts = window.location.hostname.split(".");
|
|
3871
|
-
return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
|
|
3872
|
-
})();
|
|
3873
|
-
const postToParent = (0, import_react.useCallback)((data) => {
|
|
4808
|
+
const subdomain = resolveSubdomain(subdomainFromQuery);
|
|
4809
|
+
const postToParent = (0, import_react3.useCallback)((data) => {
|
|
3874
4810
|
if (typeof window !== "undefined" && window.parent !== window) {
|
|
3875
4811
|
window.parent.postMessage(data, "*");
|
|
3876
4812
|
}
|
|
3877
4813
|
}, []);
|
|
3878
|
-
const [fetchState, setFetchState] = (0,
|
|
3879
|
-
const autoSaveTimers = (0,
|
|
3880
|
-
const activeElRef = (0,
|
|
3881
|
-
const originalContentRef = (0,
|
|
3882
|
-
const activeStateElRef = (0,
|
|
3883
|
-
const parentScrollRef = (0,
|
|
3884
|
-
const toolbarElRef = (0,
|
|
3885
|
-
const glowElRef = (0,
|
|
3886
|
-
const hoveredImageRef = (0,
|
|
3887
|
-
const
|
|
3888
|
-
const
|
|
4814
|
+
const [fetchState, setFetchState] = (0, import_react3.useState)("idle");
|
|
4815
|
+
const autoSaveTimers = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
|
4816
|
+
const activeElRef = (0, import_react3.useRef)(null);
|
|
4817
|
+
const originalContentRef = (0, import_react3.useRef)(null);
|
|
4818
|
+
const activeStateElRef = (0, import_react3.useRef)(null);
|
|
4819
|
+
const parentScrollRef = (0, import_react3.useRef)(null);
|
|
4820
|
+
const toolbarElRef = (0, import_react3.useRef)(null);
|
|
4821
|
+
const glowElRef = (0, import_react3.useRef)(null);
|
|
4822
|
+
const hoveredImageRef = (0, import_react3.useRef)(null);
|
|
4823
|
+
const hoveredImageHasTextOverlapRef = (0, import_react3.useRef)(false);
|
|
4824
|
+
const hoveredGapRef = (0, import_react3.useRef)(null);
|
|
4825
|
+
const imageUnhoverTimerRef = (0, import_react3.useRef)(null);
|
|
4826
|
+
const imageShowTimerRef = (0, import_react3.useRef)(null);
|
|
4827
|
+
const editStylesRef = (0, import_react3.useRef)(null);
|
|
4828
|
+
const activateRef = (0, import_react3.useRef)(() => {
|
|
3889
4829
|
});
|
|
3890
|
-
const deactivateRef = (0,
|
|
4830
|
+
const deactivateRef = (0, import_react3.useRef)(() => {
|
|
3891
4831
|
});
|
|
3892
|
-
const refreshActiveCommandsRef = (0,
|
|
4832
|
+
const refreshActiveCommandsRef = (0, import_react3.useRef)(() => {
|
|
3893
4833
|
});
|
|
3894
|
-
const postToParentRef = (0,
|
|
4834
|
+
const postToParentRef = (0, import_react3.useRef)(postToParent);
|
|
3895
4835
|
postToParentRef.current = postToParent;
|
|
3896
|
-
const [toolbarRect, setToolbarRect] = (0,
|
|
3897
|
-
const [toggleState, setToggleState] = (0,
|
|
3898
|
-
const [maxBadge, setMaxBadge] = (0,
|
|
3899
|
-
const [activeCommands, setActiveCommands] = (0,
|
|
3900
|
-
(0,
|
|
4836
|
+
const [toolbarRect, setToolbarRect] = (0, import_react3.useState)(null);
|
|
4837
|
+
const [toggleState, setToggleState] = (0, import_react3.useState)(null);
|
|
4838
|
+
const [maxBadge, setMaxBadge] = (0, import_react3.useState)(null);
|
|
4839
|
+
const [activeCommands, setActiveCommands] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
|
|
4840
|
+
const [sectionGap, setSectionGap] = (0, import_react3.useState)(null);
|
|
4841
|
+
(0, import_react3.useEffect)(() => {
|
|
3901
4842
|
const update = () => {
|
|
3902
4843
|
const el = activeElRef.current;
|
|
3903
4844
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -3905,6 +4846,12 @@ function OhhwellsBridge() {
|
|
|
3905
4846
|
if (!prev || !activeStateElRef.current) return prev;
|
|
3906
4847
|
return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
|
|
3907
4848
|
});
|
|
4849
|
+
setSectionGap((prev) => {
|
|
4850
|
+
if (!prev) return prev;
|
|
4851
|
+
const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
|
|
4852
|
+
if (!anchor) return prev;
|
|
4853
|
+
return { ...prev, y: anchor.getBoundingClientRect().bottom };
|
|
4854
|
+
});
|
|
3908
4855
|
};
|
|
3909
4856
|
const vvp = window.visualViewport;
|
|
3910
4857
|
if (!vvp) return;
|
|
@@ -3915,10 +4862,10 @@ function OhhwellsBridge() {
|
|
|
3915
4862
|
vvp.removeEventListener("resize", update);
|
|
3916
4863
|
};
|
|
3917
4864
|
}, []);
|
|
3918
|
-
const refreshStateRules = (0,
|
|
4865
|
+
const refreshStateRules = (0, import_react3.useCallback)(() => {
|
|
3919
4866
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
3920
4867
|
}, []);
|
|
3921
|
-
const deactivate = (0,
|
|
4868
|
+
const deactivate = (0, import_react3.useCallback)(() => {
|
|
3922
4869
|
const el = activeElRef.current;
|
|
3923
4870
|
if (!el) return;
|
|
3924
4871
|
const key = el.dataset.ohwKey;
|
|
@@ -3943,9 +4890,13 @@ function OhhwellsBridge() {
|
|
|
3943
4890
|
setActiveCommands(/* @__PURE__ */ new Set());
|
|
3944
4891
|
postToParent({ type: "ow:exit-edit" });
|
|
3945
4892
|
}, [postToParent]);
|
|
3946
|
-
const activate = (0,
|
|
4893
|
+
const activate = (0, import_react3.useCallback)((el) => {
|
|
3947
4894
|
if (activeElRef.current === el) return;
|
|
3948
4895
|
deactivate();
|
|
4896
|
+
if (hoveredImageRef.current) {
|
|
4897
|
+
hoveredImageRef.current = null;
|
|
4898
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4899
|
+
}
|
|
3949
4900
|
el.setAttribute("contenteditable", "true");
|
|
3950
4901
|
el.removeAttribute("data-ohw-hovered");
|
|
3951
4902
|
activeElRef.current = el;
|
|
@@ -3957,7 +4908,7 @@ function OhhwellsBridge() {
|
|
|
3957
4908
|
}, [deactivate, postToParent]);
|
|
3958
4909
|
activateRef.current = activate;
|
|
3959
4910
|
deactivateRef.current = deactivate;
|
|
3960
|
-
(0,
|
|
4911
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
3961
4912
|
if (!subdomain || isEditMode) {
|
|
3962
4913
|
setFetchState("done");
|
|
3963
4914
|
return;
|
|
@@ -3965,6 +4916,7 @@ function OhhwellsBridge() {
|
|
|
3965
4916
|
const applyContent = (content) => {
|
|
3966
4917
|
const imageLoads = [];
|
|
3967
4918
|
for (const [key, val] of Object.entries(content)) {
|
|
4919
|
+
if (key === "__ohw_sections") continue;
|
|
3968
4920
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3969
4921
|
if (el.dataset.ohwEditable === "image") {
|
|
3970
4922
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -3983,6 +4935,7 @@ function OhhwellsBridge() {
|
|
|
3983
4935
|
}
|
|
3984
4936
|
});
|
|
3985
4937
|
}
|
|
4938
|
+
initSectionsFromContent(content, true);
|
|
3986
4939
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
3987
4940
|
}) : Promise.resolve();
|
|
3988
4941
|
};
|
|
@@ -3993,7 +4946,7 @@ function OhhwellsBridge() {
|
|
|
3993
4946
|
}
|
|
3994
4947
|
let cancelled = false;
|
|
3995
4948
|
setFetchState("loading");
|
|
3996
|
-
const apiUrl =
|
|
4949
|
+
const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
3997
4950
|
fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
|
|
3998
4951
|
if (cancelled) return;
|
|
3999
4952
|
const content = data?.content ?? {};
|
|
@@ -4007,12 +4960,15 @@ function OhhwellsBridge() {
|
|
|
4007
4960
|
cancelled = true;
|
|
4008
4961
|
};
|
|
4009
4962
|
}, [subdomain, isEditMode, pathname]);
|
|
4010
|
-
(0,
|
|
4963
|
+
(0, import_react3.useEffect)(() => {
|
|
4011
4964
|
if (!subdomain || isEditMode) return;
|
|
4965
|
+
let debounceTimer = null;
|
|
4012
4966
|
const applyFromCache = () => {
|
|
4013
4967
|
const content = contentCache.get(subdomain);
|
|
4014
4968
|
if (!content) return;
|
|
4969
|
+
retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
|
|
4015
4970
|
for (const [key, val] of Object.entries(content)) {
|
|
4971
|
+
if (key === "__ohw_sections") continue;
|
|
4016
4972
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4017
4973
|
if (el.dataset.ohwEditable === "image") {
|
|
4018
4974
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4026,21 +4982,28 @@ function OhhwellsBridge() {
|
|
|
4026
4982
|
});
|
|
4027
4983
|
}
|
|
4028
4984
|
};
|
|
4029
|
-
|
|
4030
|
-
|
|
4985
|
+
const scheduleApply = () => {
|
|
4986
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4987
|
+
debounceTimer = setTimeout(applyFromCache, 150);
|
|
4988
|
+
};
|
|
4989
|
+
scheduleApply();
|
|
4990
|
+
const observer = new MutationObserver(scheduleApply);
|
|
4031
4991
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
4032
|
-
return () =>
|
|
4033
|
-
|
|
4034
|
-
|
|
4992
|
+
return () => {
|
|
4993
|
+
observer.disconnect();
|
|
4994
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4995
|
+
};
|
|
4996
|
+
}, [subdomain, isEditMode, pathname]);
|
|
4997
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
4035
4998
|
const el = document.getElementById("ohw-loader");
|
|
4036
4999
|
if (!el) return;
|
|
4037
5000
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
4038
5001
|
el.style.display = visible ? "flex" : "none";
|
|
4039
5002
|
}, [subdomain, fetchState]);
|
|
4040
|
-
(0,
|
|
5003
|
+
(0, import_react3.useEffect)(() => {
|
|
4041
5004
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
4042
5005
|
}, [pathname, postToParent]);
|
|
4043
|
-
(0,
|
|
5006
|
+
(0, import_react3.useEffect)(() => {
|
|
4044
5007
|
if (!isEditMode) return;
|
|
4045
5008
|
const measure = () => {
|
|
4046
5009
|
const h = document.body.scrollHeight;
|
|
@@ -4064,7 +5027,7 @@ function OhhwellsBridge() {
|
|
|
4064
5027
|
window.removeEventListener("resize", handleResize);
|
|
4065
5028
|
};
|
|
4066
5029
|
}, [pathname, isEditMode, postToParent]);
|
|
4067
|
-
(0,
|
|
5030
|
+
(0, import_react3.useEffect)(() => {
|
|
4068
5031
|
if (!subdomainFromQuery || isEditMode) return;
|
|
4069
5032
|
const handleClick = (e) => {
|
|
4070
5033
|
const anchor = e.target.closest("a");
|
|
@@ -4080,7 +5043,7 @@ function OhhwellsBridge() {
|
|
|
4080
5043
|
document.addEventListener("click", handleClick, true);
|
|
4081
5044
|
return () => document.removeEventListener("click", handleClick, true);
|
|
4082
5045
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
4083
|
-
(0,
|
|
5046
|
+
(0, import_react3.useEffect)(() => {
|
|
4084
5047
|
if (!isEditMode) {
|
|
4085
5048
|
editStylesRef.current?.base.remove();
|
|
4086
5049
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -4120,6 +5083,7 @@ function OhhwellsBridge() {
|
|
|
4120
5083
|
[data-ohw-editable][contenteditable] *::selection { background: ${PRIMARY}59 !important; }
|
|
4121
5084
|
[data-ohw-editable-state], [data-ohw-editable-state] * { pointer-events: none !important; }
|
|
4122
5085
|
[data-ohw-editable-state][data-ohw-active-state] [data-ohw-editable] { pointer-events: auto !important; }
|
|
5086
|
+
[data-ohw-editable-state][data-ohw-active-state][data-ohw-editable] { pointer-events: auto !important; }
|
|
4123
5087
|
`;
|
|
4124
5088
|
const forceHover = document.createElement("style");
|
|
4125
5089
|
forceHover.setAttribute("data-ohw-active-state-style", "");
|
|
@@ -4146,10 +5110,12 @@ function OhhwellsBridge() {
|
|
|
4146
5110
|
if (editable) {
|
|
4147
5111
|
if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
|
|
4148
5112
|
e.preventDefault();
|
|
5113
|
+
e.stopPropagation();
|
|
4149
5114
|
postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
|
|
4150
5115
|
return;
|
|
4151
5116
|
}
|
|
4152
5117
|
e.preventDefault();
|
|
5118
|
+
e.stopPropagation();
|
|
4153
5119
|
activateRef.current(editable);
|
|
4154
5120
|
return;
|
|
4155
5121
|
}
|
|
@@ -4212,12 +5178,15 @@ function OhhwellsBridge() {
|
|
|
4212
5178
|
}
|
|
4213
5179
|
return { top, left, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
|
|
4214
5180
|
};
|
|
4215
|
-
const postImageHover = (imgEl, isDragOver = false) => {
|
|
5181
|
+
const postImageHover = (imgEl, isDragOver = false, forceTextOverlap) => {
|
|
4216
5182
|
const r2 = getVisibleRect(imgEl);
|
|
5183
|
+
const hasTextOverlap = forceTextOverlap ?? false;
|
|
5184
|
+
hoveredImageHasTextOverlapRef.current = hasTextOverlap;
|
|
4217
5185
|
postToParentRef.current({
|
|
4218
5186
|
type: "ow:image-hover",
|
|
4219
5187
|
key: imgEl.dataset.ohwKey ?? "",
|
|
4220
5188
|
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
|
|
5189
|
+
hasTextOverlap,
|
|
4221
5190
|
...isDragOver ? { isDragOver: true } : {}
|
|
4222
5191
|
});
|
|
4223
5192
|
const track = imgEl.closest("[data-ohw-hover-pause]");
|
|
@@ -4264,7 +5233,7 @@ function OhhwellsBridge() {
|
|
|
4264
5233
|
}
|
|
4265
5234
|
return smallest;
|
|
4266
5235
|
};
|
|
4267
|
-
const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false
|
|
5236
|
+
const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
|
|
4268
5237
|
const toggleEl = document.querySelector("[data-ohw-state-toggle]");
|
|
4269
5238
|
if (toggleEl) {
|
|
4270
5239
|
const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
@@ -4290,33 +5259,61 @@ function OhhwellsBridge() {
|
|
|
4290
5259
|
}
|
|
4291
5260
|
return;
|
|
4292
5261
|
}
|
|
4293
|
-
const
|
|
4294
|
-
|
|
4295
|
-
|
|
5262
|
+
const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
|
|
5263
|
+
const textEditable = Array.from(
|
|
5264
|
+
document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
|
|
5265
|
+
).find((el) => {
|
|
5266
|
+
const er = el.getBoundingClientRect();
|
|
5267
|
+
return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
|
|
5268
|
+
});
|
|
5269
|
+
if (imageUnhoverTimerRef.current) {
|
|
5270
|
+
clearTimeout(imageUnhoverTimerRef.current);
|
|
5271
|
+
imageUnhoverTimerRef.current = null;
|
|
5272
|
+
}
|
|
5273
|
+
if (imageShowTimerRef.current) {
|
|
5274
|
+
clearTimeout(imageShowTimerRef.current);
|
|
5275
|
+
imageShowTimerRef.current = null;
|
|
5276
|
+
}
|
|
5277
|
+
if (textEditable) {
|
|
5278
|
+
if (textEditable.hasAttribute("contenteditable")) {
|
|
5279
|
+
if (hoveredImageRef.current) {
|
|
5280
|
+
hoveredImageRef.current = null;
|
|
5281
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
5282
|
+
resumeAnimTracks();
|
|
5283
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
5284
|
+
}
|
|
5285
|
+
return;
|
|
5286
|
+
}
|
|
5287
|
+
if (isStateCardImage) {
|
|
5288
|
+
if (imgEl !== hoveredImageRef.current || !hoveredImageHasTextOverlapRef.current) {
|
|
5289
|
+
hoveredImageRef.current = imgEl;
|
|
5290
|
+
postImageHover(imgEl, isDragOver, true);
|
|
5291
|
+
}
|
|
5292
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5293
|
+
textEditable.setAttribute("data-ohw-hovered", "");
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
4296
5296
|
if (hoveredImageRef.current) {
|
|
4297
5297
|
hoveredImageRef.current = null;
|
|
5298
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
4298
5299
|
resumeAnimTracks();
|
|
4299
5300
|
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4300
5301
|
}
|
|
4301
5302
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4302
|
-
|
|
5303
|
+
textEditable.setAttribute("data-ohw-hovered", "");
|
|
4303
5304
|
return;
|
|
4304
5305
|
}
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
if (hoveredImageRef.current) {
|
|
4311
|
-
hoveredImageRef.current = null;
|
|
4312
|
-
resumeAnimTracks();
|
|
4313
|
-
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4314
|
-
}
|
|
4315
|
-
return;
|
|
5306
|
+
if (hoveredGapRef.current) {
|
|
5307
|
+
if (hoveredImageRef.current) {
|
|
5308
|
+
hoveredImageRef.current = null;
|
|
5309
|
+
resumeAnimTracks();
|
|
5310
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
4316
5311
|
}
|
|
5312
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5313
|
+
return;
|
|
4317
5314
|
}
|
|
4318
5315
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4319
|
-
if (imgEl !== hoveredImageRef.current) {
|
|
5316
|
+
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
4320
5317
|
hoveredImageRef.current = imgEl;
|
|
4321
5318
|
postImageHover(imgEl, isDragOver);
|
|
4322
5319
|
} else if (isDragOver) {
|
|
@@ -4325,9 +5322,29 @@ function OhhwellsBridge() {
|
|
|
4325
5322
|
} else {
|
|
4326
5323
|
const inIframeView = clientX >= 0 && clientY >= 0 && clientX <= window.innerWidth && clientY <= window.innerHeight;
|
|
4327
5324
|
if (!inIframeView) return;
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
5325
|
+
if (imageUnhoverTimerRef.current) {
|
|
5326
|
+
clearTimeout(imageUnhoverTimerRef.current);
|
|
5327
|
+
imageUnhoverTimerRef.current = null;
|
|
5328
|
+
}
|
|
5329
|
+
if (imageShowTimerRef.current) {
|
|
5330
|
+
clearTimeout(imageShowTimerRef.current);
|
|
5331
|
+
imageShowTimerRef.current = null;
|
|
5332
|
+
}
|
|
5333
|
+
if (hoveredImageRef.current) {
|
|
5334
|
+
hoveredImageRef.current = null;
|
|
5335
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
5336
|
+
resumeAnimTracks();
|
|
5337
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
5338
|
+
}
|
|
5339
|
+
const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
5340
|
+
const textEl = Array.from(
|
|
5341
|
+
document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
|
|
5342
|
+
).find((el) => {
|
|
5343
|
+
const er = el.getBoundingClientRect();
|
|
5344
|
+
return x >= er.left && x <= er.right && y >= er.top && y <= er.bottom;
|
|
5345
|
+
});
|
|
5346
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5347
|
+
if (textEl && !textEl.hasAttribute("contenteditable")) textEl.setAttribute("data-ohw-hovered", "");
|
|
4331
5348
|
}
|
|
4332
5349
|
};
|
|
4333
5350
|
const probeHoverCardsAt = (clientX, clientY, fromParentViewport = false) => {
|
|
@@ -4346,27 +5363,53 @@ function OhhwellsBridge() {
|
|
|
4346
5363
|
}) ?? null;
|
|
4347
5364
|
const currentLocked = activeStateElRef.current?.hasAttribute("data-ohw-active-state") ? activeStateElRef.current : null;
|
|
4348
5365
|
const active = found ?? currentLocked;
|
|
5366
|
+
const prev = activeStateElRef.current;
|
|
4349
5367
|
activeStateElRef.current = active;
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
if (active
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
5368
|
+
if (active !== prev) {
|
|
5369
|
+
if (prev) prev.removeAttribute("data-ohw-state-hovered");
|
|
5370
|
+
if (active) {
|
|
5371
|
+
if (active.querySelector("[data-ohw-state-view]")) active.setAttribute("data-ohw-state-hovered", "");
|
|
5372
|
+
const states = deriveStates(active.dataset.ohwEditableState ?? "");
|
|
5373
|
+
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";
|
|
5374
|
+
setToggleState({ rect: active.getBoundingClientRect(), activeState, states });
|
|
5375
|
+
} else {
|
|
5376
|
+
setToggleState(null);
|
|
5377
|
+
}
|
|
5378
|
+
}
|
|
5379
|
+
};
|
|
5380
|
+
const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
|
|
5381
|
+
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
5382
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
5383
|
+
const ZONE = 20;
|
|
5384
|
+
for (let i = 0; i < sections.length; i++) {
|
|
5385
|
+
const a = sections[i];
|
|
5386
|
+
const b = sections[i + 1] ?? null;
|
|
5387
|
+
const boundaryY = a.getBoundingClientRect().bottom;
|
|
5388
|
+
if (Math.abs(y - boundaryY) <= ZONE) {
|
|
5389
|
+
const insertAfter = a.dataset.ohwSection ?? "";
|
|
5390
|
+
const insertBefore = b?.dataset.ohwSection ?? null;
|
|
5391
|
+
hoveredGapRef.current = { insertAfter, insertBefore };
|
|
5392
|
+
setSectionGap({ insertAfter, insertBefore, y: boundaryY });
|
|
5393
|
+
return;
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
if (hoveredGapRef.current) {
|
|
5397
|
+
hoveredGapRef.current = null;
|
|
5398
|
+
setSectionGap(null);
|
|
4358
5399
|
}
|
|
4359
5400
|
};
|
|
4360
5401
|
const handleMouseMove = (e) => {
|
|
4361
5402
|
const { clientX, clientY } = e;
|
|
5403
|
+
probeSectionGapAt(clientX, clientY);
|
|
4362
5404
|
probeImageAt(clientX, clientY);
|
|
4363
5405
|
probeHoverCardsAt(clientX, clientY);
|
|
4364
5406
|
};
|
|
4365
5407
|
const handlePointerSync = (e) => {
|
|
4366
5408
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
4367
|
-
const { clientX, clientY
|
|
5409
|
+
const { clientX, clientY } = e.data;
|
|
4368
5410
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
4369
|
-
|
|
5411
|
+
probeSectionGapAt(clientX, clientY);
|
|
5412
|
+
probeImageAt(clientX, clientY);
|
|
4370
5413
|
probeHoverCardsAt(clientX, clientY);
|
|
4371
5414
|
};
|
|
4372
5415
|
const handleDragOver = (e) => {
|
|
@@ -4379,7 +5422,8 @@ function OhhwellsBridge() {
|
|
|
4379
5422
|
e.dataTransfer.dropEffect = "copy";
|
|
4380
5423
|
if (hoveredImageRef.current !== el) {
|
|
4381
5424
|
hoveredImageRef.current = el;
|
|
4382
|
-
|
|
5425
|
+
const isStateCard = !!el.closest("[data-ohw-editable-state]");
|
|
5426
|
+
postImageHover(el, true, isStateCard ? true : void 0);
|
|
4383
5427
|
}
|
|
4384
5428
|
};
|
|
4385
5429
|
const handleDragLeave = (e) => {
|
|
@@ -4522,7 +5566,12 @@ function OhhwellsBridge() {
|
|
|
4522
5566
|
if (e.data?.type !== "ow:hydrate") return;
|
|
4523
5567
|
const content = e.data.content;
|
|
4524
5568
|
if (!content) return;
|
|
5569
|
+
let sectionsJson = null;
|
|
4525
5570
|
for (const [key, val] of Object.entries(content)) {
|
|
5571
|
+
if (key === "__ohw_sections") {
|
|
5572
|
+
sectionsJson = val;
|
|
5573
|
+
continue;
|
|
5574
|
+
}
|
|
4526
5575
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4527
5576
|
if (el.dataset.ohwEditable === "image") {
|
|
4528
5577
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4534,6 +5583,9 @@ function OhhwellsBridge() {
|
|
|
4534
5583
|
}
|
|
4535
5584
|
});
|
|
4536
5585
|
}
|
|
5586
|
+
if (sectionsJson) {
|
|
5587
|
+
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
5588
|
+
}
|
|
4537
5589
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
4538
5590
|
};
|
|
4539
5591
|
window.addEventListener("message", handleHydrate);
|
|
@@ -4565,17 +5617,75 @@ function OhhwellsBridge() {
|
|
|
4565
5617
|
setToggleState((prev) => prev ? { ...prev, rect } : null);
|
|
4566
5618
|
}
|
|
4567
5619
|
if (hoveredImageRef.current) {
|
|
4568
|
-
const
|
|
5620
|
+
const el = hoveredImageRef.current;
|
|
5621
|
+
const r2 = el.getBoundingClientRect();
|
|
4569
5622
|
postToParentRef.current({
|
|
4570
5623
|
type: "ow:image-hover",
|
|
4571
|
-
key:
|
|
4572
|
-
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height }
|
|
5624
|
+
key: el.dataset.ohwKey ?? "",
|
|
5625
|
+
rect: { top: r2.top, left: r2.left, width: r2.width, height: r2.height },
|
|
5626
|
+
hasTextOverlap: hoveredImageHasTextOverlapRef.current
|
|
4573
5627
|
});
|
|
4574
5628
|
}
|
|
4575
5629
|
};
|
|
4576
5630
|
const handleSave = (e) => {
|
|
4577
5631
|
if (e.data?.type !== "ow:save") return;
|
|
4578
|
-
|
|
5632
|
+
const nodes = collectEditableNodes();
|
|
5633
|
+
const tracker = document.querySelector("[data-ohw-sections-tracker]");
|
|
5634
|
+
if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
|
|
5635
|
+
postToParentRef.current({ type: "ow:save-result", nodes });
|
|
5636
|
+
};
|
|
5637
|
+
const handleInsertSection = (e) => {
|
|
5638
|
+
if (e.data?.type !== "ow:insert-section") return;
|
|
5639
|
+
const { widgetType, insertAfter, insertBefore } = e.data;
|
|
5640
|
+
if (widgetType !== "scheduling") return;
|
|
5641
|
+
const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
|
|
5642
|
+
if (inserted) {
|
|
5643
|
+
const tracker = getSectionsTracker();
|
|
5644
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
|
|
5645
|
+
const h = document.documentElement.scrollHeight;
|
|
5646
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
5647
|
+
}
|
|
5648
|
+
};
|
|
5649
|
+
const handleSwitchSchedule = (e) => {
|
|
5650
|
+
if (e.data?.type !== "ow:switch-schedule") return;
|
|
5651
|
+
const scheduleId = e.data.scheduleId;
|
|
5652
|
+
const insertAfter = e.data.insertAfter;
|
|
5653
|
+
if (!scheduleId || !insertAfter) return;
|
|
5654
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
5655
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
5656
|
+
};
|
|
5657
|
+
const handleScheduleLinked = (e) => {
|
|
5658
|
+
if (e.data?.type !== "ow:schedule-linked") return;
|
|
5659
|
+
const scheduleId = e.data.scheduleId;
|
|
5660
|
+
const insertAfter = e.data.insertAfter;
|
|
5661
|
+
if (!scheduleId || !insertAfter) return;
|
|
5662
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
5663
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
5664
|
+
};
|
|
5665
|
+
const handleClearSchedulingWidget = (e) => {
|
|
5666
|
+
if (e.data?.type !== "ow:clear-scheduling-widget") return;
|
|
5667
|
+
const insertAfter = e.data.insertAfter;
|
|
5668
|
+
if (!insertAfter) return;
|
|
5669
|
+
const text = updateSectionScheduleId(insertAfter, null);
|
|
5670
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
5671
|
+
};
|
|
5672
|
+
const handleRemoveSchedulingSection = (e) => {
|
|
5673
|
+
if (e.data?.type !== "ow:remove-scheduling-section") return;
|
|
5674
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
|
|
5675
|
+
el.remove();
|
|
5676
|
+
});
|
|
5677
|
+
const tracker = getSectionsTracker();
|
|
5678
|
+
let sections = [];
|
|
5679
|
+
try {
|
|
5680
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5681
|
+
} catch {
|
|
5682
|
+
}
|
|
5683
|
+
const currentPath = window.location.pathname;
|
|
5684
|
+
const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
|
|
5685
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5686
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
5687
|
+
const h = document.documentElement.scrollHeight;
|
|
5688
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
4579
5689
|
};
|
|
4580
5690
|
const handleSelectionChange = () => {
|
|
4581
5691
|
if (!activeElRef.current) return;
|
|
@@ -4643,12 +5753,54 @@ function OhhwellsBridge() {
|
|
|
4643
5753
|
parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
|
|
4644
5754
|
if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
|
|
4645
5755
|
};
|
|
5756
|
+
const handleClickAt = (e) => {
|
|
5757
|
+
if (e.data?.type !== "ow:click-at") return;
|
|
5758
|
+
const { clientX, clientY } = e.data;
|
|
5759
|
+
const allImages = Array.from(
|
|
5760
|
+
document.querySelectorAll('[data-ohw-editable="image"], [data-ohw-editable="bg-image"]')
|
|
5761
|
+
).filter((el) => !!el.closest("[data-ohw-editable-state]"));
|
|
5762
|
+
const stateCardImage = allImages.find((el) => {
|
|
5763
|
+
const ownerCard = el.closest("[data-ohw-editable-state]");
|
|
5764
|
+
if (ownerCard) {
|
|
5765
|
+
const inHoverState = ownerCard.getAttribute("data-ohw-active-state") === "hover";
|
|
5766
|
+
const elState = el.dataset.ohwState;
|
|
5767
|
+
if (el === ownerCard && inHoverState) return false;
|
|
5768
|
+
if (elState === "default" && inHoverState) return false;
|
|
5769
|
+
if (elState === "hover" && !inHoverState) return false;
|
|
5770
|
+
}
|
|
5771
|
+
const r2 = el.getBoundingClientRect();
|
|
5772
|
+
return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
|
|
5773
|
+
});
|
|
5774
|
+
if (stateCardImage) {
|
|
5775
|
+
postToParentRef.current({ type: "ow:image-pick", key: stateCardImage.dataset.ohwKey ?? "" });
|
|
5776
|
+
return;
|
|
5777
|
+
}
|
|
5778
|
+
const textEditable = Array.from(
|
|
5779
|
+
document.querySelectorAll(
|
|
5780
|
+
'[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])'
|
|
5781
|
+
)
|
|
5782
|
+
).find((el) => {
|
|
5783
|
+
const r2 = el.getBoundingClientRect();
|
|
5784
|
+
return clientX >= r2.left && clientX <= r2.right && clientY >= r2.top && clientY <= r2.bottom;
|
|
5785
|
+
});
|
|
5786
|
+
if (textEditable) {
|
|
5787
|
+
activateRef.current(textEditable);
|
|
5788
|
+
return;
|
|
5789
|
+
}
|
|
5790
|
+
deactivateRef.current();
|
|
5791
|
+
};
|
|
4646
5792
|
window.addEventListener("message", handleSave);
|
|
5793
|
+
window.addEventListener("message", handleInsertSection);
|
|
5794
|
+
window.addEventListener("message", handleSwitchSchedule);
|
|
5795
|
+
window.addEventListener("message", handleScheduleLinked);
|
|
5796
|
+
window.addEventListener("message", handleClearSchedulingWidget);
|
|
5797
|
+
window.addEventListener("message", handleRemoveSchedulingSection);
|
|
4647
5798
|
window.addEventListener("message", handleImageUrl);
|
|
4648
5799
|
window.addEventListener("message", handleAnimLock);
|
|
4649
5800
|
window.addEventListener("message", handleCanvasHeight);
|
|
4650
5801
|
window.addEventListener("message", handleParentScroll);
|
|
4651
5802
|
window.addEventListener("message", handlePointerSync);
|
|
5803
|
+
window.addEventListener("message", handleClickAt);
|
|
4652
5804
|
document.addEventListener("click", handleClick, true);
|
|
4653
5805
|
document.addEventListener("paste", handlePaste, true);
|
|
4654
5806
|
document.addEventListener("input", handleInput, true);
|
|
@@ -4677,18 +5829,26 @@ function OhhwellsBridge() {
|
|
|
4677
5829
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
4678
5830
|
window.removeEventListener("scroll", handleScroll, true);
|
|
4679
5831
|
window.removeEventListener("message", handleSave);
|
|
5832
|
+
window.removeEventListener("message", handleInsertSection);
|
|
5833
|
+
window.removeEventListener("message", handleSwitchSchedule);
|
|
5834
|
+
window.removeEventListener("message", handleScheduleLinked);
|
|
5835
|
+
window.removeEventListener("message", handleClearSchedulingWidget);
|
|
5836
|
+
window.removeEventListener("message", handleRemoveSchedulingSection);
|
|
4680
5837
|
window.removeEventListener("message", handleImageUrl);
|
|
4681
5838
|
window.removeEventListener("message", handleAnimLock);
|
|
4682
5839
|
window.removeEventListener("message", handleCanvasHeight);
|
|
4683
5840
|
window.removeEventListener("message", handleParentScroll);
|
|
4684
5841
|
window.removeEventListener("message", handlePointerSync);
|
|
5842
|
+
window.removeEventListener("message", handleClickAt);
|
|
4685
5843
|
window.removeEventListener("message", handleHydrate);
|
|
4686
5844
|
window.removeEventListener("message", handleDeactivate);
|
|
4687
5845
|
autoSaveTimers.current.forEach(clearTimeout);
|
|
4688
5846
|
autoSaveTimers.current.clear();
|
|
5847
|
+
if (imageUnhoverTimerRef.current) clearTimeout(imageUnhoverTimerRef.current);
|
|
5848
|
+
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
4689
5849
|
};
|
|
4690
5850
|
}, [isEditMode, refreshStateRules]);
|
|
4691
|
-
(0,
|
|
5851
|
+
(0, import_react3.useEffect)(() => {
|
|
4692
5852
|
if (!isEditMode) return;
|
|
4693
5853
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
4694
5854
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -4710,13 +5870,13 @@ function OhhwellsBridge() {
|
|
|
4710
5870
|
clearTimeout(timer);
|
|
4711
5871
|
};
|
|
4712
5872
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
4713
|
-
const handleCommand = (0,
|
|
5873
|
+
const handleCommand = (0, import_react3.useCallback)((cmd) => {
|
|
4714
5874
|
document.execCommand(cmd, false);
|
|
4715
5875
|
activeElRef.current?.focus();
|
|
4716
5876
|
if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
|
|
4717
5877
|
refreshActiveCommandsRef.current();
|
|
4718
5878
|
}, []);
|
|
4719
|
-
const handleStateChange = (0,
|
|
5879
|
+
const handleStateChange = (0, import_react3.useCallback)((state) => {
|
|
4720
5880
|
if (!activeStateElRef.current) return;
|
|
4721
5881
|
const el = activeStateElRef.current;
|
|
4722
5882
|
if (state === "Default") {
|
|
@@ -4729,13 +5889,13 @@ function OhhwellsBridge() {
|
|
|
4729
5889
|
}
|
|
4730
5890
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
4731
5891
|
}, [deactivate]);
|
|
4732
|
-
return bridgeRoot ? (0,
|
|
4733
|
-
/* @__PURE__ */ (0,
|
|
4734
|
-
toolbarRect && /* @__PURE__ */ (0,
|
|
4735
|
-
/* @__PURE__ */ (0,
|
|
4736
|
-
/* @__PURE__ */ (0,
|
|
5892
|
+
return bridgeRoot ? (0, import_react_dom2.createPortal)(
|
|
5893
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
5894
|
+
toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
5895
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
5896
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
|
|
4737
5897
|
] }),
|
|
4738
|
-
maxBadge && /* @__PURE__ */ (0,
|
|
5898
|
+
maxBadge && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
4739
5899
|
"div",
|
|
4740
5900
|
{
|
|
4741
5901
|
"data-ohw-max-badge": "",
|
|
@@ -4761,7 +5921,7 @@ function OhhwellsBridge() {
|
|
|
4761
5921
|
]
|
|
4762
5922
|
}
|
|
4763
5923
|
),
|
|
4764
|
-
toggleState && /* @__PURE__ */ (0,
|
|
5924
|
+
toggleState && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4765
5925
|
StateToggle,
|
|
4766
5926
|
{
|
|
4767
5927
|
rect: toggleState.rect,
|
|
@@ -4769,6 +5929,31 @@ function OhhwellsBridge() {
|
|
|
4769
5929
|
states: toggleState.states,
|
|
4770
5930
|
onStateChange: handleStateChange
|
|
4771
5931
|
}
|
|
5932
|
+
),
|
|
5933
|
+
sectionGap && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
5934
|
+
"div",
|
|
5935
|
+
{
|
|
5936
|
+
"data-ohw-section-insert-line": "",
|
|
5937
|
+
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
5938
|
+
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
5939
|
+
children: [
|
|
5940
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
5941
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
5942
|
+
Badge,
|
|
5943
|
+
{
|
|
5944
|
+
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",
|
|
5945
|
+
onClick: () => {
|
|
5946
|
+
window.parent.postMessage(
|
|
5947
|
+
{ type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
|
|
5948
|
+
"*"
|
|
5949
|
+
);
|
|
5950
|
+
},
|
|
5951
|
+
children: "Add Section"
|
|
5952
|
+
}
|
|
5953
|
+
),
|
|
5954
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
5955
|
+
]
|
|
5956
|
+
}
|
|
4772
5957
|
)
|
|
4773
5958
|
] }),
|
|
4774
5959
|
bridgeRoot
|
|
@@ -4777,6 +5962,7 @@ function OhhwellsBridge() {
|
|
|
4777
5962
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4778
5963
|
0 && (module.exports = {
|
|
4779
5964
|
OhhwellsBridge,
|
|
5965
|
+
SchedulingWidget,
|
|
4780
5966
|
Toggle,
|
|
4781
5967
|
ToggleGroup,
|
|
4782
5968
|
ToggleGroupItem,
|