@ohhwells/bridge 0.1.15 → 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 +1165 -78
- 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 +1152 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +504 -54
- 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,8 +4173,8 @@ 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,
|
|
@@ -3451,7 +4194,7 @@ function ToggleGroupItem({
|
|
|
3451
4194
|
}
|
|
3452
4195
|
|
|
3453
4196
|
// src/OhhwellsBridge.tsx
|
|
3454
|
-
var
|
|
4197
|
+
var import_react_dom2 = require("react-dom");
|
|
3455
4198
|
var import_navigation = require("next/navigation");
|
|
3456
4199
|
|
|
3457
4200
|
// src/lib/session-search.ts
|
|
@@ -3472,8 +4215,30 @@ function isEditSessionActive() {
|
|
|
3472
4215
|
return new URLSearchParams(q).get("mode") === "edit";
|
|
3473
4216
|
}
|
|
3474
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
|
+
|
|
3475
4240
|
// src/OhhwellsBridge.tsx
|
|
3476
|
-
var
|
|
4241
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
3477
4242
|
var PRIMARY = "#0885FE";
|
|
3478
4243
|
var IMAGE_FADE_MS = 300;
|
|
3479
4244
|
function runOpacityFade(el, onDone) {
|
|
@@ -3524,6 +4289,163 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
3524
4289
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
3525
4290
|
});
|
|
3526
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
|
+
}
|
|
3527
4449
|
function collectEditableNodes() {
|
|
3528
4450
|
return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
3529
4451
|
if (el.dataset.ohwEditable === "image") {
|
|
@@ -3681,7 +4603,7 @@ var TOOLBAR_GROUPS = [
|
|
|
3681
4603
|
];
|
|
3682
4604
|
function GlowFrame({ rect, elRef }) {
|
|
3683
4605
|
const GAP = 6;
|
|
3684
|
-
return /* @__PURE__ */ (0,
|
|
4606
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3685
4607
|
"div",
|
|
3686
4608
|
{
|
|
3687
4609
|
ref: elRef,
|
|
@@ -3732,7 +4654,7 @@ function FloatingToolbar({
|
|
|
3732
4654
|
activeCommands
|
|
3733
4655
|
}) {
|
|
3734
4656
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
3735
|
-
return /* @__PURE__ */ (0,
|
|
4657
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3736
4658
|
"div",
|
|
3737
4659
|
{
|
|
3738
4660
|
ref: elRef,
|
|
@@ -3755,11 +4677,11 @@ function FloatingToolbar({
|
|
|
3755
4677
|
pointerEvents: "auto",
|
|
3756
4678
|
whiteSpace: "nowrap"
|
|
3757
4679
|
},
|
|
3758
|
-
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0,
|
|
3759
|
-
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 } }),
|
|
3760
4682
|
btns.map((btn) => {
|
|
3761
4683
|
const isActive = activeCommands.has(btn.cmd);
|
|
3762
|
-
return /* @__PURE__ */ (0,
|
|
4684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3763
4685
|
"button",
|
|
3764
4686
|
{
|
|
3765
4687
|
title: btn.title,
|
|
@@ -3785,7 +4707,7 @@ function FloatingToolbar({
|
|
|
3785
4707
|
flexShrink: 0,
|
|
3786
4708
|
padding: 6
|
|
3787
4709
|
},
|
|
3788
|
-
children: /* @__PURE__ */ (0,
|
|
4710
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3789
4711
|
"svg",
|
|
3790
4712
|
{
|
|
3791
4713
|
width: "16",
|
|
@@ -3818,7 +4740,7 @@ function StateToggle({
|
|
|
3818
4740
|
states,
|
|
3819
4741
|
onStateChange
|
|
3820
4742
|
}) {
|
|
3821
|
-
return /* @__PURE__ */ (0,
|
|
4743
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3822
4744
|
ToggleGroup,
|
|
3823
4745
|
{
|
|
3824
4746
|
"data-ohw-state-toggle": "",
|
|
@@ -3832,18 +4754,35 @@ function StateToggle({
|
|
|
3832
4754
|
left: rect.right - 8,
|
|
3833
4755
|
transform: "translateX(-100%)"
|
|
3834
4756
|
},
|
|
3835
|
-
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))
|
|
3836
4758
|
}
|
|
3837
4759
|
);
|
|
3838
4760
|
}
|
|
3839
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
|
+
}
|
|
3840
4779
|
function OhhwellsBridge() {
|
|
3841
4780
|
const pathname = (0, import_navigation.usePathname)();
|
|
3842
4781
|
const router = (0, import_navigation.useRouter)();
|
|
3843
4782
|
const searchParams = (0, import_navigation.useSearchParams)();
|
|
3844
4783
|
const isEditMode = isEditSessionActive();
|
|
3845
|
-
const [bridgeRoot, setBridgeRoot] = (0,
|
|
3846
|
-
(0,
|
|
4784
|
+
const [bridgeRoot, setBridgeRoot] = (0, import_react3.useState)(null);
|
|
4785
|
+
(0, import_react3.useEffect)(() => {
|
|
3847
4786
|
const figtreeFontId = "ohw-figtree-font";
|
|
3848
4787
|
if (!document.getElementById(figtreeFontId)) {
|
|
3849
4788
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -3866,42 +4805,40 @@ function OhhwellsBridge() {
|
|
|
3866
4805
|
};
|
|
3867
4806
|
}, []);
|
|
3868
4807
|
const subdomainFromQuery = searchParams.get("subdomain");
|
|
3869
|
-
const subdomain = subdomainFromQuery
|
|
3870
|
-
|
|
3871
|
-
const parts = window.location.hostname.split(".");
|
|
3872
|
-
return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
|
|
3873
|
-
})();
|
|
3874
|
-
const postToParent = (0, import_react.useCallback)((data) => {
|
|
4808
|
+
const subdomain = resolveSubdomain(subdomainFromQuery);
|
|
4809
|
+
const postToParent = (0, import_react3.useCallback)((data) => {
|
|
3875
4810
|
if (typeof window !== "undefined" && window.parent !== window) {
|
|
3876
4811
|
window.parent.postMessage(data, "*");
|
|
3877
4812
|
}
|
|
3878
4813
|
}, []);
|
|
3879
|
-
const [fetchState, setFetchState] = (0,
|
|
3880
|
-
const autoSaveTimers = (0,
|
|
3881
|
-
const activeElRef = (0,
|
|
3882
|
-
const originalContentRef = (0,
|
|
3883
|
-
const activeStateElRef = (0,
|
|
3884
|
-
const parentScrollRef = (0,
|
|
3885
|
-
const toolbarElRef = (0,
|
|
3886
|
-
const glowElRef = (0,
|
|
3887
|
-
const hoveredImageRef = (0,
|
|
3888
|
-
const hoveredImageHasTextOverlapRef = (0,
|
|
3889
|
-
const
|
|
3890
|
-
const
|
|
3891
|
-
const
|
|
3892
|
-
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)(() => {
|
|
3893
4829
|
});
|
|
3894
|
-
const deactivateRef = (0,
|
|
4830
|
+
const deactivateRef = (0, import_react3.useRef)(() => {
|
|
3895
4831
|
});
|
|
3896
|
-
const refreshActiveCommandsRef = (0,
|
|
4832
|
+
const refreshActiveCommandsRef = (0, import_react3.useRef)(() => {
|
|
3897
4833
|
});
|
|
3898
|
-
const postToParentRef = (0,
|
|
4834
|
+
const postToParentRef = (0, import_react3.useRef)(postToParent);
|
|
3899
4835
|
postToParentRef.current = postToParent;
|
|
3900
|
-
const [toolbarRect, setToolbarRect] = (0,
|
|
3901
|
-
const [toggleState, setToggleState] = (0,
|
|
3902
|
-
const [maxBadge, setMaxBadge] = (0,
|
|
3903
|
-
const [activeCommands, setActiveCommands] = (0,
|
|
3904
|
-
(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)(() => {
|
|
3905
4842
|
const update = () => {
|
|
3906
4843
|
const el = activeElRef.current;
|
|
3907
4844
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -3909,6 +4846,12 @@ function OhhwellsBridge() {
|
|
|
3909
4846
|
if (!prev || !activeStateElRef.current) return prev;
|
|
3910
4847
|
return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
|
|
3911
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
|
+
});
|
|
3912
4855
|
};
|
|
3913
4856
|
const vvp = window.visualViewport;
|
|
3914
4857
|
if (!vvp) return;
|
|
@@ -3919,10 +4862,10 @@ function OhhwellsBridge() {
|
|
|
3919
4862
|
vvp.removeEventListener("resize", update);
|
|
3920
4863
|
};
|
|
3921
4864
|
}, []);
|
|
3922
|
-
const refreshStateRules = (0,
|
|
4865
|
+
const refreshStateRules = (0, import_react3.useCallback)(() => {
|
|
3923
4866
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
3924
4867
|
}, []);
|
|
3925
|
-
const deactivate = (0,
|
|
4868
|
+
const deactivate = (0, import_react3.useCallback)(() => {
|
|
3926
4869
|
const el = activeElRef.current;
|
|
3927
4870
|
if (!el) return;
|
|
3928
4871
|
const key = el.dataset.ohwKey;
|
|
@@ -3947,7 +4890,7 @@ function OhhwellsBridge() {
|
|
|
3947
4890
|
setActiveCommands(/* @__PURE__ */ new Set());
|
|
3948
4891
|
postToParent({ type: "ow:exit-edit" });
|
|
3949
4892
|
}, [postToParent]);
|
|
3950
|
-
const activate = (0,
|
|
4893
|
+
const activate = (0, import_react3.useCallback)((el) => {
|
|
3951
4894
|
if (activeElRef.current === el) return;
|
|
3952
4895
|
deactivate();
|
|
3953
4896
|
if (hoveredImageRef.current) {
|
|
@@ -3965,7 +4908,7 @@ function OhhwellsBridge() {
|
|
|
3965
4908
|
}, [deactivate, postToParent]);
|
|
3966
4909
|
activateRef.current = activate;
|
|
3967
4910
|
deactivateRef.current = deactivate;
|
|
3968
|
-
(0,
|
|
4911
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
3969
4912
|
if (!subdomain || isEditMode) {
|
|
3970
4913
|
setFetchState("done");
|
|
3971
4914
|
return;
|
|
@@ -3973,6 +4916,7 @@ function OhhwellsBridge() {
|
|
|
3973
4916
|
const applyContent = (content) => {
|
|
3974
4917
|
const imageLoads = [];
|
|
3975
4918
|
for (const [key, val] of Object.entries(content)) {
|
|
4919
|
+
if (key === "__ohw_sections") continue;
|
|
3976
4920
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3977
4921
|
if (el.dataset.ohwEditable === "image") {
|
|
3978
4922
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -3991,6 +4935,7 @@ function OhhwellsBridge() {
|
|
|
3991
4935
|
}
|
|
3992
4936
|
});
|
|
3993
4937
|
}
|
|
4938
|
+
initSectionsFromContent(content, true);
|
|
3994
4939
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
3995
4940
|
}) : Promise.resolve();
|
|
3996
4941
|
};
|
|
@@ -4001,7 +4946,7 @@ function OhhwellsBridge() {
|
|
|
4001
4946
|
}
|
|
4002
4947
|
let cancelled = false;
|
|
4003
4948
|
setFetchState("loading");
|
|
4004
|
-
const apiUrl =
|
|
4949
|
+
const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
4005
4950
|
fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
|
|
4006
4951
|
if (cancelled) return;
|
|
4007
4952
|
const content = data?.content ?? {};
|
|
@@ -4015,12 +4960,15 @@ function OhhwellsBridge() {
|
|
|
4015
4960
|
cancelled = true;
|
|
4016
4961
|
};
|
|
4017
4962
|
}, [subdomain, isEditMode, pathname]);
|
|
4018
|
-
(0,
|
|
4963
|
+
(0, import_react3.useEffect)(() => {
|
|
4019
4964
|
if (!subdomain || isEditMode) return;
|
|
4965
|
+
let debounceTimer = null;
|
|
4020
4966
|
const applyFromCache = () => {
|
|
4021
4967
|
const content = contentCache.get(subdomain);
|
|
4022
4968
|
if (!content) return;
|
|
4969
|
+
retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
|
|
4023
4970
|
for (const [key, val] of Object.entries(content)) {
|
|
4971
|
+
if (key === "__ohw_sections") continue;
|
|
4024
4972
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4025
4973
|
if (el.dataset.ohwEditable === "image") {
|
|
4026
4974
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4034,21 +4982,28 @@ function OhhwellsBridge() {
|
|
|
4034
4982
|
});
|
|
4035
4983
|
}
|
|
4036
4984
|
};
|
|
4037
|
-
|
|
4038
|
-
|
|
4985
|
+
const scheduleApply = () => {
|
|
4986
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4987
|
+
debounceTimer = setTimeout(applyFromCache, 150);
|
|
4988
|
+
};
|
|
4989
|
+
scheduleApply();
|
|
4990
|
+
const observer = new MutationObserver(scheduleApply);
|
|
4039
4991
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
4040
|
-
return () =>
|
|
4041
|
-
|
|
4042
|
-
|
|
4992
|
+
return () => {
|
|
4993
|
+
observer.disconnect();
|
|
4994
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4995
|
+
};
|
|
4996
|
+
}, [subdomain, isEditMode, pathname]);
|
|
4997
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
4043
4998
|
const el = document.getElementById("ohw-loader");
|
|
4044
4999
|
if (!el) return;
|
|
4045
5000
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
4046
5001
|
el.style.display = visible ? "flex" : "none";
|
|
4047
5002
|
}, [subdomain, fetchState]);
|
|
4048
|
-
(0,
|
|
5003
|
+
(0, import_react3.useEffect)(() => {
|
|
4049
5004
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
4050
5005
|
}, [pathname, postToParent]);
|
|
4051
|
-
(0,
|
|
5006
|
+
(0, import_react3.useEffect)(() => {
|
|
4052
5007
|
if (!isEditMode) return;
|
|
4053
5008
|
const measure = () => {
|
|
4054
5009
|
const h = document.body.scrollHeight;
|
|
@@ -4072,7 +5027,7 @@ function OhhwellsBridge() {
|
|
|
4072
5027
|
window.removeEventListener("resize", handleResize);
|
|
4073
5028
|
};
|
|
4074
5029
|
}, [pathname, isEditMode, postToParent]);
|
|
4075
|
-
(0,
|
|
5030
|
+
(0, import_react3.useEffect)(() => {
|
|
4076
5031
|
if (!subdomainFromQuery || isEditMode) return;
|
|
4077
5032
|
const handleClick = (e) => {
|
|
4078
5033
|
const anchor = e.target.closest("a");
|
|
@@ -4088,7 +5043,7 @@ function OhhwellsBridge() {
|
|
|
4088
5043
|
document.addEventListener("click", handleClick, true);
|
|
4089
5044
|
return () => document.removeEventListener("click", handleClick, true);
|
|
4090
5045
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
4091
|
-
(0,
|
|
5046
|
+
(0, import_react3.useEffect)(() => {
|
|
4092
5047
|
if (!isEditMode) {
|
|
4093
5048
|
editStylesRef.current?.base.remove();
|
|
4094
5049
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -4348,6 +5303,15 @@ function OhhwellsBridge() {
|
|
|
4348
5303
|
textEditable.setAttribute("data-ohw-hovered", "");
|
|
4349
5304
|
return;
|
|
4350
5305
|
}
|
|
5306
|
+
if (hoveredGapRef.current) {
|
|
5307
|
+
if (hoveredImageRef.current) {
|
|
5308
|
+
hoveredImageRef.current = null;
|
|
5309
|
+
resumeAnimTracks();
|
|
5310
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
5311
|
+
}
|
|
5312
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5313
|
+
return;
|
|
5314
|
+
}
|
|
4351
5315
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4352
5316
|
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
4353
5317
|
hoveredImageRef.current = imgEl;
|
|
@@ -4413,8 +5377,30 @@ function OhhwellsBridge() {
|
|
|
4413
5377
|
}
|
|
4414
5378
|
}
|
|
4415
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);
|
|
5399
|
+
}
|
|
5400
|
+
};
|
|
4416
5401
|
const handleMouseMove = (e) => {
|
|
4417
5402
|
const { clientX, clientY } = e;
|
|
5403
|
+
probeSectionGapAt(clientX, clientY);
|
|
4418
5404
|
probeImageAt(clientX, clientY);
|
|
4419
5405
|
probeHoverCardsAt(clientX, clientY);
|
|
4420
5406
|
};
|
|
@@ -4422,6 +5408,7 @@ function OhhwellsBridge() {
|
|
|
4422
5408
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
4423
5409
|
const { clientX, clientY } = e.data;
|
|
4424
5410
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
5411
|
+
probeSectionGapAt(clientX, clientY);
|
|
4425
5412
|
probeImageAt(clientX, clientY);
|
|
4426
5413
|
probeHoverCardsAt(clientX, clientY);
|
|
4427
5414
|
};
|
|
@@ -4579,7 +5566,12 @@ function OhhwellsBridge() {
|
|
|
4579
5566
|
if (e.data?.type !== "ow:hydrate") return;
|
|
4580
5567
|
const content = e.data.content;
|
|
4581
5568
|
if (!content) return;
|
|
5569
|
+
let sectionsJson = null;
|
|
4582
5570
|
for (const [key, val] of Object.entries(content)) {
|
|
5571
|
+
if (key === "__ohw_sections") {
|
|
5572
|
+
sectionsJson = val;
|
|
5573
|
+
continue;
|
|
5574
|
+
}
|
|
4583
5575
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4584
5576
|
if (el.dataset.ohwEditable === "image") {
|
|
4585
5577
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4591,6 +5583,9 @@ function OhhwellsBridge() {
|
|
|
4591
5583
|
}
|
|
4592
5584
|
});
|
|
4593
5585
|
}
|
|
5586
|
+
if (sectionsJson) {
|
|
5587
|
+
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
5588
|
+
}
|
|
4594
5589
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
4595
5590
|
};
|
|
4596
5591
|
window.addEventListener("message", handleHydrate);
|
|
@@ -4634,7 +5629,63 @@ function OhhwellsBridge() {
|
|
|
4634
5629
|
};
|
|
4635
5630
|
const handleSave = (e) => {
|
|
4636
5631
|
if (e.data?.type !== "ow:save") return;
|
|
4637
|
-
|
|
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 });
|
|
4638
5689
|
};
|
|
4639
5690
|
const handleSelectionChange = () => {
|
|
4640
5691
|
if (!activeElRef.current) return;
|
|
@@ -4739,6 +5790,11 @@ function OhhwellsBridge() {
|
|
|
4739
5790
|
deactivateRef.current();
|
|
4740
5791
|
};
|
|
4741
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);
|
|
4742
5798
|
window.addEventListener("message", handleImageUrl);
|
|
4743
5799
|
window.addEventListener("message", handleAnimLock);
|
|
4744
5800
|
window.addEventListener("message", handleCanvasHeight);
|
|
@@ -4773,6 +5829,11 @@ function OhhwellsBridge() {
|
|
|
4773
5829
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
4774
5830
|
window.removeEventListener("scroll", handleScroll, true);
|
|
4775
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);
|
|
4776
5837
|
window.removeEventListener("message", handleImageUrl);
|
|
4777
5838
|
window.removeEventListener("message", handleAnimLock);
|
|
4778
5839
|
window.removeEventListener("message", handleCanvasHeight);
|
|
@@ -4787,7 +5848,7 @@ function OhhwellsBridge() {
|
|
|
4787
5848
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
4788
5849
|
};
|
|
4789
5850
|
}, [isEditMode, refreshStateRules]);
|
|
4790
|
-
(0,
|
|
5851
|
+
(0, import_react3.useEffect)(() => {
|
|
4791
5852
|
if (!isEditMode) return;
|
|
4792
5853
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
4793
5854
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -4809,13 +5870,13 @@ function OhhwellsBridge() {
|
|
|
4809
5870
|
clearTimeout(timer);
|
|
4810
5871
|
};
|
|
4811
5872
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
4812
|
-
const handleCommand = (0,
|
|
5873
|
+
const handleCommand = (0, import_react3.useCallback)((cmd) => {
|
|
4813
5874
|
document.execCommand(cmd, false);
|
|
4814
5875
|
activeElRef.current?.focus();
|
|
4815
5876
|
if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
|
|
4816
5877
|
refreshActiveCommandsRef.current();
|
|
4817
5878
|
}, []);
|
|
4818
|
-
const handleStateChange = (0,
|
|
5879
|
+
const handleStateChange = (0, import_react3.useCallback)((state) => {
|
|
4819
5880
|
if (!activeStateElRef.current) return;
|
|
4820
5881
|
const el = activeStateElRef.current;
|
|
4821
5882
|
if (state === "Default") {
|
|
@@ -4828,13 +5889,13 @@ function OhhwellsBridge() {
|
|
|
4828
5889
|
}
|
|
4829
5890
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
4830
5891
|
}, [deactivate]);
|
|
4831
|
-
return bridgeRoot ? (0,
|
|
4832
|
-
/* @__PURE__ */ (0,
|
|
4833
|
-
toolbarRect && /* @__PURE__ */ (0,
|
|
4834
|
-
/* @__PURE__ */ (0,
|
|
4835
|
-
/* @__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 })
|
|
4836
5897
|
] }),
|
|
4837
|
-
maxBadge && /* @__PURE__ */ (0,
|
|
5898
|
+
maxBadge && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
4838
5899
|
"div",
|
|
4839
5900
|
{
|
|
4840
5901
|
"data-ohw-max-badge": "",
|
|
@@ -4860,7 +5921,7 @@ function OhhwellsBridge() {
|
|
|
4860
5921
|
]
|
|
4861
5922
|
}
|
|
4862
5923
|
),
|
|
4863
|
-
toggleState && /* @__PURE__ */ (0,
|
|
5924
|
+
toggleState && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4864
5925
|
StateToggle,
|
|
4865
5926
|
{
|
|
4866
5927
|
rect: toggleState.rect,
|
|
@@ -4868,6 +5929,31 @@ function OhhwellsBridge() {
|
|
|
4868
5929
|
states: toggleState.states,
|
|
4869
5930
|
onStateChange: handleStateChange
|
|
4870
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
|
+
}
|
|
4871
5957
|
)
|
|
4872
5958
|
] }),
|
|
4873
5959
|
bridgeRoot
|
|
@@ -4876,6 +5962,7 @@ function OhhwellsBridge() {
|
|
|
4876
5962
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4877
5963
|
0 && (module.exports = {
|
|
4878
5964
|
OhhwellsBridge,
|
|
5965
|
+
SchedulingWidget,
|
|
4879
5966
|
Toggle,
|
|
4880
5967
|
ToggleGroup,
|
|
4881
5968
|
ToggleGroupItem,
|