@ohhwells/bridge 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +891 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +880 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +366 -2
- package/package.json +1 -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,670 @@ __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
|
+
|
|
47
|
+
// src/ui/SchedulingWidget.tsx
|
|
48
|
+
var import_react2 = require("react");
|
|
49
|
+
|
|
50
|
+
// src/ui/EmailCaptureModal.tsx
|
|
51
|
+
var import_react = require("react");
|
|
52
|
+
var import_radix_ui = require("radix-ui");
|
|
53
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
54
|
+
function Spinner() {
|
|
55
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
56
|
+
"svg",
|
|
57
|
+
{
|
|
58
|
+
width: "32",
|
|
59
|
+
height: "32",
|
|
60
|
+
viewBox: "0 0 32 32",
|
|
61
|
+
fill: "none",
|
|
62
|
+
style: { animation: "ohw-spin 0.75s linear infinite" },
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
|
|
65
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
|
|
66
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
67
|
+
"path",
|
|
68
|
+
{
|
|
69
|
+
d: "M16 4a12 12 0 0 1 12 12",
|
|
70
|
+
stroke: "#3b82f6",
|
|
71
|
+
strokeWidth: "3",
|
|
72
|
+
strokeLinecap: "round"
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
80
|
+
const [email, setEmail] = (0, import_react.useState)("");
|
|
81
|
+
const [submittedEmail, setSubmittedEmail] = (0, import_react.useState)("");
|
|
82
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
83
|
+
const [success, setSuccess] = (0, import_react.useState)(false);
|
|
84
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
85
|
+
const isBook = title === "Confirm your spot";
|
|
86
|
+
const handleSubmit = async () => {
|
|
87
|
+
if (!email.trim()) return;
|
|
88
|
+
setLoading(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
try {
|
|
91
|
+
await onSubmit(email.trim());
|
|
92
|
+
setSubmittedEmail(email.trim());
|
|
93
|
+
setSuccess(true);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
96
|
+
} finally {
|
|
97
|
+
setLoading(false);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const handleKeyDown = (e) => {
|
|
101
|
+
if (e.key === "Enter" && !loading) handleSubmit();
|
|
102
|
+
};
|
|
103
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_radix_ui.Dialog.Root, { open: true, onOpenChange: (open) => {
|
|
104
|
+
if (!open) onClose();
|
|
105
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_radix_ui.Dialog.Portal, { children: [
|
|
106
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
107
|
+
import_radix_ui.Dialog.Overlay,
|
|
108
|
+
{
|
|
109
|
+
className: "fixed inset-0 z-50",
|
|
110
|
+
style: { background: "rgba(0,0,0,0.45)" }
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
114
|
+
import_radix_ui.Dialog.Content,
|
|
115
|
+
{
|
|
116
|
+
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",
|
|
117
|
+
style: { maxWidth: 400 },
|
|
118
|
+
children: [
|
|
119
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3 p-6", children: [
|
|
120
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
121
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
122
|
+
import_radix_ui.Dialog.Title,
|
|
123
|
+
{
|
|
124
|
+
className: "font-body text-xl font-bold m-0 leading-tight",
|
|
125
|
+
style: { color: "var(--color-dark,#200C02)" },
|
|
126
|
+
children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
|
|
127
|
+
}
|
|
128
|
+
),
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
130
|
+
import_radix_ui.Dialog.Description,
|
|
131
|
+
{
|
|
132
|
+
className: "font-body text-sm m-0",
|
|
133
|
+
style: { color: "#6B7280" },
|
|
134
|
+
children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
] }),
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
139
|
+
import_radix_ui.Dialog.Close,
|
|
140
|
+
{
|
|
141
|
+
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",
|
|
142
|
+
style: { color: "#6B7280" },
|
|
143
|
+
"aria-label": "Close",
|
|
144
|
+
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: [
|
|
145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
146
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
147
|
+
] })
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
] }),
|
|
151
|
+
!success && (loading ? (
|
|
152
|
+
/* Loading state — full body replaced */
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
|
|
156
|
+
] })
|
|
157
|
+
) : (
|
|
158
|
+
/* Email form */
|
|
159
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-7 pt-2 pb-6", children: [
|
|
160
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1.5 mb-8", children: [
|
|
161
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
|
|
162
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
163
|
+
"input",
|
|
164
|
+
{
|
|
165
|
+
type: "email",
|
|
166
|
+
value: email,
|
|
167
|
+
onChange: (e) => setEmail(e.target.value),
|
|
168
|
+
onKeyDown: handleKeyDown,
|
|
169
|
+
placeholder: "hello@gmail.com",
|
|
170
|
+
autoFocus: true,
|
|
171
|
+
className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
|
|
172
|
+
style: {
|
|
173
|
+
borderColor: "#E7E5E4",
|
|
174
|
+
background: "#fff",
|
|
175
|
+
color: "var(--color-dark,#200C02)"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-xs text-red-500 m-0", children: error })
|
|
180
|
+
] }),
|
|
181
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
182
|
+
"button",
|
|
183
|
+
{
|
|
184
|
+
onClick: handleSubmit,
|
|
185
|
+
disabled: !email.trim(),
|
|
186
|
+
className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
|
|
187
|
+
style: {
|
|
188
|
+
background: "var(--color-primary,#3D312B)",
|
|
189
|
+
color: "var(--color-light,#FEFFF0)"
|
|
190
|
+
},
|
|
191
|
+
children: "Send"
|
|
192
|
+
}
|
|
193
|
+
) })
|
|
194
|
+
] })
|
|
195
|
+
))
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
] }) });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/ui/SchedulingWidget.tsx
|
|
203
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
204
|
+
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
205
|
+
var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
206
|
+
function getApiDomain() {
|
|
207
|
+
const h = window.location.hostname;
|
|
208
|
+
for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
|
|
209
|
+
if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
|
|
210
|
+
}
|
|
211
|
+
return h;
|
|
212
|
+
}
|
|
213
|
+
function classRunsOnDate(cls, date, type) {
|
|
214
|
+
if (type === "FIXED") {
|
|
215
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
216
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
217
|
+
return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
|
|
218
|
+
}
|
|
219
|
+
return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
|
|
220
|
+
}
|
|
221
|
+
function formatClassTime(cls) {
|
|
222
|
+
let h = parseInt(cls.startTime.hour, 10);
|
|
223
|
+
const m = parseInt(cls.startTime.minutes, 10);
|
|
224
|
+
if (cls.startTime.time === "PM" && h !== 12) h += 12;
|
|
225
|
+
if (cls.startTime.time === "AM" && h === 12) h = 0;
|
|
226
|
+
const endTotal = h * 60 + m + cls.duration;
|
|
227
|
+
const eh = Math.floor(endTotal / 60) % 24;
|
|
228
|
+
const em = endTotal % 60;
|
|
229
|
+
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
230
|
+
}
|
|
231
|
+
function getBookingsOnDate(cls, date) {
|
|
232
|
+
if (!cls.bookings?.length) return 0;
|
|
233
|
+
const ds = date.toISOString().split("T")[0];
|
|
234
|
+
return cls.bookings.filter((b) => {
|
|
235
|
+
try {
|
|
236
|
+
return new Date(b.classDate).toISOString().split("T")[0] === ds;
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}).length;
|
|
241
|
+
}
|
|
242
|
+
function buildTimezoneLabel(tz) {
|
|
243
|
+
try {
|
|
244
|
+
const now = /* @__PURE__ */ new Date();
|
|
245
|
+
const timeStr = new Intl.DateTimeFormat("en-US", {
|
|
246
|
+
hour: "2-digit",
|
|
247
|
+
minute: "2-digit",
|
|
248
|
+
hour12: false,
|
|
249
|
+
timeZone: tz
|
|
250
|
+
}).format(now);
|
|
251
|
+
const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
252
|
+
const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
|
|
253
|
+
return `${timeStr} (${offsetPart}) ${city} time`;
|
|
254
|
+
} catch {
|
|
255
|
+
return tz;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function EmptyState({ inEditor }) {
|
|
259
|
+
const handleAddSchedule = () => {
|
|
260
|
+
if (inEditor) {
|
|
261
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
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: [
|
|
265
|
+
/* @__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)(
|
|
266
|
+
"svg",
|
|
267
|
+
{
|
|
268
|
+
width: "20",
|
|
269
|
+
height: "20",
|
|
270
|
+
viewBox: "0 0 24 24",
|
|
271
|
+
fill: "none",
|
|
272
|
+
stroke: "var(--color-accent, #A89B83)",
|
|
273
|
+
strokeWidth: "1.5",
|
|
274
|
+
strokeLinecap: "round",
|
|
275
|
+
strokeLinejoin: "round",
|
|
276
|
+
children: [
|
|
277
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
|
|
278
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
|
|
279
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
|
|
280
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
|
|
281
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
|
|
282
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
) }),
|
|
286
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
|
|
287
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
|
|
288
|
+
/* @__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." })
|
|
289
|
+
] }),
|
|
290
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
291
|
+
"button",
|
|
292
|
+
{
|
|
293
|
+
onClick: handleAddSchedule,
|
|
294
|
+
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",
|
|
295
|
+
children: "Add Schedule"
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
] });
|
|
299
|
+
}
|
|
300
|
+
function LoadingSkeleton() {
|
|
301
|
+
const shimmer = {
|
|
302
|
+
background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
|
|
303
|
+
backgroundSize: "200% 100%",
|
|
304
|
+
animation: "ohw-shimmer 1.5s infinite"
|
|
305
|
+
};
|
|
306
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
|
|
307
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
|
|
308
|
+
/* @__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: [
|
|
309
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
|
|
310
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
|
|
311
|
+
] }, i)) }),
|
|
312
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
313
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-[60px] py-4", children: [
|
|
314
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
|
|
315
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
|
|
317
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
|
|
318
|
+
] }),
|
|
319
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
|
|
320
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
|
|
321
|
+
] }),
|
|
322
|
+
i < 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
|
|
323
|
+
] }, i))
|
|
324
|
+
] });
|
|
325
|
+
}
|
|
326
|
+
function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
|
|
327
|
+
const todayMs = (0, import_react2.useMemo)(() => {
|
|
328
|
+
const d = /* @__PURE__ */ new Date();
|
|
329
|
+
d.setHours(0, 0, 0, 0);
|
|
330
|
+
return d.getTime();
|
|
331
|
+
}, []);
|
|
332
|
+
const scrollRef = (0, import_react2.useRef)(null);
|
|
333
|
+
const [canScrollLeft, setCanScrollLeft] = (0, import_react2.useState)(false);
|
|
334
|
+
const [canScrollRight, setCanScrollRight] = (0, import_react2.useState)(false);
|
|
335
|
+
const updateScrollState = () => {
|
|
336
|
+
const el = scrollRef.current;
|
|
337
|
+
if (!el) return;
|
|
338
|
+
setCanScrollLeft(el.scrollLeft > 0);
|
|
339
|
+
setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
340
|
+
};
|
|
341
|
+
(0, import_react2.useEffect)(() => {
|
|
342
|
+
const el = scrollRef.current;
|
|
343
|
+
if (!el) return;
|
|
344
|
+
updateScrollState();
|
|
345
|
+
el.addEventListener("scroll", updateScrollState);
|
|
346
|
+
const ro = new ResizeObserver(updateScrollState);
|
|
347
|
+
ro.observe(el);
|
|
348
|
+
return () => {
|
|
349
|
+
el.removeEventListener("scroll", updateScrollState);
|
|
350
|
+
ro.disconnect();
|
|
351
|
+
};
|
|
352
|
+
}, [dates]);
|
|
353
|
+
const scroll = (dir) => {
|
|
354
|
+
scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
|
|
355
|
+
};
|
|
356
|
+
const datesWithClasses = (0, import_react2.useMemo)(
|
|
357
|
+
() => new Set(dates.map(
|
|
358
|
+
(d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
|
|
359
|
+
).filter((i) => i >= 0)),
|
|
360
|
+
[dates, schedule]
|
|
361
|
+
);
|
|
362
|
+
const selectedDate = dates[selectedIdx];
|
|
363
|
+
const selectedClasses = (0, import_react2.useMemo)(
|
|
364
|
+
() => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
|
|
365
|
+
[schedule, selectedDate]
|
|
366
|
+
);
|
|
367
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
|
|
368
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full", children: [
|
|
369
|
+
canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
370
|
+
"button",
|
|
371
|
+
{
|
|
372
|
+
onClick: () => scroll("left"),
|
|
373
|
+
className: "absolute left-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 flex items-center justify-center rounded-full border-none cursor-pointer",
|
|
374
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
375
|
+
"aria-label": "Scroll left",
|
|
376
|
+
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" }) })
|
|
377
|
+
}
|
|
378
|
+
),
|
|
379
|
+
canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
380
|
+
"button",
|
|
381
|
+
{
|
|
382
|
+
onClick: () => scroll("right"),
|
|
383
|
+
className: "absolute right-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 flex items-center justify-center rounded-full border-none cursor-pointer",
|
|
384
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
385
|
+
"aria-label": "Scroll right",
|
|
386
|
+
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" }) })
|
|
387
|
+
}
|
|
388
|
+
),
|
|
389
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
390
|
+
"div",
|
|
391
|
+
{
|
|
392
|
+
ref: scrollRef,
|
|
393
|
+
className: "overflow-x-auto w-full",
|
|
394
|
+
style: {
|
|
395
|
+
scrollbarWidth: "none",
|
|
396
|
+
msOverflowStyle: "none",
|
|
397
|
+
paddingLeft: canScrollLeft ? "36px" : 0,
|
|
398
|
+
paddingRight: canScrollRight ? "36px" : 0
|
|
399
|
+
},
|
|
400
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
|
|
401
|
+
const isToday = date.getTime() === todayMs;
|
|
402
|
+
const isSelected = i === selectedIdx;
|
|
403
|
+
const clickable = datesWithClasses.has(i);
|
|
404
|
+
const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
|
|
405
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
406
|
+
"div",
|
|
407
|
+
{
|
|
408
|
+
onClick: () => clickable && onSelectDate(i),
|
|
409
|
+
className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
|
|
410
|
+
children: [
|
|
411
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
|
|
412
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
413
|
+
"div",
|
|
414
|
+
{
|
|
415
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center",
|
|
416
|
+
style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
|
|
417
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
418
|
+
"span",
|
|
419
|
+
{
|
|
420
|
+
className: "font-body text-xl font-bold text-center tracking-display-tight",
|
|
421
|
+
style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
|
|
422
|
+
children: date.getDate()
|
|
423
|
+
}
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
)
|
|
427
|
+
]
|
|
428
|
+
},
|
|
429
|
+
i
|
|
430
|
+
);
|
|
431
|
+
}) })
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
] }),
|
|
435
|
+
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) => {
|
|
436
|
+
const booked = getBookingsOnDate(cls, selectedDate);
|
|
437
|
+
const available = cls.maxParticipants - booked;
|
|
438
|
+
const isFull = available <= 0;
|
|
439
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
440
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-[60px] py-4 box-border", children: [
|
|
441
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-[120px] 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) }) }),
|
|
442
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-3 min-w-0", children: [
|
|
443
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
444
|
+
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 })
|
|
445
|
+
] }),
|
|
446
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-14 shrink-0 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: [
|
|
447
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
448
|
+
available,
|
|
449
|
+
"/",
|
|
450
|
+
cls.maxParticipants
|
|
451
|
+
] }),
|
|
452
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
453
|
+
] }) }),
|
|
454
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
455
|
+
"button",
|
|
456
|
+
{
|
|
457
|
+
className: "shrink-0 w-[200px] px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
|
|
458
|
+
style: isFull ? {
|
|
459
|
+
background: "transparent",
|
|
460
|
+
border: "1px solid var(--color-dark, #200C02)",
|
|
461
|
+
color: "var(--color-dark, #200C02)"
|
|
462
|
+
} : {
|
|
463
|
+
background: "var(--color-primary, #3D312B)",
|
|
464
|
+
border: "none",
|
|
465
|
+
color: "var(--color-light, #FEFFF0)"
|
|
466
|
+
},
|
|
467
|
+
onClick: () => {
|
|
468
|
+
if (!cls.id) return;
|
|
469
|
+
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
470
|
+
},
|
|
471
|
+
children: isFull ? "Join Waitlist" : "Book Now"
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
] }),
|
|
475
|
+
i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
|
|
476
|
+
] }, cls.id ?? i);
|
|
477
|
+
}) })
|
|
478
|
+
] });
|
|
479
|
+
}
|
|
480
|
+
function CalendarFoldIcon() {
|
|
481
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
482
|
+
"svg",
|
|
483
|
+
{
|
|
484
|
+
width: "16",
|
|
485
|
+
height: "16",
|
|
486
|
+
viewBox: "0 0 24 24",
|
|
487
|
+
fill: "none",
|
|
488
|
+
stroke: "currentColor",
|
|
489
|
+
strokeWidth: "2",
|
|
490
|
+
strokeLinecap: "round",
|
|
491
|
+
strokeLinejoin: "round",
|
|
492
|
+
style: { flexShrink: 0 },
|
|
493
|
+
children: [
|
|
494
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2v4" }),
|
|
495
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 2v4" }),
|
|
496
|
+
/* @__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" }),
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 10h18" }),
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
|
|
499
|
+
]
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
|
|
504
|
+
const [schedule, setSchedule] = (0, import_react2.useState)(null);
|
|
505
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
506
|
+
const [inEditor, setInEditor] = (0, import_react2.useState)(false);
|
|
507
|
+
const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
|
|
508
|
+
const [modalState, setModalState] = (0, import_react2.useState)(null);
|
|
509
|
+
const switchScheduleIdRef = (0, import_react2.useRef)(null);
|
|
510
|
+
const dates = (0, import_react2.useMemo)(() => {
|
|
511
|
+
const today = /* @__PURE__ */ new Date();
|
|
512
|
+
today.setHours(0, 0, 0, 0);
|
|
513
|
+
return Array.from({ length: 14 }, (_, i) => {
|
|
514
|
+
const d = new Date(today);
|
|
515
|
+
d.setDate(today.getDate() + i);
|
|
516
|
+
return d;
|
|
517
|
+
});
|
|
518
|
+
}, []);
|
|
519
|
+
const [selectedIdx, setSelectedIdx] = (0, import_react2.useState)(0);
|
|
520
|
+
(0, import_react2.useEffect)(() => {
|
|
521
|
+
if (!schedule?.classes) return;
|
|
522
|
+
for (let i = 0; i < dates.length; i++) {
|
|
523
|
+
if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
|
|
524
|
+
setSelectedIdx(i);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}, [schedule, dates]);
|
|
529
|
+
(0, import_react2.useEffect)(() => {
|
|
530
|
+
if (typeof window === "undefined") return;
|
|
531
|
+
const isInEditor = window.self !== window.top;
|
|
532
|
+
setInEditor(isInEditor);
|
|
533
|
+
if (isInEditor) {
|
|
534
|
+
const startEmpty = initialScheduleId === null;
|
|
535
|
+
if (startEmpty) setLoading(false);
|
|
536
|
+
let initialized = startEmpty;
|
|
537
|
+
const timer = !startEmpty ? setTimeout(() => {
|
|
538
|
+
if (!initialized) {
|
|
539
|
+
initialized = true;
|
|
540
|
+
setLoading(false);
|
|
541
|
+
}
|
|
542
|
+
}, 5e3) : null;
|
|
543
|
+
const handler = (e) => {
|
|
544
|
+
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
545
|
+
setSchedule(null);
|
|
546
|
+
setLoading(false);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (e.data?.type === "ow:switch-schedule") {
|
|
550
|
+
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
551
|
+
initialized = false;
|
|
552
|
+
setLoading(true);
|
|
553
|
+
window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
557
|
+
if (initialized && switchScheduleIdRef.current === null) return;
|
|
558
|
+
initialized = true;
|
|
559
|
+
if (timer) clearTimeout(timer);
|
|
560
|
+
const schedules = e.data.schedules ?? [];
|
|
561
|
+
const isSwitching = switchScheduleIdRef.current !== null;
|
|
562
|
+
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;
|
|
563
|
+
switchScheduleIdRef.current = null;
|
|
564
|
+
setSchedule(target);
|
|
565
|
+
setLoading(false);
|
|
566
|
+
if (notifyOnConnect && target && !isSwitching) {
|
|
567
|
+
window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: target.id, name: target.name } }, "*");
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
window.addEventListener("message", handler);
|
|
571
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
|
|
572
|
+
return () => {
|
|
573
|
+
window.removeEventListener("message", handler);
|
|
574
|
+
if (timer) clearTimeout(timer);
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
if (initialScheduleId === null) {
|
|
578
|
+
setLoading(false);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
;
|
|
582
|
+
(async () => {
|
|
583
|
+
try {
|
|
584
|
+
if (initialScheduleId) {
|
|
585
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
|
|
586
|
+
const data = await res.json();
|
|
587
|
+
setSchedule(data?.id ? data : null);
|
|
588
|
+
} else {
|
|
589
|
+
const domain = getApiDomain();
|
|
590
|
+
const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
|
|
591
|
+
const slugs = await sitemapRes.json();
|
|
592
|
+
if (!Array.isArray(slugs) || !slugs.length) {
|
|
593
|
+
setLoading(false);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const { slug } = [...slugs].sort(
|
|
597
|
+
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
598
|
+
)[0];
|
|
599
|
+
const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
|
|
600
|
+
const data = await res.json();
|
|
601
|
+
setSchedule(data?.id ? data : null);
|
|
602
|
+
}
|
|
603
|
+
} catch {
|
|
604
|
+
}
|
|
605
|
+
setLoading(false);
|
|
606
|
+
})();
|
|
607
|
+
}, []);
|
|
608
|
+
const timezoneLabel = schedule?.timezone ? (() => {
|
|
609
|
+
try {
|
|
610
|
+
return buildTimezoneLabel(schedule.timezone);
|
|
611
|
+
} catch {
|
|
612
|
+
return void 0;
|
|
613
|
+
}
|
|
614
|
+
})() : void 0;
|
|
615
|
+
const handleModalSubmit = async (email) => {
|
|
616
|
+
if (!modalState) return;
|
|
617
|
+
const { mode, classId, classDate } = modalState;
|
|
618
|
+
const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
|
|
619
|
+
const res = await fetch(endpoint, {
|
|
620
|
+
method: "POST",
|
|
621
|
+
headers: { "Content-Type": "application/json" },
|
|
622
|
+
body: JSON.stringify({ classDate: classDate.toISOString(), email })
|
|
623
|
+
});
|
|
624
|
+
if (!res.ok) {
|
|
625
|
+
const data = await res.json().catch(() => ({}));
|
|
626
|
+
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
const handleReplaceSchedule = () => {
|
|
630
|
+
setIsHovered(false);
|
|
631
|
+
if (schedule) {
|
|
632
|
+
window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: schedule.id, name: schedule.name } }, "*");
|
|
633
|
+
} else {
|
|
634
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
638
|
+
"section",
|
|
639
|
+
{
|
|
640
|
+
"data-ohw-section": "scheduling",
|
|
641
|
+
className: "w-full box-border py-20 px-16 font-body bg-(--color-light,#FEFFF0) relative",
|
|
642
|
+
onMouseEnter: () => inEditor && setIsHovered(true),
|
|
643
|
+
onMouseLeave: () => setIsHovered(false),
|
|
644
|
+
children: [
|
|
645
|
+
inEditor && isHovered && !!schedule && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
646
|
+
"div",
|
|
647
|
+
{
|
|
648
|
+
className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
|
|
649
|
+
onClick: handleReplaceSchedule,
|
|
650
|
+
children: [
|
|
651
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
|
|
652
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
|
|
653
|
+
/* @__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)(
|
|
654
|
+
"div",
|
|
655
|
+
{
|
|
656
|
+
className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
|
|
657
|
+
style: {
|
|
658
|
+
fontSize: 14,
|
|
659
|
+
lineHeight: "24px",
|
|
660
|
+
fontFamily: "'Figtree', system-ui, sans-serif",
|
|
661
|
+
fontWeight: 500,
|
|
662
|
+
color: "#0C0A09"
|
|
663
|
+
},
|
|
664
|
+
children: [
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CalendarFoldIcon, {}),
|
|
666
|
+
"Replace schedule"
|
|
667
|
+
]
|
|
668
|
+
}
|
|
669
|
+
) })
|
|
670
|
+
]
|
|
671
|
+
}
|
|
672
|
+
),
|
|
673
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
674
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
|
|
676
|
+
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 })
|
|
677
|
+
] }),
|
|
678
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
679
|
+
timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
680
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full 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)(
|
|
681
|
+
ScheduleView,
|
|
682
|
+
{
|
|
683
|
+
schedule,
|
|
684
|
+
dates,
|
|
685
|
+
selectedIdx,
|
|
686
|
+
onSelectDate: setSelectedIdx,
|
|
687
|
+
onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
|
|
688
|
+
}
|
|
689
|
+
) })
|
|
690
|
+
] })
|
|
691
|
+
] }),
|
|
692
|
+
modalState && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
693
|
+
EmailCaptureModal,
|
|
694
|
+
{
|
|
695
|
+
title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
|
|
696
|
+
subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
|
|
697
|
+
onSubmit: handleModalSubmit,
|
|
698
|
+
onClose: () => setModalState(null)
|
|
699
|
+
}
|
|
700
|
+
)
|
|
701
|
+
]
|
|
702
|
+
}
|
|
703
|
+
);
|
|
704
|
+
}
|
|
44
705
|
|
|
45
706
|
// src/ui/toggle-group.tsx
|
|
46
|
-
var
|
|
707
|
+
var React2 = __toESM(require("react"), 1);
|
|
47
708
|
|
|
48
709
|
// node_modules/clsx/dist/clsx.mjs
|
|
49
710
|
function r(e) {
|
|
@@ -3362,8 +4023,8 @@ var cva = (base, config) => (props) => {
|
|
|
3362
4023
|
};
|
|
3363
4024
|
|
|
3364
4025
|
// src/ui/toggle.tsx
|
|
3365
|
-
var
|
|
3366
|
-
var
|
|
4026
|
+
var import_radix_ui2 = require("radix-ui");
|
|
4027
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3367
4028
|
var toggleVariants = cva(
|
|
3368
4029
|
"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
4030
|
{
|
|
@@ -3390,8 +4051,8 @@ function Toggle({
|
|
|
3390
4051
|
size,
|
|
3391
4052
|
...props
|
|
3392
4053
|
}) {
|
|
3393
|
-
return /* @__PURE__ */ (0,
|
|
3394
|
-
|
|
4054
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
4055
|
+
import_radix_ui2.Toggle.Root,
|
|
3395
4056
|
{
|
|
3396
4057
|
"data-slot": "toggle",
|
|
3397
4058
|
className: cn(toggleVariants({ variant, size, className })),
|
|
@@ -3401,8 +4062,8 @@ function Toggle({
|
|
|
3401
4062
|
}
|
|
3402
4063
|
|
|
3403
4064
|
// src/ui/toggle-group.tsx
|
|
3404
|
-
var
|
|
3405
|
-
var ToggleGroupContext =
|
|
4065
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
4066
|
+
var ToggleGroupContext = React2.createContext({
|
|
3406
4067
|
value: "",
|
|
3407
4068
|
onValueChange: () => {
|
|
3408
4069
|
}
|
|
@@ -3414,13 +4075,13 @@ function ToggleGroup({
|
|
|
3414
4075
|
children,
|
|
3415
4076
|
...props
|
|
3416
4077
|
}) {
|
|
3417
|
-
return /* @__PURE__ */ (0,
|
|
4078
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3418
4079
|
"div",
|
|
3419
4080
|
{
|
|
3420
4081
|
role: "group",
|
|
3421
4082
|
className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
|
|
3422
4083
|
...props,
|
|
3423
|
-
children: /* @__PURE__ */ (0,
|
|
4084
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
|
|
3424
4085
|
}
|
|
3425
4086
|
);
|
|
3426
4087
|
}
|
|
@@ -3430,8 +4091,8 @@ function ToggleGroupItem({
|
|
|
3430
4091
|
children,
|
|
3431
4092
|
...props
|
|
3432
4093
|
}) {
|
|
3433
|
-
const { value: groupValue, onValueChange } =
|
|
3434
|
-
return /* @__PURE__ */ (0,
|
|
4094
|
+
const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
|
|
4095
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3435
4096
|
Toggle,
|
|
3436
4097
|
{
|
|
3437
4098
|
pressed: groupValue === value,
|
|
@@ -3473,7 +4134,7 @@ function isEditSessionActive() {
|
|
|
3473
4134
|
}
|
|
3474
4135
|
|
|
3475
4136
|
// src/ui/badge.tsx
|
|
3476
|
-
var
|
|
4137
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
3477
4138
|
var badgeVariants = cva(
|
|
3478
4139
|
"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",
|
|
3479
4140
|
{
|
|
@@ -3491,11 +4152,11 @@ var badgeVariants = cva(
|
|
|
3491
4152
|
}
|
|
3492
4153
|
);
|
|
3493
4154
|
function Badge({ className, variant, ...props }) {
|
|
3494
|
-
return /* @__PURE__ */ (0,
|
|
4155
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
3495
4156
|
}
|
|
3496
4157
|
|
|
3497
4158
|
// src/OhhwellsBridge.tsx
|
|
3498
|
-
var
|
|
4159
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
3499
4160
|
var PRIMARY = "#0885FE";
|
|
3500
4161
|
var IMAGE_FADE_MS = 300;
|
|
3501
4162
|
function runOpacityFade(el, onDone) {
|
|
@@ -3546,6 +4207,41 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
3546
4207
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
3547
4208
|
});
|
|
3548
4209
|
}
|
|
4210
|
+
function getSectionsTracker() {
|
|
4211
|
+
let el = document.querySelector("[data-ohw-sections-tracker]");
|
|
4212
|
+
if (!el) {
|
|
4213
|
+
el = document.createElement("div");
|
|
4214
|
+
el.setAttribute("data-ohw-sections-tracker", "");
|
|
4215
|
+
el.style.display = "none";
|
|
4216
|
+
document.body.appendChild(el);
|
|
4217
|
+
}
|
|
4218
|
+
return el;
|
|
4219
|
+
}
|
|
4220
|
+
function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId) {
|
|
4221
|
+
const anchor = document.querySelector(`[data-ohw-section="${insertAfter}"]`);
|
|
4222
|
+
if (!anchor) return false;
|
|
4223
|
+
if (anchor.nextElementSibling?.dataset.ohwSectionContainer) return false;
|
|
4224
|
+
const container = document.createElement("div");
|
|
4225
|
+
container.dataset.ohwSectionContainer = "scheduling";
|
|
4226
|
+
anchor.insertAdjacentElement("afterend", container);
|
|
4227
|
+
(0, import_client.createRoot)(container).render(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SchedulingWidget, { notifyOnConnect, initialScheduleId: scheduleId }));
|
|
4228
|
+
const tracker = getSectionsTracker();
|
|
4229
|
+
let sections = [];
|
|
4230
|
+
try {
|
|
4231
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
4232
|
+
} catch {
|
|
4233
|
+
}
|
|
4234
|
+
if (!sections.find((s) => s.type === "scheduling" && s.insertAfter === insertAfter)) {
|
|
4235
|
+
sections.push({
|
|
4236
|
+
type: "scheduling",
|
|
4237
|
+
insertAfter,
|
|
4238
|
+
pagePath: window.location.pathname,
|
|
4239
|
+
...scheduleId ? { scheduleId } : {}
|
|
4240
|
+
});
|
|
4241
|
+
tracker.textContent = JSON.stringify(sections);
|
|
4242
|
+
}
|
|
4243
|
+
return true;
|
|
4244
|
+
}
|
|
3549
4245
|
function collectEditableNodes() {
|
|
3550
4246
|
return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
3551
4247
|
if (el.dataset.ohwEditable === "image") {
|
|
@@ -3703,7 +4399,7 @@ var TOOLBAR_GROUPS = [
|
|
|
3703
4399
|
];
|
|
3704
4400
|
function GlowFrame({ rect, elRef }) {
|
|
3705
4401
|
const GAP = 6;
|
|
3706
|
-
return /* @__PURE__ */ (0,
|
|
4402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3707
4403
|
"div",
|
|
3708
4404
|
{
|
|
3709
4405
|
ref: elRef,
|
|
@@ -3754,7 +4450,7 @@ function FloatingToolbar({
|
|
|
3754
4450
|
activeCommands
|
|
3755
4451
|
}) {
|
|
3756
4452
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
3757
|
-
return /* @__PURE__ */ (0,
|
|
4453
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3758
4454
|
"div",
|
|
3759
4455
|
{
|
|
3760
4456
|
ref: elRef,
|
|
@@ -3777,11 +4473,11 @@ function FloatingToolbar({
|
|
|
3777
4473
|
pointerEvents: "auto",
|
|
3778
4474
|
whiteSpace: "nowrap"
|
|
3779
4475
|
},
|
|
3780
|
-
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0,
|
|
3781
|
-
gi > 0 && /* @__PURE__ */ (0,
|
|
4476
|
+
children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react3.default.Fragment, { children: [
|
|
4477
|
+
gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
|
|
3782
4478
|
btns.map((btn) => {
|
|
3783
4479
|
const isActive = activeCommands.has(btn.cmd);
|
|
3784
|
-
return /* @__PURE__ */ (0,
|
|
4480
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3785
4481
|
"button",
|
|
3786
4482
|
{
|
|
3787
4483
|
title: btn.title,
|
|
@@ -3807,7 +4503,7 @@ function FloatingToolbar({
|
|
|
3807
4503
|
flexShrink: 0,
|
|
3808
4504
|
padding: 6
|
|
3809
4505
|
},
|
|
3810
|
-
children: /* @__PURE__ */ (0,
|
|
4506
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3811
4507
|
"svg",
|
|
3812
4508
|
{
|
|
3813
4509
|
width: "16",
|
|
@@ -3840,7 +4536,7 @@ function StateToggle({
|
|
|
3840
4536
|
states,
|
|
3841
4537
|
onStateChange
|
|
3842
4538
|
}) {
|
|
3843
|
-
return /* @__PURE__ */ (0,
|
|
4539
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3844
4540
|
ToggleGroup,
|
|
3845
4541
|
{
|
|
3846
4542
|
"data-ohw-state-toggle": "",
|
|
@@ -3854,7 +4550,7 @@ function StateToggle({
|
|
|
3854
4550
|
left: rect.right - 8,
|
|
3855
4551
|
transform: "translateX(-100%)"
|
|
3856
4552
|
},
|
|
3857
|
-
children: states.map((state) => /* @__PURE__ */ (0,
|
|
4553
|
+
children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
|
|
3858
4554
|
}
|
|
3859
4555
|
);
|
|
3860
4556
|
}
|
|
@@ -3864,8 +4560,8 @@ function OhhwellsBridge() {
|
|
|
3864
4560
|
const router = (0, import_navigation.useRouter)();
|
|
3865
4561
|
const searchParams = (0, import_navigation.useSearchParams)();
|
|
3866
4562
|
const isEditMode = isEditSessionActive();
|
|
3867
|
-
const [bridgeRoot, setBridgeRoot] = (0,
|
|
3868
|
-
(0,
|
|
4563
|
+
const [bridgeRoot, setBridgeRoot] = (0, import_react3.useState)(null);
|
|
4564
|
+
(0, import_react3.useEffect)(() => {
|
|
3869
4565
|
const figtreeFontId = "ohw-figtree-font";
|
|
3870
4566
|
if (!document.getElementById(figtreeFontId)) {
|
|
3871
4567
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -3893,39 +4589,39 @@ function OhhwellsBridge() {
|
|
|
3893
4589
|
const parts = window.location.hostname.split(".");
|
|
3894
4590
|
return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
|
|
3895
4591
|
})();
|
|
3896
|
-
const postToParent = (0,
|
|
4592
|
+
const postToParent = (0, import_react3.useCallback)((data) => {
|
|
3897
4593
|
if (typeof window !== "undefined" && window.parent !== window) {
|
|
3898
4594
|
window.parent.postMessage(data, "*");
|
|
3899
4595
|
}
|
|
3900
4596
|
}, []);
|
|
3901
|
-
const [fetchState, setFetchState] = (0,
|
|
3902
|
-
const autoSaveTimers = (0,
|
|
3903
|
-
const activeElRef = (0,
|
|
3904
|
-
const originalContentRef = (0,
|
|
3905
|
-
const activeStateElRef = (0,
|
|
3906
|
-
const parentScrollRef = (0,
|
|
3907
|
-
const toolbarElRef = (0,
|
|
3908
|
-
const glowElRef = (0,
|
|
3909
|
-
const hoveredImageRef = (0,
|
|
3910
|
-
const hoveredImageHasTextOverlapRef = (0,
|
|
3911
|
-
const hoveredGapRef = (0,
|
|
3912
|
-
const imageUnhoverTimerRef = (0,
|
|
3913
|
-
const imageShowTimerRef = (0,
|
|
3914
|
-
const editStylesRef = (0,
|
|
3915
|
-
const activateRef = (0,
|
|
4597
|
+
const [fetchState, setFetchState] = (0, import_react3.useState)("idle");
|
|
4598
|
+
const autoSaveTimers = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
|
4599
|
+
const activeElRef = (0, import_react3.useRef)(null);
|
|
4600
|
+
const originalContentRef = (0, import_react3.useRef)(null);
|
|
4601
|
+
const activeStateElRef = (0, import_react3.useRef)(null);
|
|
4602
|
+
const parentScrollRef = (0, import_react3.useRef)(null);
|
|
4603
|
+
const toolbarElRef = (0, import_react3.useRef)(null);
|
|
4604
|
+
const glowElRef = (0, import_react3.useRef)(null);
|
|
4605
|
+
const hoveredImageRef = (0, import_react3.useRef)(null);
|
|
4606
|
+
const hoveredImageHasTextOverlapRef = (0, import_react3.useRef)(false);
|
|
4607
|
+
const hoveredGapRef = (0, import_react3.useRef)(null);
|
|
4608
|
+
const imageUnhoverTimerRef = (0, import_react3.useRef)(null);
|
|
4609
|
+
const imageShowTimerRef = (0, import_react3.useRef)(null);
|
|
4610
|
+
const editStylesRef = (0, import_react3.useRef)(null);
|
|
4611
|
+
const activateRef = (0, import_react3.useRef)(() => {
|
|
3916
4612
|
});
|
|
3917
|
-
const deactivateRef = (0,
|
|
4613
|
+
const deactivateRef = (0, import_react3.useRef)(() => {
|
|
3918
4614
|
});
|
|
3919
|
-
const refreshActiveCommandsRef = (0,
|
|
4615
|
+
const refreshActiveCommandsRef = (0, import_react3.useRef)(() => {
|
|
3920
4616
|
});
|
|
3921
|
-
const postToParentRef = (0,
|
|
4617
|
+
const postToParentRef = (0, import_react3.useRef)(postToParent);
|
|
3922
4618
|
postToParentRef.current = postToParent;
|
|
3923
|
-
const [toolbarRect, setToolbarRect] = (0,
|
|
3924
|
-
const [toggleState, setToggleState] = (0,
|
|
3925
|
-
const [maxBadge, setMaxBadge] = (0,
|
|
3926
|
-
const [activeCommands, setActiveCommands] = (0,
|
|
3927
|
-
const [sectionGap, setSectionGap] = (0,
|
|
3928
|
-
(0,
|
|
4619
|
+
const [toolbarRect, setToolbarRect] = (0, import_react3.useState)(null);
|
|
4620
|
+
const [toggleState, setToggleState] = (0, import_react3.useState)(null);
|
|
4621
|
+
const [maxBadge, setMaxBadge] = (0, import_react3.useState)(null);
|
|
4622
|
+
const [activeCommands, setActiveCommands] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
|
|
4623
|
+
const [sectionGap, setSectionGap] = (0, import_react3.useState)(null);
|
|
4624
|
+
(0, import_react3.useEffect)(() => {
|
|
3929
4625
|
const update = () => {
|
|
3930
4626
|
const el = activeElRef.current;
|
|
3931
4627
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -3949,10 +4645,10 @@ function OhhwellsBridge() {
|
|
|
3949
4645
|
vvp.removeEventListener("resize", update);
|
|
3950
4646
|
};
|
|
3951
4647
|
}, []);
|
|
3952
|
-
const refreshStateRules = (0,
|
|
4648
|
+
const refreshStateRules = (0, import_react3.useCallback)(() => {
|
|
3953
4649
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
3954
4650
|
}, []);
|
|
3955
|
-
const deactivate = (0,
|
|
4651
|
+
const deactivate = (0, import_react3.useCallback)(() => {
|
|
3956
4652
|
const el = activeElRef.current;
|
|
3957
4653
|
if (!el) return;
|
|
3958
4654
|
const key = el.dataset.ohwKey;
|
|
@@ -3977,7 +4673,7 @@ function OhhwellsBridge() {
|
|
|
3977
4673
|
setActiveCommands(/* @__PURE__ */ new Set());
|
|
3978
4674
|
postToParent({ type: "ow:exit-edit" });
|
|
3979
4675
|
}, [postToParent]);
|
|
3980
|
-
const activate = (0,
|
|
4676
|
+
const activate = (0, import_react3.useCallback)((el) => {
|
|
3981
4677
|
if (activeElRef.current === el) return;
|
|
3982
4678
|
deactivate();
|
|
3983
4679
|
if (hoveredImageRef.current) {
|
|
@@ -3995,14 +4691,27 @@ function OhhwellsBridge() {
|
|
|
3995
4691
|
}, [deactivate, postToParent]);
|
|
3996
4692
|
activateRef.current = activate;
|
|
3997
4693
|
deactivateRef.current = deactivate;
|
|
3998
|
-
(0,
|
|
4694
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
3999
4695
|
if (!subdomain || isEditMode) {
|
|
4000
4696
|
setFetchState("done");
|
|
4001
4697
|
return;
|
|
4002
4698
|
}
|
|
4003
4699
|
const applyContent = (content) => {
|
|
4004
4700
|
const imageLoads = [];
|
|
4701
|
+
document.querySelectorAll("[data-ohw-section-container]").forEach((el) => el.remove());
|
|
4702
|
+
if (content["__ohw_sections"]) {
|
|
4703
|
+
try {
|
|
4704
|
+
const entries = JSON.parse(content["__ohw_sections"]);
|
|
4705
|
+
const currentPath = window.location.pathname;
|
|
4706
|
+
entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
|
|
4707
|
+
if (pagePath && pagePath !== currentPath) return;
|
|
4708
|
+
if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
|
|
4709
|
+
});
|
|
4710
|
+
} catch {
|
|
4711
|
+
}
|
|
4712
|
+
}
|
|
4005
4713
|
for (const [key, val] of Object.entries(content)) {
|
|
4714
|
+
if (key === "__ohw_sections") continue;
|
|
4006
4715
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4007
4716
|
if (el.dataset.ohwEditable === "image") {
|
|
4008
4717
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4045,12 +4754,24 @@ function OhhwellsBridge() {
|
|
|
4045
4754
|
cancelled = true;
|
|
4046
4755
|
};
|
|
4047
4756
|
}, [subdomain, isEditMode, pathname]);
|
|
4048
|
-
(0,
|
|
4757
|
+
(0, import_react3.useEffect)(() => {
|
|
4049
4758
|
if (!subdomain || isEditMode) return;
|
|
4050
4759
|
const applyFromCache = () => {
|
|
4051
4760
|
const content = contentCache.get(subdomain);
|
|
4052
4761
|
if (!content) return;
|
|
4762
|
+
if (content["__ohw_sections"]) {
|
|
4763
|
+
try {
|
|
4764
|
+
const entries = JSON.parse(content["__ohw_sections"]);
|
|
4765
|
+
const currentPath = window.location.pathname;
|
|
4766
|
+
entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
|
|
4767
|
+
if (pagePath && pagePath !== currentPath) return;
|
|
4768
|
+
if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
|
|
4769
|
+
});
|
|
4770
|
+
} catch {
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4053
4773
|
for (const [key, val] of Object.entries(content)) {
|
|
4774
|
+
if (key === "__ohw_sections") continue;
|
|
4054
4775
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4055
4776
|
if (el.dataset.ohwEditable === "image") {
|
|
4056
4777
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4069,16 +4790,16 @@ function OhhwellsBridge() {
|
|
|
4069
4790
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
4070
4791
|
return () => observer.disconnect();
|
|
4071
4792
|
}, [subdomain, isEditMode]);
|
|
4072
|
-
(0,
|
|
4793
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
4073
4794
|
const el = document.getElementById("ohw-loader");
|
|
4074
4795
|
if (!el) return;
|
|
4075
4796
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
4076
4797
|
el.style.display = visible ? "flex" : "none";
|
|
4077
4798
|
}, [subdomain, fetchState]);
|
|
4078
|
-
(0,
|
|
4799
|
+
(0, import_react3.useEffect)(() => {
|
|
4079
4800
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
4080
4801
|
}, [pathname, postToParent]);
|
|
4081
|
-
(0,
|
|
4802
|
+
(0, import_react3.useEffect)(() => {
|
|
4082
4803
|
if (!isEditMode) return;
|
|
4083
4804
|
const measure = () => {
|
|
4084
4805
|
const h = document.body.scrollHeight;
|
|
@@ -4102,7 +4823,7 @@ function OhhwellsBridge() {
|
|
|
4102
4823
|
window.removeEventListener("resize", handleResize);
|
|
4103
4824
|
};
|
|
4104
4825
|
}, [pathname, isEditMode, postToParent]);
|
|
4105
|
-
(0,
|
|
4826
|
+
(0, import_react3.useEffect)(() => {
|
|
4106
4827
|
if (!subdomainFromQuery || isEditMode) return;
|
|
4107
4828
|
const handleClick = (e) => {
|
|
4108
4829
|
const anchor = e.target.closest("a");
|
|
@@ -4118,7 +4839,7 @@ function OhhwellsBridge() {
|
|
|
4118
4839
|
document.addEventListener("click", handleClick, true);
|
|
4119
4840
|
return () => document.removeEventListener("click", handleClick, true);
|
|
4120
4841
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
4121
|
-
(0,
|
|
4842
|
+
(0, import_react3.useEffect)(() => {
|
|
4122
4843
|
if (!isEditMode) {
|
|
4123
4844
|
editStylesRef.current?.base.remove();
|
|
4124
4845
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -4378,6 +5099,15 @@ function OhhwellsBridge() {
|
|
|
4378
5099
|
textEditable.setAttribute("data-ohw-hovered", "");
|
|
4379
5100
|
return;
|
|
4380
5101
|
}
|
|
5102
|
+
if (hoveredGapRef.current) {
|
|
5103
|
+
if (hoveredImageRef.current) {
|
|
5104
|
+
hoveredImageRef.current = null;
|
|
5105
|
+
resumeAnimTracks();
|
|
5106
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
5107
|
+
}
|
|
5108
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5109
|
+
return;
|
|
5110
|
+
}
|
|
4381
5111
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4382
5112
|
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
4383
5113
|
hoveredImageRef.current = imgEl;
|
|
@@ -4466,17 +5196,17 @@ function OhhwellsBridge() {
|
|
|
4466
5196
|
};
|
|
4467
5197
|
const handleMouseMove = (e) => {
|
|
4468
5198
|
const { clientX, clientY } = e;
|
|
5199
|
+
probeSectionGapAt(clientX, clientY);
|
|
4469
5200
|
probeImageAt(clientX, clientY);
|
|
4470
5201
|
probeHoverCardsAt(clientX, clientY);
|
|
4471
|
-
probeSectionGapAt(clientX, clientY);
|
|
4472
5202
|
};
|
|
4473
5203
|
const handlePointerSync = (e) => {
|
|
4474
5204
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
4475
5205
|
const { clientX, clientY } = e.data;
|
|
4476
5206
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
5207
|
+
probeSectionGapAt(clientX, clientY);
|
|
4477
5208
|
probeImageAt(clientX, clientY);
|
|
4478
5209
|
probeHoverCardsAt(clientX, clientY);
|
|
4479
|
-
probeSectionGapAt(clientX, clientY);
|
|
4480
5210
|
};
|
|
4481
5211
|
const handleDragOver = (e) => {
|
|
4482
5212
|
e.preventDefault();
|
|
@@ -4633,6 +5363,18 @@ function OhhwellsBridge() {
|
|
|
4633
5363
|
const content = e.data.content;
|
|
4634
5364
|
if (!content) return;
|
|
4635
5365
|
for (const [key, val] of Object.entries(content)) {
|
|
5366
|
+
if (key === "__ohw_sections") {
|
|
5367
|
+
try {
|
|
5368
|
+
const entries = JSON.parse(val);
|
|
5369
|
+
const tracker = getSectionsTracker();
|
|
5370
|
+
tracker.textContent = val;
|
|
5371
|
+
entries.forEach(({ type, insertAfter, scheduleId }) => {
|
|
5372
|
+
if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
|
|
5373
|
+
});
|
|
5374
|
+
} catch {
|
|
5375
|
+
}
|
|
5376
|
+
continue;
|
|
5377
|
+
}
|
|
4636
5378
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4637
5379
|
if (el.dataset.ohwEditable === "image") {
|
|
4638
5380
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4687,7 +5429,71 @@ function OhhwellsBridge() {
|
|
|
4687
5429
|
};
|
|
4688
5430
|
const handleSave = (e) => {
|
|
4689
5431
|
if (e.data?.type !== "ow:save") return;
|
|
4690
|
-
|
|
5432
|
+
const nodes = collectEditableNodes();
|
|
5433
|
+
const tracker = document.querySelector("[data-ohw-sections-tracker]");
|
|
5434
|
+
if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
|
|
5435
|
+
postToParentRef.current({ type: "ow:save-result", nodes });
|
|
5436
|
+
};
|
|
5437
|
+
const handleInsertSection = (e) => {
|
|
5438
|
+
if (e.data?.type !== "ow:insert-section") return;
|
|
5439
|
+
const { widgetType, insertAfter } = e.data;
|
|
5440
|
+
if (widgetType !== "scheduling") return;
|
|
5441
|
+
const inserted = mountSchedulingWidget(insertAfter, true);
|
|
5442
|
+
if (inserted) {
|
|
5443
|
+
const tracker = getSectionsTracker();
|
|
5444
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
|
|
5445
|
+
const h = document.documentElement.scrollHeight;
|
|
5446
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
5447
|
+
}
|
|
5448
|
+
};
|
|
5449
|
+
const handleSwitchSchedule = (e) => {
|
|
5450
|
+
if (e.data?.type !== "ow:switch-schedule") return;
|
|
5451
|
+
const scheduleId = e.data.scheduleId;
|
|
5452
|
+
if (!scheduleId) return;
|
|
5453
|
+
const tracker = getSectionsTracker();
|
|
5454
|
+
let sections = [];
|
|
5455
|
+
try {
|
|
5456
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5457
|
+
} catch {
|
|
5458
|
+
}
|
|
5459
|
+
const currentPath = window.location.pathname;
|
|
5460
|
+
const updated = sections.map(
|
|
5461
|
+
(s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId } : s
|
|
5462
|
+
);
|
|
5463
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5464
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
5465
|
+
};
|
|
5466
|
+
const handleClearSchedulingWidget = (e) => {
|
|
5467
|
+
if (e.data?.type !== "ow:clear-scheduling-widget") return;
|
|
5468
|
+
const tracker = getSectionsTracker();
|
|
5469
|
+
let sections = [];
|
|
5470
|
+
try {
|
|
5471
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5472
|
+
} catch {
|
|
5473
|
+
}
|
|
5474
|
+
const currentPath = window.location.pathname;
|
|
5475
|
+
const updated = sections.map(
|
|
5476
|
+
(s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId: null } : s
|
|
5477
|
+
);
|
|
5478
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5479
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
5480
|
+
};
|
|
5481
|
+
const handleRemoveSchedulingSection = (e) => {
|
|
5482
|
+
if (e.data?.type !== "ow:remove-scheduling-section") return;
|
|
5483
|
+
const els = document.querySelectorAll('[data-ohw-section="scheduling"]');
|
|
5484
|
+
els.forEach((el) => el.parentElement?.removeChild(el));
|
|
5485
|
+
const tracker = getSectionsTracker();
|
|
5486
|
+
let sections = [];
|
|
5487
|
+
try {
|
|
5488
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5489
|
+
} catch {
|
|
5490
|
+
}
|
|
5491
|
+
const currentPath = window.location.pathname;
|
|
5492
|
+
const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
|
|
5493
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5494
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
5495
|
+
const h = document.documentElement.scrollHeight;
|
|
5496
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
4691
5497
|
};
|
|
4692
5498
|
const handleSelectionChange = () => {
|
|
4693
5499
|
if (!activeElRef.current) return;
|
|
@@ -4792,6 +5598,10 @@ function OhhwellsBridge() {
|
|
|
4792
5598
|
deactivateRef.current();
|
|
4793
5599
|
};
|
|
4794
5600
|
window.addEventListener("message", handleSave);
|
|
5601
|
+
window.addEventListener("message", handleInsertSection);
|
|
5602
|
+
window.addEventListener("message", handleSwitchSchedule);
|
|
5603
|
+
window.addEventListener("message", handleClearSchedulingWidget);
|
|
5604
|
+
window.addEventListener("message", handleRemoveSchedulingSection);
|
|
4795
5605
|
window.addEventListener("message", handleImageUrl);
|
|
4796
5606
|
window.addEventListener("message", handleAnimLock);
|
|
4797
5607
|
window.addEventListener("message", handleCanvasHeight);
|
|
@@ -4826,6 +5636,10 @@ function OhhwellsBridge() {
|
|
|
4826
5636
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
4827
5637
|
window.removeEventListener("scroll", handleScroll, true);
|
|
4828
5638
|
window.removeEventListener("message", handleSave);
|
|
5639
|
+
window.removeEventListener("message", handleInsertSection);
|
|
5640
|
+
window.removeEventListener("message", handleSwitchSchedule);
|
|
5641
|
+
window.removeEventListener("message", handleClearSchedulingWidget);
|
|
5642
|
+
window.removeEventListener("message", handleRemoveSchedulingSection);
|
|
4829
5643
|
window.removeEventListener("message", handleImageUrl);
|
|
4830
5644
|
window.removeEventListener("message", handleAnimLock);
|
|
4831
5645
|
window.removeEventListener("message", handleCanvasHeight);
|
|
@@ -4840,7 +5654,7 @@ function OhhwellsBridge() {
|
|
|
4840
5654
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
4841
5655
|
};
|
|
4842
5656
|
}, [isEditMode, refreshStateRules]);
|
|
4843
|
-
(0,
|
|
5657
|
+
(0, import_react3.useEffect)(() => {
|
|
4844
5658
|
if (!isEditMode) return;
|
|
4845
5659
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
4846
5660
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -4862,13 +5676,13 @@ function OhhwellsBridge() {
|
|
|
4862
5676
|
clearTimeout(timer);
|
|
4863
5677
|
};
|
|
4864
5678
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
4865
|
-
const handleCommand = (0,
|
|
5679
|
+
const handleCommand = (0, import_react3.useCallback)((cmd) => {
|
|
4866
5680
|
document.execCommand(cmd, false);
|
|
4867
5681
|
activeElRef.current?.focus();
|
|
4868
5682
|
if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
|
|
4869
5683
|
refreshActiveCommandsRef.current();
|
|
4870
5684
|
}, []);
|
|
4871
|
-
const handleStateChange = (0,
|
|
5685
|
+
const handleStateChange = (0, import_react3.useCallback)((state) => {
|
|
4872
5686
|
if (!activeStateElRef.current) return;
|
|
4873
5687
|
const el = activeStateElRef.current;
|
|
4874
5688
|
if (state === "Default") {
|
|
@@ -4882,12 +5696,12 @@ function OhhwellsBridge() {
|
|
|
4882
5696
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
4883
5697
|
}, [deactivate]);
|
|
4884
5698
|
return bridgeRoot ? (0, import_react_dom.createPortal)(
|
|
4885
|
-
/* @__PURE__ */ (0,
|
|
4886
|
-
toolbarRect && /* @__PURE__ */ (0,
|
|
4887
|
-
/* @__PURE__ */ (0,
|
|
4888
|
-
/* @__PURE__ */ (0,
|
|
5699
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
5700
|
+
toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
5701
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
5702
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
|
|
4889
5703
|
] }),
|
|
4890
|
-
maxBadge && /* @__PURE__ */ (0,
|
|
5704
|
+
maxBadge && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
4891
5705
|
"div",
|
|
4892
5706
|
{
|
|
4893
5707
|
"data-ohw-max-badge": "",
|
|
@@ -4913,7 +5727,7 @@ function OhhwellsBridge() {
|
|
|
4913
5727
|
]
|
|
4914
5728
|
}
|
|
4915
5729
|
),
|
|
4916
|
-
toggleState && /* @__PURE__ */ (0,
|
|
5730
|
+
toggleState && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4917
5731
|
StateToggle,
|
|
4918
5732
|
{
|
|
4919
5733
|
rect: toggleState.rect,
|
|
@@ -4922,15 +5736,15 @@ function OhhwellsBridge() {
|
|
|
4922
5736
|
onStateChange: handleStateChange
|
|
4923
5737
|
}
|
|
4924
5738
|
),
|
|
4925
|
-
sectionGap && /* @__PURE__ */ (0,
|
|
5739
|
+
sectionGap && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
4926
5740
|
"div",
|
|
4927
5741
|
{
|
|
4928
5742
|
"data-ohw-section-insert-line": "",
|
|
4929
5743
|
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
4930
5744
|
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
4931
5745
|
children: [
|
|
4932
|
-
/* @__PURE__ */ (0,
|
|
4933
|
-
/* @__PURE__ */ (0,
|
|
5746
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
5747
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4934
5748
|
Badge,
|
|
4935
5749
|
{
|
|
4936
5750
|
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",
|
|
@@ -4943,7 +5757,7 @@ function OhhwellsBridge() {
|
|
|
4943
5757
|
children: "Add Section"
|
|
4944
5758
|
}
|
|
4945
5759
|
),
|
|
4946
|
-
/* @__PURE__ */ (0,
|
|
5760
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
4947
5761
|
]
|
|
4948
5762
|
}
|
|
4949
5763
|
)
|
|
@@ -4954,6 +5768,7 @@ function OhhwellsBridge() {
|
|
|
4954
5768
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4955
5769
|
0 && (module.exports = {
|
|
4956
5770
|
OhhwellsBridge,
|
|
5771
|
+
SchedulingWidget,
|
|
4957
5772
|
Toggle,
|
|
4958
5773
|
ToggleGroup,
|
|
4959
5774
|
ToggleGroupItem,
|