@ohhwells/bridge 0.1.29 → 0.1.30
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 +362 -304
- package/dist/index.cjs +1362 -214
- 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 +1338 -191
- package/dist/index.js.map +1 -1
- package/dist/styles.css +453 -12
- package/package.json +48 -47
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
LinkEditorPanel: () => LinkEditorPanel,
|
|
35
35
|
LinkPopover: () => LinkPopover,
|
|
36
36
|
OhhwellsBridge: () => OhhwellsBridge,
|
|
37
|
+
SchedulingWidget: () => SchedulingWidget,
|
|
37
38
|
Toggle: () => Toggle,
|
|
38
39
|
ToggleGroup: () => ToggleGroup,
|
|
39
40
|
ToggleGroupItem: () => ToggleGroupItem,
|
|
@@ -48,10 +49,762 @@ __export(index_exports, {
|
|
|
48
49
|
module.exports = __toCommonJS(index_exports);
|
|
49
50
|
|
|
50
51
|
// src/OhhwellsBridge.tsx
|
|
51
|
-
var
|
|
52
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
53
|
+
var import_client = require("react-dom/client");
|
|
54
|
+
var import_react_dom = require("react-dom");
|
|
55
|
+
|
|
56
|
+
// src/ui/SchedulingWidget.tsx
|
|
57
|
+
var import_react2 = require("react");
|
|
58
|
+
|
|
59
|
+
// src/ui/EmailCaptureModal.tsx
|
|
60
|
+
var import_react = require("react");
|
|
61
|
+
var import_radix_ui = require("radix-ui");
|
|
62
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
63
|
+
function Spinner() {
|
|
64
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
65
|
+
"svg",
|
|
66
|
+
{
|
|
67
|
+
width: "32",
|
|
68
|
+
height: "32",
|
|
69
|
+
viewBox: "0 0 32 32",
|
|
70
|
+
fill: "none",
|
|
71
|
+
style: { animation: "ohw-spin 0.75s linear infinite" },
|
|
72
|
+
children: [
|
|
73
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
|
|
74
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
|
|
75
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
76
|
+
"path",
|
|
77
|
+
{
|
|
78
|
+
d: "M16 4a12 12 0 0 1 12 12",
|
|
79
|
+
stroke: "#3b82f6",
|
|
80
|
+
strokeWidth: "3",
|
|
81
|
+
strokeLinecap: "round"
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
89
|
+
const [email, setEmail] = (0, import_react.useState)("");
|
|
90
|
+
const [submittedEmail, setSubmittedEmail] = (0, import_react.useState)("");
|
|
91
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
92
|
+
const [success, setSuccess] = (0, import_react.useState)(false);
|
|
93
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
94
|
+
const isBook = title === "Confirm your spot";
|
|
95
|
+
const handleSubmit = async () => {
|
|
96
|
+
if (!email.trim()) return;
|
|
97
|
+
setLoading(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
try {
|
|
100
|
+
await onSubmit(email.trim());
|
|
101
|
+
setSubmittedEmail(email.trim());
|
|
102
|
+
setSuccess(true);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
105
|
+
} finally {
|
|
106
|
+
setLoading(false);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const handleKeyDown = (e) => {
|
|
110
|
+
if (e.key === "Enter" && !loading) handleSubmit();
|
|
111
|
+
};
|
|
112
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_radix_ui.Dialog.Root, { open: true, onOpenChange: (open) => {
|
|
113
|
+
if (!open) onClose();
|
|
114
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_radix_ui.Dialog.Portal, { children: [
|
|
115
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
116
|
+
import_radix_ui.Dialog.Overlay,
|
|
117
|
+
{
|
|
118
|
+
className: "fixed inset-0 z-50",
|
|
119
|
+
style: { background: "rgba(0,0,0,0.45)" }
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
123
|
+
import_radix_ui.Dialog.Content,
|
|
124
|
+
{
|
|
125
|
+
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",
|
|
126
|
+
style: { maxWidth: 400 },
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3 p-6", children: [
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
130
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
131
|
+
import_radix_ui.Dialog.Title,
|
|
132
|
+
{
|
|
133
|
+
className: "font-body text-xl font-bold m-0 leading-tight",
|
|
134
|
+
style: { color: "var(--color-dark,#200C02)" },
|
|
135
|
+
children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
139
|
+
import_radix_ui.Dialog.Description,
|
|
140
|
+
{
|
|
141
|
+
className: "font-body text-sm m-0",
|
|
142
|
+
style: { color: "#6B7280" },
|
|
143
|
+
children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
] }),
|
|
147
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
148
|
+
import_radix_ui.Dialog.Close,
|
|
149
|
+
{
|
|
150
|
+
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",
|
|
151
|
+
style: { color: "#6B7280" },
|
|
152
|
+
"aria-label": "Close",
|
|
153
|
+
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: [
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
156
|
+
] })
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
] }),
|
|
160
|
+
!success && (loading ? (
|
|
161
|
+
/* Loading state — full body replaced */
|
|
162
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
164
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
|
|
165
|
+
] })
|
|
166
|
+
) : (
|
|
167
|
+
/* Email form */
|
|
168
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-7 pt-2 pb-6", children: [
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1.5 mb-8", children: [
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
172
|
+
"input",
|
|
173
|
+
{
|
|
174
|
+
type: "email",
|
|
175
|
+
value: email,
|
|
176
|
+
onChange: (e) => setEmail(e.target.value),
|
|
177
|
+
onKeyDown: handleKeyDown,
|
|
178
|
+
placeholder: "hello@gmail.com",
|
|
179
|
+
autoFocus: true,
|
|
180
|
+
className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
|
|
181
|
+
style: {
|
|
182
|
+
borderColor: "#E7E5E4",
|
|
183
|
+
background: "#fff",
|
|
184
|
+
color: "var(--color-dark,#200C02)"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
),
|
|
188
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-xs text-red-500 m-0", children: error })
|
|
189
|
+
] }),
|
|
190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
191
|
+
"button",
|
|
192
|
+
{
|
|
193
|
+
onClick: handleSubmit,
|
|
194
|
+
disabled: !email.trim(),
|
|
195
|
+
className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
|
|
196
|
+
style: {
|
|
197
|
+
background: "var(--color-primary,#3D312B)",
|
|
198
|
+
color: "var(--color-light,#FEFFF0)"
|
|
199
|
+
},
|
|
200
|
+
children: "Send"
|
|
201
|
+
}
|
|
202
|
+
) })
|
|
203
|
+
] })
|
|
204
|
+
))
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
] }) });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/ui/SchedulingWidget.tsx
|
|
212
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
213
|
+
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
214
|
+
var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
215
|
+
function classRunsOnDate(cls, date, type) {
|
|
216
|
+
if (type === "FIXED") {
|
|
217
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
218
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
219
|
+
return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
|
|
220
|
+
}
|
|
221
|
+
return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
|
|
222
|
+
}
|
|
223
|
+
function formatClassTime(cls) {
|
|
224
|
+
let h = parseInt(cls.startTime.hour, 10);
|
|
225
|
+
const m = parseInt(cls.startTime.minutes, 10);
|
|
226
|
+
if (cls.startTime.time === "PM" && h !== 12) h += 12;
|
|
227
|
+
if (cls.startTime.time === "AM" && h === 12) h = 0;
|
|
228
|
+
const endTotal = h * 60 + m + cls.duration;
|
|
229
|
+
const eh = Math.floor(endTotal / 60) % 24;
|
|
230
|
+
const em = endTotal % 60;
|
|
231
|
+
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
232
|
+
}
|
|
233
|
+
function getBookingsOnDate(cls, date) {
|
|
234
|
+
if (!cls.bookings?.length) return 0;
|
|
235
|
+
const ds = date.toISOString().split("T")[0];
|
|
236
|
+
return cls.bookings.filter((b) => {
|
|
237
|
+
try {
|
|
238
|
+
return new Date(b.classDate).toISOString().split("T")[0] === ds;
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}).length;
|
|
243
|
+
}
|
|
244
|
+
function buildTimezoneLabel(tz) {
|
|
245
|
+
try {
|
|
246
|
+
const now = /* @__PURE__ */ new Date();
|
|
247
|
+
const timeStr = new Intl.DateTimeFormat("en-US", {
|
|
248
|
+
hour: "2-digit",
|
|
249
|
+
minute: "2-digit",
|
|
250
|
+
hour12: false,
|
|
251
|
+
timeZone: tz
|
|
252
|
+
}).format(now);
|
|
253
|
+
const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
254
|
+
const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
|
|
255
|
+
return `${timeStr} (${offsetPart}) ${city} time`;
|
|
256
|
+
} catch {
|
|
257
|
+
return tz;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function EmptyState({ inEditor, insertAfter }) {
|
|
261
|
+
const handleAddSchedule = () => {
|
|
262
|
+
if (inEditor) {
|
|
263
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
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: [
|
|
267
|
+
/* @__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)(
|
|
268
|
+
"svg",
|
|
269
|
+
{
|
|
270
|
+
width: "20",
|
|
271
|
+
height: "20",
|
|
272
|
+
viewBox: "0 0 24 24",
|
|
273
|
+
fill: "none",
|
|
274
|
+
stroke: "var(--color-accent, #A89B83)",
|
|
275
|
+
strokeWidth: "1.5",
|
|
276
|
+
strokeLinecap: "round",
|
|
277
|
+
strokeLinejoin: "round",
|
|
278
|
+
children: [
|
|
279
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
|
|
280
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
|
|
281
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
|
|
282
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
|
|
283
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
|
|
284
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
) }),
|
|
288
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
|
|
289
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
|
|
290
|
+
/* @__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." })
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
293
|
+
"button",
|
|
294
|
+
{
|
|
295
|
+
onClick: handleAddSchedule,
|
|
296
|
+
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",
|
|
297
|
+
children: "Add Schedule"
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
] });
|
|
301
|
+
}
|
|
302
|
+
function LoadingSkeleton() {
|
|
303
|
+
const shimmer = {
|
|
304
|
+
background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
|
|
305
|
+
backgroundSize: "200% 100%",
|
|
306
|
+
animation: "ohw-shimmer 1.5s infinite"
|
|
307
|
+
};
|
|
308
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
|
|
309
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
|
|
310
|
+
/* @__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: [
|
|
311
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
|
|
312
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
|
|
313
|
+
] }, i)) }),
|
|
314
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
315
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:hidden", children: [
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0", children: [
|
|
317
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
|
|
318
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
|
|
319
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
|
|
320
|
+
] }),
|
|
321
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2", children: [
|
|
322
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
|
|
323
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
|
|
324
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
|
|
325
|
+
] })
|
|
326
|
+
] }),
|
|
327
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
|
|
329
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
|
|
331
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
|
|
332
|
+
] }),
|
|
333
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
|
|
334
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
|
|
335
|
+
] }),
|
|
336
|
+
i < 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
|
|
337
|
+
] }, i))
|
|
338
|
+
] });
|
|
339
|
+
}
|
|
340
|
+
function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
|
|
341
|
+
const todayMs = (0, import_react2.useMemo)(() => {
|
|
342
|
+
const d = /* @__PURE__ */ new Date();
|
|
343
|
+
d.setHours(0, 0, 0, 0);
|
|
344
|
+
return d.getTime();
|
|
345
|
+
}, []);
|
|
346
|
+
const scrollRef = (0, import_react2.useRef)(null);
|
|
347
|
+
const [canScrollLeft, setCanScrollLeft] = (0, import_react2.useState)(false);
|
|
348
|
+
const [canScrollRight, setCanScrollRight] = (0, import_react2.useState)(false);
|
|
349
|
+
const updateScrollState = () => {
|
|
350
|
+
const el = scrollRef.current;
|
|
351
|
+
if (!el) return;
|
|
352
|
+
setCanScrollLeft(el.scrollLeft > 0);
|
|
353
|
+
setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
354
|
+
};
|
|
355
|
+
(0, import_react2.useEffect)(() => {
|
|
356
|
+
const el = scrollRef.current;
|
|
357
|
+
if (!el) return;
|
|
358
|
+
updateScrollState();
|
|
359
|
+
el.addEventListener("scroll", updateScrollState);
|
|
360
|
+
const ro = new ResizeObserver(updateScrollState);
|
|
361
|
+
ro.observe(el);
|
|
362
|
+
return () => {
|
|
363
|
+
el.removeEventListener("scroll", updateScrollState);
|
|
364
|
+
ro.disconnect();
|
|
365
|
+
};
|
|
366
|
+
}, [dates]);
|
|
367
|
+
const scroll = (dir) => {
|
|
368
|
+
scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
|
|
369
|
+
};
|
|
370
|
+
const isDragging = (0, import_react2.useRef)(false);
|
|
371
|
+
const dragStartX = (0, import_react2.useRef)(0);
|
|
372
|
+
const dragStartScrollLeft = (0, import_react2.useRef)(0);
|
|
373
|
+
const onDragStart = (e) => {
|
|
374
|
+
if (!scrollRef.current) return;
|
|
375
|
+
isDragging.current = true;
|
|
376
|
+
dragStartX.current = e.clientX;
|
|
377
|
+
dragStartScrollLeft.current = scrollRef.current.scrollLeft;
|
|
378
|
+
scrollRef.current.style.cursor = "grabbing";
|
|
379
|
+
scrollRef.current.style.userSelect = "none";
|
|
380
|
+
};
|
|
381
|
+
const onDragMove = (e) => {
|
|
382
|
+
if (!isDragging.current || !scrollRef.current) return;
|
|
383
|
+
const delta = e.clientX - dragStartX.current;
|
|
384
|
+
scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
|
|
385
|
+
};
|
|
386
|
+
const onDragEnd = () => {
|
|
387
|
+
if (!scrollRef.current) return;
|
|
388
|
+
isDragging.current = false;
|
|
389
|
+
scrollRef.current.style.cursor = "grab";
|
|
390
|
+
scrollRef.current.style.userSelect = "";
|
|
391
|
+
};
|
|
392
|
+
const datesWithClasses = (0, import_react2.useMemo)(
|
|
393
|
+
() => new Set(dates.map(
|
|
394
|
+
(d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
|
|
395
|
+
).filter((i) => i >= 0)),
|
|
396
|
+
[dates, schedule]
|
|
397
|
+
);
|
|
398
|
+
const selectedDate = dates[selectedIdx];
|
|
399
|
+
const selectedClasses = (0, import_react2.useMemo)(
|
|
400
|
+
() => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
|
|
401
|
+
[schedule, selectedDate]
|
|
402
|
+
);
|
|
403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
|
|
404
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full", children: [
|
|
405
|
+
canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
406
|
+
"button",
|
|
407
|
+
{
|
|
408
|
+
onClick: () => scroll("left"),
|
|
409
|
+
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",
|
|
410
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
411
|
+
"aria-label": "Scroll left",
|
|
412
|
+
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" }) })
|
|
413
|
+
}
|
|
414
|
+
),
|
|
415
|
+
canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
416
|
+
"button",
|
|
417
|
+
{
|
|
418
|
+
onClick: () => scroll("right"),
|
|
419
|
+
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",
|
|
420
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
421
|
+
"aria-label": "Scroll right",
|
|
422
|
+
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" }) })
|
|
423
|
+
}
|
|
424
|
+
),
|
|
425
|
+
canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
426
|
+
"div",
|
|
427
|
+
{
|
|
428
|
+
className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
429
|
+
style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
|
|
430
|
+
}
|
|
431
|
+
),
|
|
432
|
+
canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
433
|
+
"div",
|
|
434
|
+
{
|
|
435
|
+
className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
436
|
+
style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
|
|
437
|
+
}
|
|
438
|
+
),
|
|
439
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
440
|
+
"div",
|
|
441
|
+
{
|
|
442
|
+
ref: scrollRef,
|
|
443
|
+
className: "overflow-x-auto w-full",
|
|
444
|
+
style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
|
|
445
|
+
onMouseDown: onDragStart,
|
|
446
|
+
onMouseMove: onDragMove,
|
|
447
|
+
onMouseUp: onDragEnd,
|
|
448
|
+
onMouseLeave: onDragEnd,
|
|
449
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
|
|
450
|
+
const isToday = date.getTime() === todayMs;
|
|
451
|
+
const isSelected = i === selectedIdx;
|
|
452
|
+
const clickable = datesWithClasses.has(i);
|
|
453
|
+
const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
|
|
454
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
455
|
+
"div",
|
|
456
|
+
{
|
|
457
|
+
onClick: () => clickable && onSelectDate(i),
|
|
458
|
+
className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
|
|
459
|
+
children: [
|
|
460
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
|
|
461
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
462
|
+
"div",
|
|
463
|
+
{
|
|
464
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center",
|
|
465
|
+
style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
|
|
466
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
467
|
+
"span",
|
|
468
|
+
{
|
|
469
|
+
className: "font-body text-xl font-bold text-center tracking-display-tight",
|
|
470
|
+
style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
|
|
471
|
+
children: date.getDate()
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
}
|
|
475
|
+
)
|
|
476
|
+
]
|
|
477
|
+
},
|
|
478
|
+
i
|
|
479
|
+
);
|
|
480
|
+
}) })
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
] }),
|
|
484
|
+
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) => {
|
|
485
|
+
const booked = getBookingsOnDate(cls, selectedDate);
|
|
486
|
+
const available = cls.maxParticipants - booked;
|
|
487
|
+
const isFull = available <= 0;
|
|
488
|
+
const isPrivate = cls.maxParticipants === 1;
|
|
489
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
490
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
|
|
491
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 sm:w-[120px] sm:shrink-0", children: [
|
|
493
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }),
|
|
494
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
495
|
+
"span",
|
|
496
|
+
{
|
|
497
|
+
className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium w-fit",
|
|
498
|
+
style: isPrivate ? { background: "#DDFBE3", color: "#199130", border: "1px solid #199130", fontWeight: 500 } : { background: "#EEEDFF", color: "#5953FF", border: "1px solid #5953FF", fontWeight: 500 },
|
|
499
|
+
children: isPrivate ? "PRIVATE" : "GROUP"
|
|
500
|
+
}
|
|
501
|
+
)
|
|
502
|
+
] }),
|
|
503
|
+
/* @__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: [
|
|
504
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
505
|
+
available,
|
|
506
|
+
"/",
|
|
507
|
+
cls.maxParticipants
|
|
508
|
+
] }),
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
510
|
+
] }) })
|
|
511
|
+
] }),
|
|
512
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
|
|
513
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
|
|
514
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
515
|
+
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 })
|
|
516
|
+
] }),
|
|
517
|
+
/* @__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: [
|
|
518
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
519
|
+
available,
|
|
520
|
+
"/",
|
|
521
|
+
cls.maxParticipants
|
|
522
|
+
] }),
|
|
523
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
524
|
+
] }) }),
|
|
525
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
526
|
+
"button",
|
|
527
|
+
{
|
|
528
|
+
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",
|
|
529
|
+
style: isFull ? {
|
|
530
|
+
background: "transparent",
|
|
531
|
+
border: "1px solid var(--color-dark, #200C02)",
|
|
532
|
+
color: "var(--color-dark, #200C02)"
|
|
533
|
+
} : {
|
|
534
|
+
background: "var(--color-primary, #3D312B)",
|
|
535
|
+
border: "none",
|
|
536
|
+
color: "var(--color-light, #FEFFF0)"
|
|
537
|
+
},
|
|
538
|
+
onClick: () => {
|
|
539
|
+
if (!cls.id) return;
|
|
540
|
+
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
541
|
+
},
|
|
542
|
+
children: isFull ? "Join Waitlist" : "Book Now"
|
|
543
|
+
}
|
|
544
|
+
)
|
|
545
|
+
] })
|
|
546
|
+
] }),
|
|
547
|
+
i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
|
|
548
|
+
] }, cls.id ?? i);
|
|
549
|
+
}) })
|
|
550
|
+
] });
|
|
551
|
+
}
|
|
552
|
+
function CalendarFoldIcon() {
|
|
553
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
554
|
+
"svg",
|
|
555
|
+
{
|
|
556
|
+
width: "16",
|
|
557
|
+
height: "16",
|
|
558
|
+
viewBox: "0 0 24 24",
|
|
559
|
+
fill: "none",
|
|
560
|
+
stroke: "currentColor",
|
|
561
|
+
strokeWidth: "2",
|
|
562
|
+
strokeLinecap: "round",
|
|
563
|
+
strokeLinejoin: "round",
|
|
564
|
+
style: { flexShrink: 0 },
|
|
565
|
+
children: [
|
|
566
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2v4" }),
|
|
567
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 2v4" }),
|
|
568
|
+
/* @__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" }),
|
|
569
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 10h18" }),
|
|
570
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter: insertAfterProp }) {
|
|
576
|
+
const autoId = (0, import_react2.useId)();
|
|
577
|
+
const insertAfter = insertAfterProp ?? autoId;
|
|
578
|
+
const [schedule, setSchedule] = (0, import_react2.useState)(null);
|
|
579
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
580
|
+
const [inEditor, setInEditor] = (0, import_react2.useState)(false);
|
|
581
|
+
const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
|
|
582
|
+
const [modalState, setModalState] = (0, import_react2.useState)(null);
|
|
583
|
+
const switchScheduleIdRef = (0, import_react2.useRef)(null);
|
|
584
|
+
const dates = (0, import_react2.useMemo)(() => {
|
|
585
|
+
const today = /* @__PURE__ */ new Date();
|
|
586
|
+
today.setHours(0, 0, 0, 0);
|
|
587
|
+
return Array.from({ length: 14 }, (_, i) => {
|
|
588
|
+
const d = new Date(today);
|
|
589
|
+
d.setDate(today.getDate() + i);
|
|
590
|
+
return d;
|
|
591
|
+
});
|
|
592
|
+
}, []);
|
|
593
|
+
const [selectedIdx, setSelectedIdx] = (0, import_react2.useState)(0);
|
|
594
|
+
(0, import_react2.useEffect)(() => {
|
|
595
|
+
if (!schedule?.classes) return;
|
|
596
|
+
for (let i = 0; i < dates.length; i++) {
|
|
597
|
+
if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
|
|
598
|
+
setSelectedIdx(i);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}, [schedule, dates]);
|
|
603
|
+
(0, import_react2.useEffect)(() => {
|
|
604
|
+
if (typeof window === "undefined") return;
|
|
605
|
+
const isInEditor = window.self !== window.top;
|
|
606
|
+
setInEditor(isInEditor);
|
|
607
|
+
function loadWithId(effectiveId) {
|
|
608
|
+
if (isInEditor) {
|
|
609
|
+
const startEmpty = effectiveId === null;
|
|
610
|
+
if (startEmpty) setLoading(false);
|
|
611
|
+
let initialized = startEmpty;
|
|
612
|
+
const timer = !startEmpty ? setTimeout(() => {
|
|
613
|
+
if (!initialized) {
|
|
614
|
+
initialized = true;
|
|
615
|
+
setLoading(false);
|
|
616
|
+
}
|
|
617
|
+
}, 5e3) : null;
|
|
618
|
+
const handler = (e) => {
|
|
619
|
+
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
620
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
621
|
+
setSchedule(null);
|
|
622
|
+
setLoading(false);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (e.data?.type === "ow:switch-schedule") {
|
|
626
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
627
|
+
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
628
|
+
initialized = false;
|
|
629
|
+
setLoading(true);
|
|
630
|
+
window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
634
|
+
if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
|
|
635
|
+
if (initialized && switchScheduleIdRef.current === null) return;
|
|
636
|
+
initialized = true;
|
|
637
|
+
if (timer) clearTimeout(timer);
|
|
638
|
+
const schedules = e.data.schedules ?? [];
|
|
639
|
+
const isSwitching = switchScheduleIdRef.current !== null;
|
|
640
|
+
const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : effectiveId ? schedules.find((s) => s.id === effectiveId) ?? schedules[0] ?? null : schedules[0] ?? null;
|
|
641
|
+
switchScheduleIdRef.current = null;
|
|
642
|
+
setSchedule(target);
|
|
643
|
+
setLoading(false);
|
|
644
|
+
if (notifyOnConnect && target && !isSwitching) {
|
|
645
|
+
window.parent.postMessage({
|
|
646
|
+
type: "ow:schedule-connected",
|
|
647
|
+
schedule: { id: target.id, name: target.name },
|
|
648
|
+
insertAfter
|
|
649
|
+
}, "*");
|
|
650
|
+
window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
window.addEventListener("message", handler);
|
|
654
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
655
|
+
return () => {
|
|
656
|
+
window.removeEventListener("message", handler);
|
|
657
|
+
if (timer) clearTimeout(timer);
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (!effectiveId) {
|
|
661
|
+
setLoading(false);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
;
|
|
665
|
+
(async () => {
|
|
666
|
+
try {
|
|
667
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
|
|
668
|
+
const data = await res.json();
|
|
669
|
+
setSchedule(data?.id ? data : null);
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
setLoading(false);
|
|
673
|
+
})();
|
|
674
|
+
}
|
|
675
|
+
if (initialScheduleId === void 0) {
|
|
676
|
+
let done = false;
|
|
677
|
+
let innerCleanup;
|
|
678
|
+
const timer = setTimeout(() => {
|
|
679
|
+
if (!done) {
|
|
680
|
+
done = true;
|
|
681
|
+
setLoading(false);
|
|
682
|
+
}
|
|
683
|
+
}, 2e3);
|
|
684
|
+
const configHandler = (e) => {
|
|
685
|
+
if (e.data?.type !== "ow:schedule-config" || e.data.insertAfter !== insertAfter || done) return;
|
|
686
|
+
done = true;
|
|
687
|
+
clearTimeout(timer);
|
|
688
|
+
window.removeEventListener("message", configHandler);
|
|
689
|
+
innerCleanup = loadWithId(e.data.scheduleId ?? null);
|
|
690
|
+
};
|
|
691
|
+
window.addEventListener("message", configHandler);
|
|
692
|
+
window.postMessage({ type: "ow:request-schedule-config", insertAfter }, "*");
|
|
693
|
+
return () => {
|
|
694
|
+
window.removeEventListener("message", configHandler);
|
|
695
|
+
clearTimeout(timer);
|
|
696
|
+
innerCleanup?.();
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
return loadWithId(initialScheduleId) ?? void 0;
|
|
700
|
+
}, []);
|
|
701
|
+
const timezoneLabel = schedule?.timezone ? (() => {
|
|
702
|
+
try {
|
|
703
|
+
return buildTimezoneLabel(schedule.timezone);
|
|
704
|
+
} catch {
|
|
705
|
+
return void 0;
|
|
706
|
+
}
|
|
707
|
+
})() : void 0;
|
|
708
|
+
const handleModalSubmit = async (email) => {
|
|
709
|
+
if (!modalState) return;
|
|
710
|
+
const { mode, classId, classDate } = modalState;
|
|
711
|
+
const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
|
|
712
|
+
const res = await fetch(endpoint, {
|
|
713
|
+
method: "POST",
|
|
714
|
+
headers: { "Content-Type": "application/json" },
|
|
715
|
+
body: JSON.stringify({ classDate: classDate.toISOString(), email })
|
|
716
|
+
});
|
|
717
|
+
if (!res.ok) {
|
|
718
|
+
const data = await res.json().catch(() => ({}));
|
|
719
|
+
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
const handleReplaceSchedule = () => {
|
|
723
|
+
setIsHovered(false);
|
|
724
|
+
if (schedule) {
|
|
725
|
+
window.parent.postMessage({
|
|
726
|
+
type: "ow:schedule-connected",
|
|
727
|
+
schedule: { id: schedule.id, name: schedule.name },
|
|
728
|
+
insertAfter
|
|
729
|
+
}, "*");
|
|
730
|
+
} else {
|
|
731
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
if (!inEditor && !loading && !schedule) return null;
|
|
735
|
+
const sectionId = `scheduling-${insertAfter}`;
|
|
736
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
737
|
+
"section",
|
|
738
|
+
{
|
|
739
|
+
"data-ohw-section": sectionId,
|
|
740
|
+
"data-ohw-scheduling-anchor": insertAfter,
|
|
741
|
+
className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
|
|
742
|
+
onMouseEnter: () => inEditor && setIsHovered(true),
|
|
743
|
+
onMouseLeave: () => setIsHovered(false),
|
|
744
|
+
children: [
|
|
745
|
+
inEditor && isHovered && !!schedule && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
746
|
+
"div",
|
|
747
|
+
{
|
|
748
|
+
className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
|
|
749
|
+
onClick: handleReplaceSchedule,
|
|
750
|
+
children: [
|
|
751
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
|
|
752
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
|
|
753
|
+
/* @__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)(
|
|
754
|
+
"div",
|
|
755
|
+
{
|
|
756
|
+
className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
|
|
757
|
+
style: {
|
|
758
|
+
fontSize: 14,
|
|
759
|
+
lineHeight: "24px",
|
|
760
|
+
fontFamily: "'Figtree', system-ui, sans-serif",
|
|
761
|
+
fontWeight: 500,
|
|
762
|
+
color: "#0C0A09"
|
|
763
|
+
},
|
|
764
|
+
children: [
|
|
765
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CalendarFoldIcon, {}),
|
|
766
|
+
"Replace schedule"
|
|
767
|
+
]
|
|
768
|
+
}
|
|
769
|
+
) })
|
|
770
|
+
]
|
|
771
|
+
}
|
|
772
|
+
),
|
|
773
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
774
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
|
|
775
|
+
/* @__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" }),
|
|
776
|
+
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 })
|
|
777
|
+
] }),
|
|
778
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
779
|
+
timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
780
|
+
/* @__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, insertAfter }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
781
|
+
ScheduleView,
|
|
782
|
+
{
|
|
783
|
+
schedule,
|
|
784
|
+
dates,
|
|
785
|
+
selectedIdx,
|
|
786
|
+
onSelectDate: setSelectedIdx,
|
|
787
|
+
onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
|
|
788
|
+
}
|
|
789
|
+
) })
|
|
790
|
+
] })
|
|
791
|
+
] }),
|
|
792
|
+
modalState && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
793
|
+
EmailCaptureModal,
|
|
794
|
+
{
|
|
795
|
+
title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
|
|
796
|
+
subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
|
|
797
|
+
onSubmit: handleModalSubmit,
|
|
798
|
+
onClose: () => setModalState(null)
|
|
799
|
+
}
|
|
800
|
+
)
|
|
801
|
+
]
|
|
802
|
+
}
|
|
803
|
+
);
|
|
804
|
+
}
|
|
52
805
|
|
|
53
806
|
// src/ui/toggle-group.tsx
|
|
54
|
-
var
|
|
807
|
+
var React2 = __toESM(require("react"), 1);
|
|
55
808
|
|
|
56
809
|
// node_modules/clsx/dist/clsx.mjs
|
|
57
810
|
function r(e) {
|
|
@@ -3370,8 +4123,8 @@ var cva = (base, config) => (props) => {
|
|
|
3370
4123
|
};
|
|
3371
4124
|
|
|
3372
4125
|
// src/ui/toggle.tsx
|
|
3373
|
-
var
|
|
3374
|
-
var
|
|
4126
|
+
var import_radix_ui2 = require("radix-ui");
|
|
4127
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3375
4128
|
var toggleVariants = cva(
|
|
3376
4129
|
"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",
|
|
3377
4130
|
{
|
|
@@ -3398,8 +4151,8 @@ function Toggle({
|
|
|
3398
4151
|
size,
|
|
3399
4152
|
...props
|
|
3400
4153
|
}) {
|
|
3401
|
-
return /* @__PURE__ */ (0,
|
|
3402
|
-
|
|
4154
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
4155
|
+
import_radix_ui2.Toggle.Root,
|
|
3403
4156
|
{
|
|
3404
4157
|
"data-slot": "toggle",
|
|
3405
4158
|
className: cn(toggleVariants({ variant, size, className })),
|
|
@@ -3409,8 +4162,8 @@ function Toggle({
|
|
|
3409
4162
|
}
|
|
3410
4163
|
|
|
3411
4164
|
// src/ui/toggle-group.tsx
|
|
3412
|
-
var
|
|
3413
|
-
var ToggleGroupContext =
|
|
4165
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
4166
|
+
var ToggleGroupContext = React2.createContext({
|
|
3414
4167
|
value: "",
|
|
3415
4168
|
onValueChange: () => {
|
|
3416
4169
|
}
|
|
@@ -3422,13 +4175,13 @@ function ToggleGroup({
|
|
|
3422
4175
|
children,
|
|
3423
4176
|
...props
|
|
3424
4177
|
}) {
|
|
3425
|
-
return /* @__PURE__ */ (0,
|
|
4178
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3426
4179
|
"div",
|
|
3427
4180
|
{
|
|
3428
4181
|
role: "group",
|
|
3429
4182
|
className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
|
|
3430
4183
|
...props,
|
|
3431
|
-
children: /* @__PURE__ */ (0,
|
|
4184
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
|
|
3432
4185
|
}
|
|
3433
4186
|
);
|
|
3434
4187
|
}
|
|
@@ -3438,8 +4191,8 @@ function ToggleGroupItem({
|
|
|
3438
4191
|
children,
|
|
3439
4192
|
...props
|
|
3440
4193
|
}) {
|
|
3441
|
-
const { value: groupValue, onValueChange } =
|
|
3442
|
-
return /* @__PURE__ */ (0,
|
|
4194
|
+
const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
|
|
4195
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3443
4196
|
Toggle,
|
|
3444
4197
|
{
|
|
3445
4198
|
pressed: groupValue === value,
|
|
@@ -3459,7 +4212,7 @@ function ToggleGroupItem({
|
|
|
3459
4212
|
}
|
|
3460
4213
|
|
|
3461
4214
|
// src/OhhwellsBridge.tsx
|
|
3462
|
-
var
|
|
4215
|
+
var import_react_dom2 = require("react-dom");
|
|
3463
4216
|
var import_navigation = require("next/navigation");
|
|
3464
4217
|
|
|
3465
4218
|
// src/lib/session-search.ts
|
|
@@ -3753,9 +4506,9 @@ function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
|
|
|
3753
4506
|
}
|
|
3754
4507
|
|
|
3755
4508
|
// src/ui/button.tsx
|
|
3756
|
-
var
|
|
3757
|
-
var
|
|
3758
|
-
var
|
|
4509
|
+
var React3 = __toESM(require("react"), 1);
|
|
4510
|
+
var import_radix_ui3 = require("radix-ui");
|
|
4511
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
3759
4512
|
var buttonVariants = cva(
|
|
3760
4513
|
"inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
|
|
3761
4514
|
{
|
|
@@ -3776,10 +4529,10 @@ var buttonVariants = cva(
|
|
|
3776
4529
|
}
|
|
3777
4530
|
}
|
|
3778
4531
|
);
|
|
3779
|
-
var Button =
|
|
4532
|
+
var Button = React3.forwardRef(
|
|
3780
4533
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
3781
|
-
const Comp = asChild ?
|
|
3782
|
-
return /* @__PURE__ */ (0,
|
|
4534
|
+
const Comp = asChild ? import_radix_ui3.Slot.Root : "button";
|
|
4535
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3783
4536
|
Comp,
|
|
3784
4537
|
{
|
|
3785
4538
|
ref,
|
|
@@ -3793,75 +4546,75 @@ var Button = React2.forwardRef(
|
|
|
3793
4546
|
Button.displayName = "Button";
|
|
3794
4547
|
|
|
3795
4548
|
// src/ui/icons.tsx
|
|
3796
|
-
var
|
|
4549
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
3797
4550
|
function IconX({ className, ...props }) {
|
|
3798
|
-
return /* @__PURE__ */ (0,
|
|
3799
|
-
/* @__PURE__ */ (0,
|
|
3800
|
-
/* @__PURE__ */ (0,
|
|
4551
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4552
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M18 6 6 18" }),
|
|
4553
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 6 12 12" })
|
|
3801
4554
|
] });
|
|
3802
4555
|
}
|
|
3803
4556
|
function IconChevronDown({ className, ...props }) {
|
|
3804
|
-
return /* @__PURE__ */ (0,
|
|
4557
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 9 6 6 6-6" }) });
|
|
3805
4558
|
}
|
|
3806
4559
|
function IconFile({ className, ...props }) {
|
|
3807
|
-
return /* @__PURE__ */ (0,
|
|
3808
|
-
/* @__PURE__ */ (0,
|
|
3809
|
-
/* @__PURE__ */ (0,
|
|
4560
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4561
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
|
|
4562
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
|
|
3810
4563
|
] });
|
|
3811
4564
|
}
|
|
3812
4565
|
function IconInfo({ className, ...props }) {
|
|
3813
|
-
return /* @__PURE__ */ (0,
|
|
3814
|
-
/* @__PURE__ */ (0,
|
|
3815
|
-
/* @__PURE__ */ (0,
|
|
3816
|
-
/* @__PURE__ */ (0,
|
|
4566
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4567
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
4568
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 16v-4" }),
|
|
4569
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 8h.01" })
|
|
3817
4570
|
] });
|
|
3818
4571
|
}
|
|
3819
4572
|
function IconArrowRight({ className, ...props }) {
|
|
3820
|
-
return /* @__PURE__ */ (0,
|
|
3821
|
-
/* @__PURE__ */ (0,
|
|
3822
|
-
/* @__PURE__ */ (0,
|
|
4573
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4574
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5 12h14" }),
|
|
4575
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m12 5 7 7-7 7" })
|
|
3823
4576
|
] });
|
|
3824
4577
|
}
|
|
3825
4578
|
function IconSection({ className, ...props }) {
|
|
3826
|
-
return /* @__PURE__ */ (0,
|
|
3827
|
-
/* @__PURE__ */ (0,
|
|
3828
|
-
/* @__PURE__ */ (0,
|
|
3829
|
-
/* @__PURE__ */ (0,
|
|
4579
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4580
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
|
|
4581
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
|
|
4582
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
|
|
3830
4583
|
] });
|
|
3831
4584
|
}
|
|
3832
4585
|
|
|
3833
4586
|
// src/ui/link-modal/DestinationBreadcrumb.tsx
|
|
3834
|
-
var
|
|
4587
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
3835
4588
|
function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
|
|
3836
|
-
return /* @__PURE__ */ (0,
|
|
3837
|
-
/* @__PURE__ */ (0,
|
|
3838
|
-
/* @__PURE__ */ (0,
|
|
3839
|
-
/* @__PURE__ */ (0,
|
|
3840
|
-
/* @__PURE__ */ (0,
|
|
3841
|
-
/* @__PURE__ */ (0,
|
|
4589
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
|
|
4590
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
|
|
4591
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
4592
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4593
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4594
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
|
|
3842
4595
|
] }),
|
|
3843
|
-
/* @__PURE__ */ (0,
|
|
3844
|
-
/* @__PURE__ */ (0,
|
|
3845
|
-
/* @__PURE__ */ (0,
|
|
3846
|
-
/* @__PURE__ */ (0,
|
|
4596
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
4597
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
|
|
4598
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4599
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
|
|
3847
4600
|
] })
|
|
3848
4601
|
] })
|
|
3849
4602
|
] });
|
|
3850
4603
|
}
|
|
3851
4604
|
|
|
3852
4605
|
// src/ui/link-modal/SectionTreeItem.tsx
|
|
3853
|
-
var
|
|
4606
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
3854
4607
|
function SectionTreeItem({ section, onSelect, selected }) {
|
|
3855
4608
|
const interactive = Boolean(onSelect);
|
|
3856
|
-
return /* @__PURE__ */ (0,
|
|
3857
|
-
/* @__PURE__ */ (0,
|
|
4609
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
|
|
4610
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3858
4611
|
"div",
|
|
3859
4612
|
{
|
|
3860
4613
|
className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
|
|
3861
4614
|
"aria-hidden": true
|
|
3862
4615
|
}
|
|
3863
4616
|
),
|
|
3864
|
-
/* @__PURE__ */ (0,
|
|
4617
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
3865
4618
|
"div",
|
|
3866
4619
|
{
|
|
3867
4620
|
role: interactive ? "button" : void 0,
|
|
@@ -3879,8 +4632,8 @@ function SectionTreeItem({ section, onSelect, selected }) {
|
|
|
3879
4632
|
interactive && selected && "border-primary"
|
|
3880
4633
|
),
|
|
3881
4634
|
children: [
|
|
3882
|
-
/* @__PURE__ */ (0,
|
|
3883
|
-
/* @__PURE__ */ (0,
|
|
4635
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4636
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
|
|
3884
4637
|
]
|
|
3885
4638
|
}
|
|
3886
4639
|
)
|
|
@@ -3888,23 +4641,23 @@ function SectionTreeItem({ section, onSelect, selected }) {
|
|
|
3888
4641
|
}
|
|
3889
4642
|
function SectionPickerList({ sections, onSelect }) {
|
|
3890
4643
|
if (sections.length === 0) {
|
|
3891
|
-
return /* @__PURE__ */ (0,
|
|
4644
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
|
|
3892
4645
|
}
|
|
3893
|
-
return /* @__PURE__ */ (0,
|
|
3894
|
-
/* @__PURE__ */ (0,
|
|
3895
|
-
sections.map((section) => /* @__PURE__ */ (0,
|
|
4646
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
4647
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
|
|
4648
|
+
sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SectionTreeItem, { section, onSelect }, section.id))
|
|
3896
4649
|
] });
|
|
3897
4650
|
}
|
|
3898
4651
|
|
|
3899
4652
|
// src/ui/link-modal/UrlOrPageInput.tsx
|
|
3900
|
-
var
|
|
4653
|
+
var import_react3 = require("react");
|
|
3901
4654
|
|
|
3902
4655
|
// src/ui/input.tsx
|
|
3903
|
-
var
|
|
3904
|
-
var
|
|
3905
|
-
var Input =
|
|
4656
|
+
var React4 = __toESM(require("react"), 1);
|
|
4657
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
4658
|
+
var Input = React4.forwardRef(
|
|
3906
4659
|
({ className, type, ...props }, ref) => {
|
|
3907
|
-
return /* @__PURE__ */ (0,
|
|
4660
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3908
4661
|
"input",
|
|
3909
4662
|
{
|
|
3910
4663
|
type,
|
|
@@ -3922,11 +4675,11 @@ var Input = React3.forwardRef(
|
|
|
3922
4675
|
Input.displayName = "Input";
|
|
3923
4676
|
|
|
3924
4677
|
// src/ui/label.tsx
|
|
3925
|
-
var
|
|
3926
|
-
var
|
|
4678
|
+
var import_radix_ui4 = require("radix-ui");
|
|
4679
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
3927
4680
|
function Label({ className, ...props }) {
|
|
3928
|
-
return /* @__PURE__ */ (0,
|
|
3929
|
-
|
|
4681
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4682
|
+
import_radix_ui4.Label.Root,
|
|
3930
4683
|
{
|
|
3931
4684
|
"data-slot": "label",
|
|
3932
4685
|
className: cn("text-sm font-medium leading-5 text-foreground", className),
|
|
@@ -3936,13 +4689,13 @@ function Label({ className, ...props }) {
|
|
|
3936
4689
|
}
|
|
3937
4690
|
|
|
3938
4691
|
// src/ui/popover.tsx
|
|
3939
|
-
var
|
|
3940
|
-
var
|
|
4692
|
+
var import_radix_ui5 = require("radix-ui");
|
|
4693
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
3941
4694
|
function Popover({ ...props }) {
|
|
3942
|
-
return /* @__PURE__ */ (0,
|
|
4695
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Root, { "data-slot": "popover", ...props });
|
|
3943
4696
|
}
|
|
3944
4697
|
function PopoverTrigger({ ...props }) {
|
|
3945
|
-
return /* @__PURE__ */ (0,
|
|
4698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
3946
4699
|
}
|
|
3947
4700
|
function PopoverContent({
|
|
3948
4701
|
className,
|
|
@@ -3950,8 +4703,8 @@ function PopoverContent({
|
|
|
3950
4703
|
sideOffset = 6,
|
|
3951
4704
|
...props
|
|
3952
4705
|
}) {
|
|
3953
|
-
return /* @__PURE__ */ (0,
|
|
3954
|
-
|
|
4706
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4707
|
+
import_radix_ui5.Popover.Content,
|
|
3955
4708
|
{
|
|
3956
4709
|
"data-slot": "popover-content",
|
|
3957
4710
|
align,
|
|
@@ -3966,9 +4719,9 @@ function PopoverContent({
|
|
|
3966
4719
|
}
|
|
3967
4720
|
|
|
3968
4721
|
// src/ui/link-modal/UrlOrPageInput.tsx
|
|
3969
|
-
var
|
|
4722
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
3970
4723
|
function FieldChevron({ onClick }) {
|
|
3971
|
-
return /* @__PURE__ */ (0,
|
|
4724
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3972
4725
|
"button",
|
|
3973
4726
|
{
|
|
3974
4727
|
type: "button",
|
|
@@ -3976,7 +4729,7 @@ function FieldChevron({ onClick }) {
|
|
|
3976
4729
|
onClick,
|
|
3977
4730
|
"aria-label": "Open page list",
|
|
3978
4731
|
tabIndex: -1,
|
|
3979
|
-
children: /* @__PURE__ */ (0,
|
|
4732
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconChevronDown, {})
|
|
3980
4733
|
}
|
|
3981
4734
|
);
|
|
3982
4735
|
}
|
|
@@ -3992,9 +4745,9 @@ function UrlOrPageInput({
|
|
|
3992
4745
|
readOnly = false,
|
|
3993
4746
|
urlError
|
|
3994
4747
|
}) {
|
|
3995
|
-
const inputId = (0,
|
|
3996
|
-
const inputRef = (0,
|
|
3997
|
-
const [isFocused, setIsFocused] = (0,
|
|
4748
|
+
const inputId = (0, import_react3.useId)();
|
|
4749
|
+
const inputRef = (0, import_react3.useRef)(null);
|
|
4750
|
+
const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
|
|
3998
4751
|
const openDropdown = () => {
|
|
3999
4752
|
if (readOnly || filteredPages.length === 0) return;
|
|
4000
4753
|
onDropdownOpenChange(true);
|
|
@@ -4015,12 +4768,12 @@ function UrlOrPageInput({
|
|
|
4015
4768
|
"data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
|
|
4016
4769
|
urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
|
|
4017
4770
|
);
|
|
4018
|
-
return /* @__PURE__ */ (0,
|
|
4019
|
-
/* @__PURE__ */ (0,
|
|
4020
|
-
/* @__PURE__ */ (0,
|
|
4021
|
-
/* @__PURE__ */ (0,
|
|
4022
|
-
selectedPage ? /* @__PURE__ */ (0,
|
|
4023
|
-
readOnly ? /* @__PURE__ */ (0,
|
|
4771
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
|
|
4772
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
|
|
4773
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
|
|
4774
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
|
|
4775
|
+
selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
|
|
4776
|
+
readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
4024
4777
|
Input,
|
|
4025
4778
|
{
|
|
4026
4779
|
ref: inputRef,
|
|
@@ -4039,7 +4792,7 @@ function UrlOrPageInput({
|
|
|
4039
4792
|
)
|
|
4040
4793
|
}
|
|
4041
4794
|
),
|
|
4042
|
-
selectedPage && !readOnly ? /* @__PURE__ */ (0,
|
|
4795
|
+
selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
4043
4796
|
"button",
|
|
4044
4797
|
{
|
|
4045
4798
|
type: "button",
|
|
@@ -4047,31 +4800,31 @@ function UrlOrPageInput({
|
|
|
4047
4800
|
onMouseDown: clearSelection,
|
|
4048
4801
|
"aria-label": "Clear selected page",
|
|
4049
4802
|
tabIndex: -1,
|
|
4050
|
-
children: /* @__PURE__ */ (0,
|
|
4803
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconX, { "aria-hidden": true })
|
|
4051
4804
|
}
|
|
4052
4805
|
) : null,
|
|
4053
|
-
!readOnly ? /* @__PURE__ */ (0,
|
|
4806
|
+
!readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
|
|
4054
4807
|
] }) }),
|
|
4055
|
-
filteredPages.length > 0 && /* @__PURE__ */ (0,
|
|
4808
|
+
filteredPages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
4056
4809
|
"button",
|
|
4057
4810
|
{
|
|
4058
4811
|
type: "button",
|
|
4059
4812
|
className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
|
|
4060
4813
|
onClick: () => onPageSelect(page),
|
|
4061
4814
|
children: [
|
|
4062
|
-
/* @__PURE__ */ (0,
|
|
4063
|
-
/* @__PURE__ */ (0,
|
|
4815
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
|
|
4816
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: page.title })
|
|
4064
4817
|
]
|
|
4065
4818
|
},
|
|
4066
4819
|
page.path
|
|
4067
4820
|
)) })
|
|
4068
4821
|
] }),
|
|
4069
|
-
urlError ? /* @__PURE__ */ (0,
|
|
4822
|
+
urlError ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
|
|
4070
4823
|
] });
|
|
4071
4824
|
}
|
|
4072
4825
|
|
|
4073
4826
|
// src/ui/link-modal/useLinkModalState.ts
|
|
4074
|
-
var
|
|
4827
|
+
var import_react4 = require("react");
|
|
4075
4828
|
function useLinkModalState({
|
|
4076
4829
|
open,
|
|
4077
4830
|
mode,
|
|
@@ -4083,17 +4836,17 @@ function useLinkModalState({
|
|
|
4083
4836
|
onClose,
|
|
4084
4837
|
onSubmit
|
|
4085
4838
|
}) {
|
|
4086
|
-
const availablePages = (0,
|
|
4839
|
+
const availablePages = (0, import_react4.useMemo)(
|
|
4087
4840
|
() => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
|
|
4088
4841
|
[mode, pages, existingTargets]
|
|
4089
4842
|
);
|
|
4090
|
-
const [searchValue, setSearchValue] = (0,
|
|
4091
|
-
const [selectedPage, setSelectedPage] = (0,
|
|
4092
|
-
const [selectedSection, setSelectedSection] = (0,
|
|
4093
|
-
const [step, setStep] = (0,
|
|
4094
|
-
const [dropdownOpen, setDropdownOpen] = (0,
|
|
4095
|
-
const [urlError, setUrlError] = (0,
|
|
4096
|
-
const reset = (0,
|
|
4843
|
+
const [searchValue, setSearchValue] = (0, import_react4.useState)("");
|
|
4844
|
+
const [selectedPage, setSelectedPage] = (0, import_react4.useState)(null);
|
|
4845
|
+
const [selectedSection, setSelectedSection] = (0, import_react4.useState)(null);
|
|
4846
|
+
const [step, setStep] = (0, import_react4.useState)("input");
|
|
4847
|
+
const [dropdownOpen, setDropdownOpen] = (0, import_react4.useState)(false);
|
|
4848
|
+
const [urlError, setUrlError] = (0, import_react4.useState)("");
|
|
4849
|
+
const reset = (0, import_react4.useCallback)(() => {
|
|
4097
4850
|
setSearchValue("");
|
|
4098
4851
|
setSelectedPage(null);
|
|
4099
4852
|
setSelectedSection(null);
|
|
@@ -4101,7 +4854,7 @@ function useLinkModalState({
|
|
|
4101
4854
|
setDropdownOpen(false);
|
|
4102
4855
|
setUrlError("");
|
|
4103
4856
|
}, []);
|
|
4104
|
-
(0,
|
|
4857
|
+
(0, import_react4.useEffect)(() => {
|
|
4105
4858
|
if (!open) return;
|
|
4106
4859
|
if (mode === "edit" && initialTarget) {
|
|
4107
4860
|
const { pageRoute } = parseTarget(initialTarget);
|
|
@@ -4117,11 +4870,11 @@ function useLinkModalState({
|
|
|
4117
4870
|
setDropdownOpen(false);
|
|
4118
4871
|
setUrlError("");
|
|
4119
4872
|
}, [open, mode, initialTarget, pages, sectionsByPath, reset]);
|
|
4120
|
-
const filteredPages = (0,
|
|
4873
|
+
const filteredPages = (0, import_react4.useMemo)(() => {
|
|
4121
4874
|
if (!searchValue.trim()) return availablePages;
|
|
4122
4875
|
return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
|
|
4123
4876
|
}, [availablePages, searchValue]);
|
|
4124
|
-
const activeSections = (0,
|
|
4877
|
+
const activeSections = (0, import_react4.useMemo)(() => {
|
|
4125
4878
|
if (!selectedPage) return [];
|
|
4126
4879
|
return getSectionsForPath(sectionsByPath, selectedPage.path);
|
|
4127
4880
|
}, [selectedPage, sectionsByPath]);
|
|
@@ -4167,7 +4920,7 @@ function useLinkModalState({
|
|
|
4167
4920
|
reset();
|
|
4168
4921
|
onClose();
|
|
4169
4922
|
};
|
|
4170
|
-
const isValid = (0,
|
|
4923
|
+
const isValid = (0, import_react4.useMemo)(() => {
|
|
4171
4924
|
if (urlError) return false;
|
|
4172
4925
|
if (showBreadcrumb) return true;
|
|
4173
4926
|
if (selectedPage) return true;
|
|
@@ -4234,7 +4987,7 @@ function useLinkModalState({
|
|
|
4234
4987
|
}
|
|
4235
4988
|
|
|
4236
4989
|
// src/ui/link-modal/LinkEditorPanel.tsx
|
|
4237
|
-
var
|
|
4990
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
4238
4991
|
function LinkEditorPanel({
|
|
4239
4992
|
open = true,
|
|
4240
4993
|
mode = "create",
|
|
@@ -4258,26 +5011,26 @@ function LinkEditorPanel({
|
|
|
4258
5011
|
onSubmit
|
|
4259
5012
|
});
|
|
4260
5013
|
const isCancel = state.secondaryLabel === "Cancel";
|
|
4261
|
-
return /* @__PURE__ */ (0,
|
|
4262
|
-
/* @__PURE__ */ (0,
|
|
5014
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
5015
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4263
5016
|
"button",
|
|
4264
5017
|
{
|
|
4265
5018
|
type: "button",
|
|
4266
5019
|
className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
|
|
4267
5020
|
onClick: state.handleClose,
|
|
4268
5021
|
"aria-label": "Close",
|
|
4269
|
-
children: /* @__PURE__ */ (0,
|
|
5022
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
|
|
4270
5023
|
}
|
|
4271
5024
|
),
|
|
4272
|
-
/* @__PURE__ */ (0,
|
|
4273
|
-
/* @__PURE__ */ (0,
|
|
4274
|
-
state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0,
|
|
5025
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
|
|
5026
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
|
|
5027
|
+
state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4275
5028
|
DestinationBreadcrumb,
|
|
4276
5029
|
{
|
|
4277
5030
|
pageTitle: state.selectedPage.title,
|
|
4278
5031
|
sectionLabel: state.selectedSection.label
|
|
4279
5032
|
}
|
|
4280
|
-
) : /* @__PURE__ */ (0,
|
|
5033
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4281
5034
|
UrlOrPageInput,
|
|
4282
5035
|
{
|
|
4283
5036
|
value: state.searchValue,
|
|
@@ -4290,18 +5043,18 @@ function LinkEditorPanel({
|
|
|
4290
5043
|
urlError: state.urlError
|
|
4291
5044
|
}
|
|
4292
5045
|
),
|
|
4293
|
-
state.showChooseSection ? /* @__PURE__ */ (0,
|
|
4294
|
-
/* @__PURE__ */ (0,
|
|
4295
|
-
/* @__PURE__ */ (0,
|
|
4296
|
-
/* @__PURE__ */ (0,
|
|
4297
|
-
/* @__PURE__ */ (0,
|
|
5046
|
+
state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
|
|
5047
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
|
|
5048
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
|
|
5049
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
|
|
5050
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Pick a section this link should scroll to." })
|
|
4298
5051
|
] })
|
|
4299
5052
|
] }) : null,
|
|
4300
|
-
state.showSectionPicker ? /* @__PURE__ */ (0,
|
|
4301
|
-
state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0,
|
|
5053
|
+
state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
|
|
5054
|
+
state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
|
|
4302
5055
|
] }),
|
|
4303
|
-
/* @__PURE__ */ (0,
|
|
4304
|
-
/* @__PURE__ */ (0,
|
|
5056
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
|
|
5057
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4305
5058
|
Button,
|
|
4306
5059
|
{
|
|
4307
5060
|
type: "button",
|
|
@@ -4316,7 +5069,7 @@ function LinkEditorPanel({
|
|
|
4316
5069
|
children: state.secondaryLabel
|
|
4317
5070
|
}
|
|
4318
5071
|
),
|
|
4319
|
-
/* @__PURE__ */ (0,
|
|
5072
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4320
5073
|
Button,
|
|
4321
5074
|
{
|
|
4322
5075
|
type: "button",
|
|
@@ -4335,7 +5088,7 @@ function LinkEditorPanel({
|
|
|
4335
5088
|
}
|
|
4336
5089
|
|
|
4337
5090
|
// src/ui/link-modal/LinkPopover.tsx
|
|
4338
|
-
var
|
|
5091
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
4339
5092
|
function LinkPopover({
|
|
4340
5093
|
rect,
|
|
4341
5094
|
parentScroll,
|
|
@@ -4343,7 +5096,7 @@ function LinkPopover({
|
|
|
4343
5096
|
...editorProps
|
|
4344
5097
|
}) {
|
|
4345
5098
|
const { top, left, transform } = calcPopoverPos(rect, parentScroll);
|
|
4346
|
-
return /* @__PURE__ */ (0,
|
|
5099
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
4347
5100
|
"div",
|
|
4348
5101
|
{
|
|
4349
5102
|
ref: panelRef,
|
|
@@ -4356,7 +5109,7 @@ function LinkPopover({
|
|
|
4356
5109
|
),
|
|
4357
5110
|
style: { top, left, transform },
|
|
4358
5111
|
onMouseDown: (e) => e.stopPropagation(),
|
|
4359
|
-
children: /* @__PURE__ */ (0,
|
|
5112
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LinkEditorPanel, { ...editorProps, open: true })
|
|
4360
5113
|
}
|
|
4361
5114
|
);
|
|
4362
5115
|
}
|
|
@@ -4419,8 +5172,30 @@ function shouldUseDevFixtures() {
|
|
|
4419
5172
|
return new URLSearchParams(q).get("ohw-fixtures") === "1";
|
|
4420
5173
|
}
|
|
4421
5174
|
|
|
5175
|
+
// src/ui/badge.tsx
|
|
5176
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
5177
|
+
var badgeVariants = cva(
|
|
5178
|
+
"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",
|
|
5179
|
+
{
|
|
5180
|
+
variants: {
|
|
5181
|
+
variant: {
|
|
5182
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
5183
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
5184
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
5185
|
+
outline: "text-foreground"
|
|
5186
|
+
}
|
|
5187
|
+
},
|
|
5188
|
+
defaultVariants: {
|
|
5189
|
+
variant: "default"
|
|
5190
|
+
}
|
|
5191
|
+
}
|
|
5192
|
+
);
|
|
5193
|
+
function Badge({ className, variant, ...props }) {
|
|
5194
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
5195
|
+
}
|
|
5196
|
+
|
|
4422
5197
|
// src/OhhwellsBridge.tsx
|
|
4423
|
-
var
|
|
5198
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
4424
5199
|
var PRIMARY = "#0885FE";
|
|
4425
5200
|
var IMAGE_FADE_MS = 300;
|
|
4426
5201
|
function runOpacityFade(el, onDone) {
|
|
@@ -4471,6 +5246,169 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
4471
5246
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
4472
5247
|
});
|
|
4473
5248
|
}
|
|
5249
|
+
function getSectionsTracker() {
|
|
5250
|
+
let el = document.querySelector("[data-ohw-sections-tracker]");
|
|
5251
|
+
if (!el) {
|
|
5252
|
+
el = document.createElement("div");
|
|
5253
|
+
el.setAttribute("data-ohw-sections-tracker", "");
|
|
5254
|
+
el.style.display = "none";
|
|
5255
|
+
document.body.appendChild(el);
|
|
5256
|
+
}
|
|
5257
|
+
return el;
|
|
5258
|
+
}
|
|
5259
|
+
function updateSectionScheduleId(insertAfter, scheduleId) {
|
|
5260
|
+
const tracker = getSectionsTracker();
|
|
5261
|
+
let sections = [];
|
|
5262
|
+
try {
|
|
5263
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5264
|
+
} catch {
|
|
5265
|
+
}
|
|
5266
|
+
const currentPath = window.location.pathname;
|
|
5267
|
+
const updated = sections.map((s) => {
|
|
5268
|
+
if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
|
|
5269
|
+
if (s.pagePath && s.pagePath !== currentPath) return s;
|
|
5270
|
+
return { ...s, scheduleId };
|
|
5271
|
+
});
|
|
5272
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5273
|
+
return tracker.textContent ?? "[]";
|
|
5274
|
+
}
|
|
5275
|
+
function schedulingSectionId(insertAfter) {
|
|
5276
|
+
return `scheduling-${insertAfter}`;
|
|
5277
|
+
}
|
|
5278
|
+
var INSERT_BEFORE_MARKER = ":before:";
|
|
5279
|
+
function parseSchedulingInsertAfter(insertAfter) {
|
|
5280
|
+
const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
|
|
5281
|
+
if (idx < 0) return { anchor: insertAfter, insertBefore: null };
|
|
5282
|
+
return {
|
|
5283
|
+
anchor: insertAfter.slice(0, idx),
|
|
5284
|
+
insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
|
|
5285
|
+
};
|
|
5286
|
+
}
|
|
5287
|
+
function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
|
|
5288
|
+
const parsed = parseSchedulingInsertAfter(insertAfter);
|
|
5289
|
+
const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
|
|
5290
|
+
const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
|
|
5291
|
+
return { effectiveInsertAfter, insertBefore };
|
|
5292
|
+
}
|
|
5293
|
+
function getSchedulingMountPoint(insertAfter) {
|
|
5294
|
+
const { anchor } = parseSchedulingInsertAfter(insertAfter);
|
|
5295
|
+
let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
|
|
5296
|
+
if (!anchorEl && anchor === "scheduling") {
|
|
5297
|
+
const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
|
|
5298
|
+
anchorEl = widgets.at(-1) ?? null;
|
|
5299
|
+
}
|
|
5300
|
+
if (!anchorEl) return null;
|
|
5301
|
+
return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
|
|
5302
|
+
}
|
|
5303
|
+
function schedulingMountDepth(insertAfter) {
|
|
5304
|
+
if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
|
|
5305
|
+
return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
|
|
5306
|
+
}
|
|
5307
|
+
function getPageSchedulingEntries(raw) {
|
|
5308
|
+
if (!raw) return [];
|
|
5309
|
+
try {
|
|
5310
|
+
const entries = JSON.parse(raw);
|
|
5311
|
+
const currentPath = window.location.pathname;
|
|
5312
|
+
return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
|
|
5313
|
+
} catch {
|
|
5314
|
+
return [];
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
function isSchedulingWidgetMissing(entry) {
|
|
5318
|
+
const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
|
|
5319
|
+
return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
|
|
5320
|
+
}
|
|
5321
|
+
function hasMissingSchedulingWidgets(entries) {
|
|
5322
|
+
return entries.some(isSchedulingWidgetMissing);
|
|
5323
|
+
}
|
|
5324
|
+
function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
|
|
5325
|
+
if (!hasMissingSchedulingWidgets(entries)) return;
|
|
5326
|
+
mountSchedulingEntries(entries, notifyOnConnect);
|
|
5327
|
+
}
|
|
5328
|
+
function initSectionsFromContent(content, removeExisting = false) {
|
|
5329
|
+
const raw = content["__ohw_sections"];
|
|
5330
|
+
if (!raw) return;
|
|
5331
|
+
try {
|
|
5332
|
+
if (removeExisting) {
|
|
5333
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
|
|
5334
|
+
}
|
|
5335
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
5336
|
+
if (inEditor) getSectionsTracker().textContent = raw;
|
|
5337
|
+
const pageEntries = getPageSchedulingEntries(raw);
|
|
5338
|
+
const preExisting = pageEntries.filter((e) => !isSchedulingWidgetMissing(e));
|
|
5339
|
+
const notifyForEntry = inEditor ? (e) => !e.scheduleId : false;
|
|
5340
|
+
mountSchedulingEntries(pageEntries, notifyForEntry);
|
|
5341
|
+
requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
|
|
5342
|
+
setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
|
|
5343
|
+
for (const entry of preExisting) {
|
|
5344
|
+
window.postMessage({ type: "ow:schedule-config", insertAfter: entry.insertAfter, scheduleId: entry.scheduleId ?? null }, "*");
|
|
5345
|
+
}
|
|
5346
|
+
} catch {
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
|
|
5350
|
+
const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
|
|
5351
|
+
const sectionId = schedulingSectionId(effectiveInsertAfter);
|
|
5352
|
+
if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
|
|
5353
|
+
const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
|
|
5354
|
+
if (!mountPoint) return false;
|
|
5355
|
+
const container = document.createElement("div");
|
|
5356
|
+
container.dataset.ohwSectionContainer = "scheduling";
|
|
5357
|
+
if (insertBefore) {
|
|
5358
|
+
const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
|
|
5359
|
+
const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
|
|
5360
|
+
if (!beforePoint) return false;
|
|
5361
|
+
beforePoint.insertAdjacentElement("beforebegin", container);
|
|
5362
|
+
} else {
|
|
5363
|
+
let tail = mountPoint;
|
|
5364
|
+
while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
|
|
5365
|
+
tail = tail.nextElementSibling;
|
|
5366
|
+
}
|
|
5367
|
+
tail.insertAdjacentElement("afterend", container);
|
|
5368
|
+
}
|
|
5369
|
+
const root = (0, import_client.createRoot)(container);
|
|
5370
|
+
(0, import_react_dom.flushSync)(() => {
|
|
5371
|
+
root.render(
|
|
5372
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5373
|
+
SchedulingWidget,
|
|
5374
|
+
{
|
|
5375
|
+
notifyOnConnect,
|
|
5376
|
+
initialScheduleId: scheduleId,
|
|
5377
|
+
insertAfter: effectiveInsertAfter
|
|
5378
|
+
}
|
|
5379
|
+
)
|
|
5380
|
+
);
|
|
5381
|
+
});
|
|
5382
|
+
const tracker = getSectionsTracker();
|
|
5383
|
+
let sections = [];
|
|
5384
|
+
try {
|
|
5385
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5386
|
+
} catch {
|
|
5387
|
+
}
|
|
5388
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
5389
|
+
if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
|
|
5390
|
+
sections.push({
|
|
5391
|
+
type: "scheduling",
|
|
5392
|
+
insertAfter: effectiveInsertAfter,
|
|
5393
|
+
pagePath: window.location.pathname,
|
|
5394
|
+
...scheduleId ? { scheduleId } : {}
|
|
5395
|
+
});
|
|
5396
|
+
tracker.textContent = JSON.stringify(sections);
|
|
5397
|
+
}
|
|
5398
|
+
return true;
|
|
5399
|
+
}
|
|
5400
|
+
function mountSchedulingEntries(entries, notifyOnConnect = false) {
|
|
5401
|
+
const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
|
|
5402
|
+
for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
|
|
5403
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
5404
|
+
const entry = pending[i];
|
|
5405
|
+
const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
|
|
5406
|
+
if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
|
|
5407
|
+
pending.splice(i, 1);
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
4474
5412
|
function getLinkHref(el) {
|
|
4475
5413
|
const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
|
|
4476
5414
|
return anchor?.getAttribute("href") ?? "";
|
|
@@ -4670,7 +5608,7 @@ var TOOLBAR_GROUPS = [
|
|
|
4670
5608
|
];
|
|
4671
5609
|
function GlowFrame({ rect, elRef }) {
|
|
4672
5610
|
const GAP = 6;
|
|
4673
|
-
return /* @__PURE__ */ (0,
|
|
5611
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4674
5612
|
"div",
|
|
4675
5613
|
{
|
|
4676
5614
|
ref: elRef,
|
|
@@ -4723,7 +5661,7 @@ function FloatingToolbar({
|
|
|
4723
5661
|
onEditLink
|
|
4724
5662
|
}) {
|
|
4725
5663
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
4726
|
-
return /* @__PURE__ */ (0,
|
|
5664
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4727
5665
|
"div",
|
|
4728
5666
|
{
|
|
4729
5667
|
ref: elRef,
|
|
@@ -4748,11 +5686,11 @@ function FloatingToolbar({
|
|
|
4748
5686
|
},
|
|
4749
5687
|
onMouseDown: (e) => e.stopPropagation(),
|
|
4750
5688
|
children: [
|
|
4751
|
-
TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0,
|
|
4752
|
-
gi > 0 && /* @__PURE__ */ (0,
|
|
5689
|
+
TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react5.default.Fragment, { children: [
|
|
5690
|
+
gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
|
|
4753
5691
|
btns.map((btn) => {
|
|
4754
5692
|
const isActive = activeCommands.has(btn.cmd);
|
|
4755
|
-
return /* @__PURE__ */ (0,
|
|
5693
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4756
5694
|
"button",
|
|
4757
5695
|
{
|
|
4758
5696
|
title: btn.title,
|
|
@@ -4778,7 +5716,7 @@ function FloatingToolbar({
|
|
|
4778
5716
|
flexShrink: 0,
|
|
4779
5717
|
padding: 6
|
|
4780
5718
|
},
|
|
4781
|
-
children: /* @__PURE__ */ (0,
|
|
5719
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4782
5720
|
"svg",
|
|
4783
5721
|
{
|
|
4784
5722
|
width: "16",
|
|
@@ -4798,7 +5736,7 @@ function FloatingToolbar({
|
|
|
4798
5736
|
);
|
|
4799
5737
|
})
|
|
4800
5738
|
] }, gi)),
|
|
4801
|
-
showEditLink ? /* @__PURE__ */ (0,
|
|
5739
|
+
showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4802
5740
|
"button",
|
|
4803
5741
|
{
|
|
4804
5742
|
type: "button",
|
|
@@ -4830,9 +5768,9 @@ function FloatingToolbar({
|
|
|
4830
5768
|
flexShrink: 0,
|
|
4831
5769
|
padding: 6
|
|
4832
5770
|
},
|
|
4833
|
-
children: /* @__PURE__ */ (0,
|
|
4834
|
-
/* @__PURE__ */ (0,
|
|
4835
|
-
/* @__PURE__ */ (0,
|
|
5771
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
5772
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
|
|
5773
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
|
|
4836
5774
|
] })
|
|
4837
5775
|
}
|
|
4838
5776
|
) : null
|
|
@@ -4850,7 +5788,7 @@ function StateToggle({
|
|
|
4850
5788
|
states,
|
|
4851
5789
|
onStateChange
|
|
4852
5790
|
}) {
|
|
4853
|
-
return /* @__PURE__ */ (0,
|
|
5791
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4854
5792
|
ToggleGroup,
|
|
4855
5793
|
{
|
|
4856
5794
|
"data-ohw-state-toggle": "",
|
|
@@ -4864,18 +5802,35 @@ function StateToggle({
|
|
|
4864
5802
|
left: rect.right - 8,
|
|
4865
5803
|
transform: "translateX(-100%)"
|
|
4866
5804
|
},
|
|
4867
|
-
children: states.map((state) => /* @__PURE__ */ (0,
|
|
5805
|
+
children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
|
|
4868
5806
|
}
|
|
4869
5807
|
);
|
|
4870
5808
|
}
|
|
4871
5809
|
var contentCache = /* @__PURE__ */ new Map();
|
|
5810
|
+
function resolveSubdomain(subdomainFromQuery) {
|
|
5811
|
+
if (subdomainFromQuery) return subdomainFromQuery;
|
|
5812
|
+
if (typeof window !== "undefined") {
|
|
5813
|
+
const parts = window.location.hostname.split(".");
|
|
5814
|
+
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
5815
|
+
}
|
|
5816
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
|
|
5817
|
+
if (siteUrl) {
|
|
5818
|
+
try {
|
|
5819
|
+
const host = new URL(siteUrl).hostname;
|
|
5820
|
+
const siteParts = host.split(".");
|
|
5821
|
+
if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
|
|
5822
|
+
} catch {
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5825
|
+
return "";
|
|
5826
|
+
}
|
|
4872
5827
|
function OhhwellsBridge() {
|
|
4873
5828
|
const pathname = (0, import_navigation.usePathname)();
|
|
4874
5829
|
const router = (0, import_navigation.useRouter)();
|
|
4875
5830
|
const searchParams = (0, import_navigation.useSearchParams)();
|
|
4876
5831
|
const isEditMode = isEditSessionActive();
|
|
4877
|
-
const [bridgeRoot, setBridgeRoot] = (0,
|
|
4878
|
-
(0,
|
|
5832
|
+
const [bridgeRoot, setBridgeRoot] = (0, import_react5.useState)(null);
|
|
5833
|
+
(0, import_react5.useEffect)(() => {
|
|
4879
5834
|
const figtreeFontId = "ohw-figtree-font";
|
|
4880
5835
|
if (!document.getElementById(figtreeFontId)) {
|
|
4881
5836
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -4898,55 +5853,55 @@ function OhhwellsBridge() {
|
|
|
4898
5853
|
};
|
|
4899
5854
|
}, []);
|
|
4900
5855
|
const subdomainFromQuery = searchParams.get("subdomain");
|
|
4901
|
-
const subdomain = subdomainFromQuery
|
|
4902
|
-
|
|
4903
|
-
const parts = window.location.hostname.split(".");
|
|
4904
|
-
return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
|
|
4905
|
-
})();
|
|
4906
|
-
const postToParent = (0, import_react3.useCallback)((data) => {
|
|
5856
|
+
const subdomain = resolveSubdomain(subdomainFromQuery);
|
|
5857
|
+
const postToParent = (0, import_react5.useCallback)((data) => {
|
|
4907
5858
|
if (typeof window !== "undefined" && window.parent !== window) {
|
|
4908
5859
|
window.parent.postMessage(data, "*");
|
|
4909
5860
|
}
|
|
4910
5861
|
}, []);
|
|
4911
|
-
const [fetchState, setFetchState] = (0,
|
|
4912
|
-
const autoSaveTimers = (0,
|
|
4913
|
-
const activeElRef = (0,
|
|
4914
|
-
const originalContentRef = (0,
|
|
4915
|
-
const activeStateElRef = (0,
|
|
4916
|
-
const parentScrollRef = (0,
|
|
4917
|
-
const toolbarElRef = (0,
|
|
4918
|
-
const glowElRef = (0,
|
|
4919
|
-
const hoveredImageRef = (0,
|
|
4920
|
-
const hoveredImageHasTextOverlapRef = (0,
|
|
4921
|
-
const
|
|
4922
|
-
const
|
|
4923
|
-
const
|
|
4924
|
-
const
|
|
5862
|
+
const [fetchState, setFetchState] = (0, import_react5.useState)("idle");
|
|
5863
|
+
const autoSaveTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
|
|
5864
|
+
const activeElRef = (0, import_react5.useRef)(null);
|
|
5865
|
+
const originalContentRef = (0, import_react5.useRef)(null);
|
|
5866
|
+
const activeStateElRef = (0, import_react5.useRef)(null);
|
|
5867
|
+
const parentScrollRef = (0, import_react5.useRef)(null);
|
|
5868
|
+
const toolbarElRef = (0, import_react5.useRef)(null);
|
|
5869
|
+
const glowElRef = (0, import_react5.useRef)(null);
|
|
5870
|
+
const hoveredImageRef = (0, import_react5.useRef)(null);
|
|
5871
|
+
const hoveredImageHasTextOverlapRef = (0, import_react5.useRef)(false);
|
|
5872
|
+
const hoveredGapRef = (0, import_react5.useRef)(null);
|
|
5873
|
+
const imageUnhoverTimerRef = (0, import_react5.useRef)(null);
|
|
5874
|
+
const imageShowTimerRef = (0, import_react5.useRef)(null);
|
|
5875
|
+
const editStylesRef = (0, import_react5.useRef)(null);
|
|
5876
|
+
const activateRef = (0, import_react5.useRef)(() => {
|
|
4925
5877
|
});
|
|
4926
|
-
const deactivateRef = (0,
|
|
5878
|
+
const deactivateRef = (0, import_react5.useRef)(() => {
|
|
4927
5879
|
});
|
|
4928
|
-
const refreshActiveCommandsRef = (0,
|
|
5880
|
+
const refreshActiveCommandsRef = (0, import_react5.useRef)(() => {
|
|
4929
5881
|
});
|
|
4930
|
-
const postToParentRef = (0,
|
|
5882
|
+
const postToParentRef = (0, import_react5.useRef)(postToParent);
|
|
4931
5883
|
postToParentRef.current = postToParent;
|
|
4932
|
-
const
|
|
4933
|
-
const
|
|
4934
|
-
const [
|
|
4935
|
-
const [
|
|
4936
|
-
const [
|
|
4937
|
-
const [
|
|
4938
|
-
const [
|
|
4939
|
-
const [
|
|
4940
|
-
const
|
|
4941
|
-
const
|
|
4942
|
-
const
|
|
4943
|
-
const
|
|
4944
|
-
const
|
|
5884
|
+
const sectionsLoadedRef = (0, import_react5.useRef)(false);
|
|
5885
|
+
const pendingScheduleConfigRequests = (0, import_react5.useRef)([]);
|
|
5886
|
+
const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
|
|
5887
|
+
const [toggleState, setToggleState] = (0, import_react5.useState)(null);
|
|
5888
|
+
const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
|
|
5889
|
+
const [activeCommands, setActiveCommands] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
|
|
5890
|
+
const [sectionGap, setSectionGap] = (0, import_react5.useState)(null);
|
|
5891
|
+
const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react5.useState)(false);
|
|
5892
|
+
const [linkPopover, setLinkPopover] = (0, import_react5.useState)(null);
|
|
5893
|
+
const [sitePages, setSitePages] = (0, import_react5.useState)([]);
|
|
5894
|
+
const [sectionsByPath, setSectionsByPath] = (0, import_react5.useState)({});
|
|
5895
|
+
const sectionsPrefetchGenRef = (0, import_react5.useRef)(0);
|
|
5896
|
+
const setLinkPopoverRef = (0, import_react5.useRef)(setLinkPopover);
|
|
5897
|
+
const linkPopoverPanelRef = (0, import_react5.useRef)(null);
|
|
5898
|
+
const linkPopoverOpenRef = (0, import_react5.useRef)(false);
|
|
5899
|
+
const linkPopoverGraceUntilRef = (0, import_react5.useRef)(0);
|
|
4945
5900
|
setLinkPopoverRef.current = setLinkPopover;
|
|
4946
5901
|
const bumpLinkPopoverGrace = () => {
|
|
4947
5902
|
linkPopoverGraceUntilRef.current = Date.now() + 350;
|
|
4948
5903
|
};
|
|
4949
|
-
const runSectionsPrefetch = (0,
|
|
5904
|
+
const runSectionsPrefetch = (0, import_react5.useCallback)((pages) => {
|
|
4950
5905
|
if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
|
|
4951
5906
|
const gen = ++sectionsPrefetchGenRef.current;
|
|
4952
5907
|
const paths = pages.map((p) => p.path);
|
|
@@ -4965,9 +5920,9 @@ function OhhwellsBridge() {
|
|
|
4965
5920
|
);
|
|
4966
5921
|
});
|
|
4967
5922
|
}, [isEditMode, pathname]);
|
|
4968
|
-
const runSectionsPrefetchRef = (0,
|
|
5923
|
+
const runSectionsPrefetchRef = (0, import_react5.useRef)(runSectionsPrefetch);
|
|
4969
5924
|
runSectionsPrefetchRef.current = runSectionsPrefetch;
|
|
4970
|
-
(0,
|
|
5925
|
+
(0, import_react5.useEffect)(() => {
|
|
4971
5926
|
if (!linkPopover) return;
|
|
4972
5927
|
if (hoveredImageRef.current) {
|
|
4973
5928
|
hoveredImageRef.current = null;
|
|
@@ -4975,7 +5930,7 @@ function OhhwellsBridge() {
|
|
|
4975
5930
|
}
|
|
4976
5931
|
postToParent({ type: "ow:image-unhover" });
|
|
4977
5932
|
}, [linkPopover, postToParent]);
|
|
4978
|
-
(0,
|
|
5933
|
+
(0, import_react5.useEffect)(() => {
|
|
4979
5934
|
if (!isEditMode) return;
|
|
4980
5935
|
const useFixtures = shouldUseDevFixtures();
|
|
4981
5936
|
if (useFixtures) {
|
|
@@ -4999,14 +5954,14 @@ function OhhwellsBridge() {
|
|
|
4999
5954
|
if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
|
|
5000
5955
|
return () => window.removeEventListener("message", onSitePages);
|
|
5001
5956
|
}, [isEditMode, postToParent]);
|
|
5002
|
-
(0,
|
|
5957
|
+
(0, import_react5.useEffect)(() => {
|
|
5003
5958
|
if (!isEditMode || shouldUseDevFixtures()) return;
|
|
5004
5959
|
void loadAllSectionsManifest().then((manifest) => {
|
|
5005
5960
|
if (Object.keys(manifest).length === 0) return;
|
|
5006
5961
|
setSectionsByPath((prev) => ({ ...manifest, ...prev }));
|
|
5007
5962
|
});
|
|
5008
5963
|
}, [isEditMode]);
|
|
5009
|
-
(0,
|
|
5964
|
+
(0, import_react5.useEffect)(() => {
|
|
5010
5965
|
const update = () => {
|
|
5011
5966
|
const el = activeElRef.current;
|
|
5012
5967
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -5014,6 +5969,12 @@ function OhhwellsBridge() {
|
|
|
5014
5969
|
if (!prev || !activeStateElRef.current) return prev;
|
|
5015
5970
|
return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
|
|
5016
5971
|
});
|
|
5972
|
+
setSectionGap((prev) => {
|
|
5973
|
+
if (!prev) return prev;
|
|
5974
|
+
const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
|
|
5975
|
+
if (!anchor) return prev;
|
|
5976
|
+
return { ...prev, y: anchor.getBoundingClientRect().bottom };
|
|
5977
|
+
});
|
|
5017
5978
|
};
|
|
5018
5979
|
const vvp = window.visualViewport;
|
|
5019
5980
|
if (!vvp) return;
|
|
@@ -5024,10 +5985,33 @@ function OhhwellsBridge() {
|
|
|
5024
5985
|
vvp.removeEventListener("resize", update);
|
|
5025
5986
|
};
|
|
5026
5987
|
}, []);
|
|
5027
|
-
const refreshStateRules = (0,
|
|
5988
|
+
const refreshStateRules = (0, import_react5.useCallback)(() => {
|
|
5028
5989
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
5029
5990
|
}, []);
|
|
5030
|
-
const
|
|
5991
|
+
const processConfigRequest = (0, import_react5.useCallback)((insertAfterVal) => {
|
|
5992
|
+
const tracker = getSectionsTracker();
|
|
5993
|
+
let entries = [];
|
|
5994
|
+
try {
|
|
5995
|
+
entries = JSON.parse(tracker.textContent || "[]");
|
|
5996
|
+
} catch {
|
|
5997
|
+
}
|
|
5998
|
+
const path = window.location.pathname;
|
|
5999
|
+
const entry = entries.find(
|
|
6000
|
+
(e) => e.type === "scheduling" && e.insertAfter === insertAfterVal && (!e.pagePath || e.pagePath === path)
|
|
6001
|
+
);
|
|
6002
|
+
if (entry) {
|
|
6003
|
+
window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: entry.scheduleId ?? null }, "*");
|
|
6004
|
+
return;
|
|
6005
|
+
}
|
|
6006
|
+
const newEntry = { type: "scheduling", insertAfter: insertAfterVal, pagePath: path };
|
|
6007
|
+
entries.push(newEntry);
|
|
6008
|
+
tracker.textContent = JSON.stringify(entries);
|
|
6009
|
+
if (isEditMode) {
|
|
6010
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
6011
|
+
}
|
|
6012
|
+
window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
|
|
6013
|
+
}, [isEditMode]);
|
|
6014
|
+
const deactivate = (0, import_react5.useCallback)(() => {
|
|
5031
6015
|
const el = activeElRef.current;
|
|
5032
6016
|
if (!el) return;
|
|
5033
6017
|
const key = el.dataset.ohwKey;
|
|
@@ -5053,7 +6037,7 @@ function OhhwellsBridge() {
|
|
|
5053
6037
|
setToolbarShowEditLink(false);
|
|
5054
6038
|
postToParent({ type: "ow:exit-edit" });
|
|
5055
6039
|
}, [postToParent]);
|
|
5056
|
-
const activate = (0,
|
|
6040
|
+
const activate = (0, import_react5.useCallback)((el) => {
|
|
5057
6041
|
if (activeElRef.current === el) return;
|
|
5058
6042
|
deactivate();
|
|
5059
6043
|
if (hoveredImageRef.current) {
|
|
@@ -5072,7 +6056,7 @@ function OhhwellsBridge() {
|
|
|
5072
6056
|
}, [deactivate, postToParent]);
|
|
5073
6057
|
activateRef.current = activate;
|
|
5074
6058
|
deactivateRef.current = deactivate;
|
|
5075
|
-
(0,
|
|
6059
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
5076
6060
|
if (!subdomain || isEditMode) {
|
|
5077
6061
|
setFetchState("done");
|
|
5078
6062
|
return;
|
|
@@ -5080,6 +6064,7 @@ function OhhwellsBridge() {
|
|
|
5080
6064
|
const applyContent = (content) => {
|
|
5081
6065
|
const imageLoads = [];
|
|
5082
6066
|
for (const [key, val] of Object.entries(content)) {
|
|
6067
|
+
if (key === "__ohw_sections") continue;
|
|
5083
6068
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
5084
6069
|
if (el.dataset.ohwEditable === "image") {
|
|
5085
6070
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -5101,6 +6086,9 @@ function OhhwellsBridge() {
|
|
|
5101
6086
|
});
|
|
5102
6087
|
applyLinkByKey(key, val);
|
|
5103
6088
|
}
|
|
6089
|
+
initSectionsFromContent(content, true);
|
|
6090
|
+
sectionsLoadedRef.current = true;
|
|
6091
|
+
pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
|
|
5104
6092
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
5105
6093
|
}) : Promise.resolve();
|
|
5106
6094
|
};
|
|
@@ -5125,12 +6113,15 @@ function OhhwellsBridge() {
|
|
|
5125
6113
|
cancelled = true;
|
|
5126
6114
|
};
|
|
5127
6115
|
}, [subdomain, isEditMode, pathname]);
|
|
5128
|
-
(0,
|
|
6116
|
+
(0, import_react5.useEffect)(() => {
|
|
5129
6117
|
if (!subdomain || isEditMode) return;
|
|
6118
|
+
let debounceTimer = null;
|
|
5130
6119
|
const applyFromCache = () => {
|
|
5131
6120
|
const content = contentCache.get(subdomain);
|
|
5132
6121
|
if (!content) return;
|
|
6122
|
+
retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
|
|
5133
6123
|
for (const [key, val] of Object.entries(content)) {
|
|
6124
|
+
if (key === "__ohw_sections") continue;
|
|
5134
6125
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
5135
6126
|
if (el.dataset.ohwEditable === "image") {
|
|
5136
6127
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -5147,21 +6138,28 @@ function OhhwellsBridge() {
|
|
|
5147
6138
|
applyLinkByKey(key, val);
|
|
5148
6139
|
}
|
|
5149
6140
|
};
|
|
5150
|
-
|
|
5151
|
-
|
|
6141
|
+
const scheduleApply = () => {
|
|
6142
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
6143
|
+
debounceTimer = setTimeout(applyFromCache, 150);
|
|
6144
|
+
};
|
|
6145
|
+
scheduleApply();
|
|
6146
|
+
const observer = new MutationObserver(scheduleApply);
|
|
5152
6147
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
5153
|
-
return () =>
|
|
5154
|
-
|
|
5155
|
-
|
|
6148
|
+
return () => {
|
|
6149
|
+
observer.disconnect();
|
|
6150
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
6151
|
+
};
|
|
6152
|
+
}, [subdomain, isEditMode, pathname]);
|
|
6153
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
5156
6154
|
const el = document.getElementById("ohw-loader");
|
|
5157
6155
|
if (!el) return;
|
|
5158
6156
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
5159
6157
|
el.style.display = visible ? "flex" : "none";
|
|
5160
6158
|
}, [subdomain, fetchState]);
|
|
5161
|
-
(0,
|
|
6159
|
+
(0, import_react5.useEffect)(() => {
|
|
5162
6160
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
5163
6161
|
}, [pathname, postToParent]);
|
|
5164
|
-
(0,
|
|
6162
|
+
(0, import_react5.useEffect)(() => {
|
|
5165
6163
|
if (!isEditMode) return;
|
|
5166
6164
|
const measure = () => {
|
|
5167
6165
|
const h = document.body.scrollHeight;
|
|
@@ -5185,7 +6183,7 @@ function OhhwellsBridge() {
|
|
|
5185
6183
|
window.removeEventListener("resize", handleResize);
|
|
5186
6184
|
};
|
|
5187
6185
|
}, [pathname, isEditMode, postToParent]);
|
|
5188
|
-
(0,
|
|
6186
|
+
(0, import_react5.useEffect)(() => {
|
|
5189
6187
|
if (!subdomainFromQuery || isEditMode) return;
|
|
5190
6188
|
const handleClick = (e) => {
|
|
5191
6189
|
const anchor = e.target.closest("a");
|
|
@@ -5201,7 +6199,7 @@ function OhhwellsBridge() {
|
|
|
5201
6199
|
document.addEventListener("click", handleClick, true);
|
|
5202
6200
|
return () => document.removeEventListener("click", handleClick, true);
|
|
5203
6201
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
5204
|
-
(0,
|
|
6202
|
+
(0, import_react5.useEffect)(() => {
|
|
5205
6203
|
if (!isEditMode) {
|
|
5206
6204
|
editStylesRef.current?.base.remove();
|
|
5207
6205
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -5502,6 +6500,15 @@ function OhhwellsBridge() {
|
|
|
5502
6500
|
textEditable.setAttribute("data-ohw-hovered", "");
|
|
5503
6501
|
return;
|
|
5504
6502
|
}
|
|
6503
|
+
if (hoveredGapRef.current) {
|
|
6504
|
+
if (hoveredImageRef.current) {
|
|
6505
|
+
hoveredImageRef.current = null;
|
|
6506
|
+
resumeAnimTracks();
|
|
6507
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
6508
|
+
}
|
|
6509
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
6510
|
+
return;
|
|
6511
|
+
}
|
|
5505
6512
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
5506
6513
|
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
5507
6514
|
hoveredImageRef.current = imgEl;
|
|
@@ -5567,8 +6574,30 @@ function OhhwellsBridge() {
|
|
|
5567
6574
|
}
|
|
5568
6575
|
}
|
|
5569
6576
|
};
|
|
6577
|
+
const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
|
|
6578
|
+
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
6579
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
6580
|
+
const ZONE = 20;
|
|
6581
|
+
for (let i = 0; i < sections.length; i++) {
|
|
6582
|
+
const a = sections[i];
|
|
6583
|
+
const b = sections[i + 1] ?? null;
|
|
6584
|
+
const boundaryY = a.getBoundingClientRect().bottom;
|
|
6585
|
+
if (Math.abs(y - boundaryY) <= ZONE) {
|
|
6586
|
+
const insertAfter = a.dataset.ohwSection ?? "";
|
|
6587
|
+
const insertBefore = b?.dataset.ohwSection ?? null;
|
|
6588
|
+
hoveredGapRef.current = { insertAfter, insertBefore };
|
|
6589
|
+
setSectionGap({ insertAfter, insertBefore, y: boundaryY });
|
|
6590
|
+
return;
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
6593
|
+
if (hoveredGapRef.current) {
|
|
6594
|
+
hoveredGapRef.current = null;
|
|
6595
|
+
setSectionGap(null);
|
|
6596
|
+
}
|
|
6597
|
+
};
|
|
5570
6598
|
const handleMouseMove = (e) => {
|
|
5571
6599
|
const { clientX, clientY } = e;
|
|
6600
|
+
probeSectionGapAt(clientX, clientY);
|
|
5572
6601
|
probeImageAt(clientX, clientY);
|
|
5573
6602
|
probeHoverCardsAt(clientX, clientY);
|
|
5574
6603
|
};
|
|
@@ -5576,6 +6605,7 @@ function OhhwellsBridge() {
|
|
|
5576
6605
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
5577
6606
|
const { clientX, clientY } = e.data;
|
|
5578
6607
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
6608
|
+
probeSectionGapAt(clientX, clientY);
|
|
5579
6609
|
probeImageAt(clientX, clientY);
|
|
5580
6610
|
probeHoverCardsAt(clientX, clientY);
|
|
5581
6611
|
};
|
|
@@ -5733,7 +6763,12 @@ function OhhwellsBridge() {
|
|
|
5733
6763
|
if (e.data?.type !== "ow:hydrate") return;
|
|
5734
6764
|
const content = e.data.content;
|
|
5735
6765
|
if (!content) return;
|
|
6766
|
+
let sectionsJson = null;
|
|
5736
6767
|
for (const [key, val] of Object.entries(content)) {
|
|
6768
|
+
if (key === "__ohw_sections") {
|
|
6769
|
+
sectionsJson = val;
|
|
6770
|
+
continue;
|
|
6771
|
+
}
|
|
5737
6772
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
5738
6773
|
if (el.dataset.ohwEditable === "image") {
|
|
5739
6774
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -5748,6 +6783,11 @@ function OhhwellsBridge() {
|
|
|
5748
6783
|
});
|
|
5749
6784
|
applyLinkByKey(key, val);
|
|
5750
6785
|
}
|
|
6786
|
+
if (sectionsJson) {
|
|
6787
|
+
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
6788
|
+
sectionsLoadedRef.current = true;
|
|
6789
|
+
pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
|
|
6790
|
+
}
|
|
5751
6791
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
5752
6792
|
};
|
|
5753
6793
|
window.addEventListener("message", handleHydrate);
|
|
@@ -5800,7 +6840,63 @@ function OhhwellsBridge() {
|
|
|
5800
6840
|
};
|
|
5801
6841
|
const handleSave = (e) => {
|
|
5802
6842
|
if (e.data?.type !== "ow:save") return;
|
|
5803
|
-
|
|
6843
|
+
const nodes = collectEditableNodes();
|
|
6844
|
+
const tracker = document.querySelector("[data-ohw-sections-tracker]");
|
|
6845
|
+
if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
|
|
6846
|
+
postToParentRef.current({ type: "ow:save-result", nodes });
|
|
6847
|
+
};
|
|
6848
|
+
const handleInsertSection = (e) => {
|
|
6849
|
+
if (e.data?.type !== "ow:insert-section") return;
|
|
6850
|
+
const { widgetType, insertAfter, insertBefore } = e.data;
|
|
6851
|
+
if (widgetType !== "scheduling") return;
|
|
6852
|
+
const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
|
|
6853
|
+
if (inserted) {
|
|
6854
|
+
const tracker = getSectionsTracker();
|
|
6855
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
|
|
6856
|
+
const h = document.documentElement.scrollHeight;
|
|
6857
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
6858
|
+
}
|
|
6859
|
+
};
|
|
6860
|
+
const handleSwitchSchedule = (e) => {
|
|
6861
|
+
if (e.data?.type !== "ow:switch-schedule") return;
|
|
6862
|
+
const scheduleId = e.data.scheduleId;
|
|
6863
|
+
const insertAfter = e.data.insertAfter;
|
|
6864
|
+
if (!scheduleId || !insertAfter) return;
|
|
6865
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
6866
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6867
|
+
};
|
|
6868
|
+
const handleScheduleLinked = (e) => {
|
|
6869
|
+
if (e.data?.type !== "ow:schedule-linked") return;
|
|
6870
|
+
const scheduleId = e.data.scheduleId;
|
|
6871
|
+
const insertAfter = e.data.insertAfter;
|
|
6872
|
+
if (!scheduleId || !insertAfter) return;
|
|
6873
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
6874
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6875
|
+
};
|
|
6876
|
+
const handleClearSchedulingWidget = (e) => {
|
|
6877
|
+
if (e.data?.type !== "ow:clear-scheduling-widget") return;
|
|
6878
|
+
const insertAfter = e.data.insertAfter;
|
|
6879
|
+
if (!insertAfter) return;
|
|
6880
|
+
const text = updateSectionScheduleId(insertAfter, null);
|
|
6881
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6882
|
+
};
|
|
6883
|
+
const handleRemoveSchedulingSection = (e) => {
|
|
6884
|
+
if (e.data?.type !== "ow:remove-scheduling-section") return;
|
|
6885
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
|
|
6886
|
+
el.remove();
|
|
6887
|
+
});
|
|
6888
|
+
const tracker = getSectionsTracker();
|
|
6889
|
+
let sections = [];
|
|
6890
|
+
try {
|
|
6891
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
6892
|
+
} catch {
|
|
6893
|
+
}
|
|
6894
|
+
const currentPath = window.location.pathname;
|
|
6895
|
+
const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
|
|
6896
|
+
tracker.textContent = JSON.stringify(updated);
|
|
6897
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
6898
|
+
const h = document.documentElement.scrollHeight;
|
|
6899
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
5804
6900
|
};
|
|
5805
6901
|
const handleSelectionChange = () => {
|
|
5806
6902
|
if (!activeElRef.current) return;
|
|
@@ -5905,6 +7001,11 @@ function OhhwellsBridge() {
|
|
|
5905
7001
|
deactivateRef.current();
|
|
5906
7002
|
};
|
|
5907
7003
|
window.addEventListener("message", handleSave);
|
|
7004
|
+
window.addEventListener("message", handleInsertSection);
|
|
7005
|
+
window.addEventListener("message", handleSwitchSchedule);
|
|
7006
|
+
window.addEventListener("message", handleScheduleLinked);
|
|
7007
|
+
window.addEventListener("message", handleClearSchedulingWidget);
|
|
7008
|
+
window.addEventListener("message", handleRemoveSchedulingSection);
|
|
5908
7009
|
window.addEventListener("message", handleImageUrl);
|
|
5909
7010
|
window.addEventListener("message", handleAnimLock);
|
|
5910
7011
|
window.addEventListener("message", handleCanvasHeight);
|
|
@@ -5939,6 +7040,11 @@ function OhhwellsBridge() {
|
|
|
5939
7040
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
5940
7041
|
window.removeEventListener("scroll", handleScroll, true);
|
|
5941
7042
|
window.removeEventListener("message", handleSave);
|
|
7043
|
+
window.removeEventListener("message", handleInsertSection);
|
|
7044
|
+
window.removeEventListener("message", handleSwitchSchedule);
|
|
7045
|
+
window.removeEventListener("message", handleScheduleLinked);
|
|
7046
|
+
window.removeEventListener("message", handleClearSchedulingWidget);
|
|
7047
|
+
window.removeEventListener("message", handleRemoveSchedulingSection);
|
|
5942
7048
|
window.removeEventListener("message", handleImageUrl);
|
|
5943
7049
|
window.removeEventListener("message", handleAnimLock);
|
|
5944
7050
|
window.removeEventListener("message", handleCanvasHeight);
|
|
@@ -5953,7 +7059,23 @@ function OhhwellsBridge() {
|
|
|
5953
7059
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
5954
7060
|
};
|
|
5955
7061
|
}, [isEditMode, refreshStateRules]);
|
|
5956
|
-
(0,
|
|
7062
|
+
(0, import_react5.useEffect)(() => {
|
|
7063
|
+
const handler = (e) => {
|
|
7064
|
+
if (e.data?.type !== "ow:request-schedule-config") return;
|
|
7065
|
+
const insertAfterVal = e.data.insertAfter;
|
|
7066
|
+
if (!insertAfterVal) return;
|
|
7067
|
+
if (!sectionsLoadedRef.current) {
|
|
7068
|
+
if (!pendingScheduleConfigRequests.current.includes(insertAfterVal)) {
|
|
7069
|
+
pendingScheduleConfigRequests.current.push(insertAfterVal);
|
|
7070
|
+
}
|
|
7071
|
+
return;
|
|
7072
|
+
}
|
|
7073
|
+
processConfigRequest(insertAfterVal);
|
|
7074
|
+
};
|
|
7075
|
+
window.addEventListener("message", handler);
|
|
7076
|
+
return () => window.removeEventListener("message", handler);
|
|
7077
|
+
}, [processConfigRequest]);
|
|
7078
|
+
(0, import_react5.useEffect)(() => {
|
|
5957
7079
|
if (!isEditMode) return;
|
|
5958
7080
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
5959
7081
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -5982,19 +7104,19 @@ function OhhwellsBridge() {
|
|
|
5982
7104
|
clearTimeout(timer);
|
|
5983
7105
|
};
|
|
5984
7106
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
5985
|
-
(0,
|
|
7107
|
+
(0, import_react5.useEffect)(() => {
|
|
5986
7108
|
scrollToHashSectionWhenReady();
|
|
5987
7109
|
const onHashChange = () => scrollToHashSectionWhenReady();
|
|
5988
7110
|
window.addEventListener("hashchange", onHashChange);
|
|
5989
7111
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
5990
7112
|
}, [pathname]);
|
|
5991
|
-
const handleCommand = (0,
|
|
7113
|
+
const handleCommand = (0, import_react5.useCallback)((cmd) => {
|
|
5992
7114
|
document.execCommand(cmd, false);
|
|
5993
7115
|
activeElRef.current?.focus();
|
|
5994
7116
|
if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
|
|
5995
7117
|
refreshActiveCommandsRef.current();
|
|
5996
7118
|
}, []);
|
|
5997
|
-
const handleStateChange = (0,
|
|
7119
|
+
const handleStateChange = (0, import_react5.useCallback)((state) => {
|
|
5998
7120
|
if (!activeStateElRef.current) return;
|
|
5999
7121
|
const el = activeStateElRef.current;
|
|
6000
7122
|
if (state === "Default") {
|
|
@@ -6007,8 +7129,8 @@ function OhhwellsBridge() {
|
|
|
6007
7129
|
}
|
|
6008
7130
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
6009
7131
|
}, [deactivate]);
|
|
6010
|
-
const closeLinkPopover = (0,
|
|
6011
|
-
const openLinkPopoverForActive = (0,
|
|
7132
|
+
const closeLinkPopover = (0, import_react5.useCallback)(() => setLinkPopover(null), []);
|
|
7133
|
+
const openLinkPopoverForActive = (0, import_react5.useCallback)(() => {
|
|
6012
7134
|
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6013
7135
|
if (!hrefCtx) return;
|
|
6014
7136
|
bumpLinkPopoverGrace();
|
|
@@ -6018,7 +7140,7 @@ function OhhwellsBridge() {
|
|
|
6018
7140
|
rect: hrefCtx.anchor.getBoundingClientRect()
|
|
6019
7141
|
});
|
|
6020
7142
|
}, []);
|
|
6021
|
-
const handleLinkPopoverSubmit = (0,
|
|
7143
|
+
const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
|
|
6022
7144
|
(target) => {
|
|
6023
7145
|
if (!linkPopover) return;
|
|
6024
7146
|
const { key } = linkPopover;
|
|
@@ -6031,11 +7153,11 @@ function OhhwellsBridge() {
|
|
|
6031
7153
|
const showEditLink = toolbarShowEditLink;
|
|
6032
7154
|
const currentSections = sectionsByPath[pathname] ?? [];
|
|
6033
7155
|
linkPopoverOpenRef.current = linkPopover !== null;
|
|
6034
|
-
return bridgeRoot ? (0,
|
|
6035
|
-
/* @__PURE__ */ (0,
|
|
6036
|
-
toolbarRect && /* @__PURE__ */ (0,
|
|
6037
|
-
/* @__PURE__ */ (0,
|
|
6038
|
-
/* @__PURE__ */ (0,
|
|
7156
|
+
return bridgeRoot ? (0, import_react_dom2.createPortal)(
|
|
7157
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
7158
|
+
toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
7159
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
7160
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6039
7161
|
FloatingToolbar,
|
|
6040
7162
|
{
|
|
6041
7163
|
rect: toolbarRect,
|
|
@@ -6048,7 +7170,7 @@ function OhhwellsBridge() {
|
|
|
6048
7170
|
}
|
|
6049
7171
|
)
|
|
6050
7172
|
] }),
|
|
6051
|
-
maxBadge && /* @__PURE__ */ (0,
|
|
7173
|
+
maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
6052
7174
|
"div",
|
|
6053
7175
|
{
|
|
6054
7176
|
"data-ohw-max-badge": "",
|
|
@@ -6074,7 +7196,7 @@ function OhhwellsBridge() {
|
|
|
6074
7196
|
]
|
|
6075
7197
|
}
|
|
6076
7198
|
),
|
|
6077
|
-
toggleState && /* @__PURE__ */ (0,
|
|
7199
|
+
toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6078
7200
|
StateToggle,
|
|
6079
7201
|
{
|
|
6080
7202
|
rect: toggleState.rect,
|
|
@@ -6083,7 +7205,32 @@ function OhhwellsBridge() {
|
|
|
6083
7205
|
onStateChange: handleStateChange
|
|
6084
7206
|
}
|
|
6085
7207
|
),
|
|
6086
|
-
|
|
7208
|
+
sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
7209
|
+
"div",
|
|
7210
|
+
{
|
|
7211
|
+
"data-ohw-section-insert-line": "",
|
|
7212
|
+
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
7213
|
+
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
7214
|
+
children: [
|
|
7215
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
7216
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
7217
|
+
Badge,
|
|
7218
|
+
{
|
|
7219
|
+
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",
|
|
7220
|
+
onClick: () => {
|
|
7221
|
+
window.parent.postMessage(
|
|
7222
|
+
{ type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
|
|
7223
|
+
"*"
|
|
7224
|
+
);
|
|
7225
|
+
},
|
|
7226
|
+
children: "Add Section"
|
|
7227
|
+
}
|
|
7228
|
+
),
|
|
7229
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
7230
|
+
]
|
|
7231
|
+
}
|
|
7232
|
+
),
|
|
7233
|
+
linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6087
7234
|
LinkPopover,
|
|
6088
7235
|
{
|
|
6089
7236
|
rect: linkPopover.rect,
|
|
@@ -6108,6 +7255,7 @@ function OhhwellsBridge() {
|
|
|
6108
7255
|
LinkEditorPanel,
|
|
6109
7256
|
LinkPopover,
|
|
6110
7257
|
OhhwellsBridge,
|
|
7258
|
+
SchedulingWidget,
|
|
6111
7259
|
Toggle,
|
|
6112
7260
|
ToggleGroup,
|
|
6113
7261
|
ToggleGroupItem,
|