@ohhwells/bridge 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -43,6 +43,7 @@ module.exports = __toCommonJS(index_exports);
43
43
  // src/OhhwellsBridge.tsx
44
44
  var import_react3 = __toESM(require("react"), 1);
45
45
  var import_client = require("react-dom/client");
46
+ var import_react_dom = require("react-dom");
46
47
 
47
48
  // src/ui/SchedulingWidget.tsx
48
49
  var import_react2 = require("react");
@@ -255,10 +256,10 @@ function buildTimezoneLabel(tz) {
255
256
  return tz;
256
257
  }
257
258
  }
258
- function EmptyState({ inEditor }) {
259
+ function EmptyState({ inEditor, insertAfter }) {
259
260
  const handleAddSchedule = () => {
260
261
  if (inEditor) {
261
- window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
262
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
262
263
  }
263
264
  };
264
265
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
@@ -310,7 +311,19 @@ function LoadingSkeleton() {
310
311
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
311
312
  ] }, i)) }),
312
313
  [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
313
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-[60px] py-4", children: [
314
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:hidden", children: [
315
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0", children: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
317
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
319
+ ] }),
320
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2", children: [
321
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
323
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
324
+ ] })
325
+ ] }),
326
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
314
327
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
315
328
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
316
329
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
@@ -353,6 +366,28 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
353
366
  const scroll = (dir) => {
354
367
  scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
355
368
  };
369
+ const isDragging = (0, import_react2.useRef)(false);
370
+ const dragStartX = (0, import_react2.useRef)(0);
371
+ const dragStartScrollLeft = (0, import_react2.useRef)(0);
372
+ const onDragStart = (e) => {
373
+ if (!scrollRef.current) return;
374
+ isDragging.current = true;
375
+ dragStartX.current = e.clientX;
376
+ dragStartScrollLeft.current = scrollRef.current.scrollLeft;
377
+ scrollRef.current.style.cursor = "grabbing";
378
+ scrollRef.current.style.userSelect = "none";
379
+ };
380
+ const onDragMove = (e) => {
381
+ if (!isDragging.current || !scrollRef.current) return;
382
+ const delta = e.clientX - dragStartX.current;
383
+ scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
384
+ };
385
+ const onDragEnd = () => {
386
+ if (!scrollRef.current) return;
387
+ isDragging.current = false;
388
+ scrollRef.current.style.cursor = "grab";
389
+ scrollRef.current.style.userSelect = "";
390
+ };
356
391
  const datesWithClasses = (0, import_react2.useMemo)(
357
392
  () => new Set(dates.map(
358
393
  (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
@@ -370,7 +405,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
370
405
  "button",
371
406
  {
372
407
  onClick: () => scroll("left"),
373
- className: "absolute left-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 flex items-center justify-center rounded-full border-none cursor-pointer",
408
+ className: "hidden sm:flex absolute left-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
374
409
  style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
375
410
  "aria-label": "Scroll left",
376
411
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "15 18 9 12 15 6" }) })
@@ -380,23 +415,36 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
380
415
  "button",
381
416
  {
382
417
  onClick: () => scroll("right"),
383
- className: "absolute right-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 flex items-center justify-center rounded-full border-none cursor-pointer",
418
+ className: "hidden sm:flex absolute right-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
384
419
  style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
385
420
  "aria-label": "Scroll right",
386
421
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "9 18 15 12 9 6" }) })
387
422
  }
388
423
  ),
424
+ canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
425
+ "div",
426
+ {
427
+ className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
428
+ style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
429
+ }
430
+ ),
431
+ canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
432
+ "div",
433
+ {
434
+ className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
435
+ style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
436
+ }
437
+ ),
389
438
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
390
439
  "div",
391
440
  {
392
441
  ref: scrollRef,
393
442
  className: "overflow-x-auto w-full",
394
- style: {
395
- scrollbarWidth: "none",
396
- msOverflowStyle: "none",
397
- paddingLeft: canScrollLeft ? "36px" : 0,
398
- paddingRight: canScrollRight ? "36px" : 0
399
- },
443
+ style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
444
+ onMouseDown: onDragStart,
445
+ onMouseMove: onDragMove,
446
+ onMouseUp: onDragEnd,
447
+ onMouseLeave: onDragEnd,
400
448
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
401
449
  const isToday = date.getTime() === todayMs;
402
450
  const isSelected = i === selectedIdx;
@@ -437,40 +485,60 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
437
485
  const available = cls.maxParticipants - booked;
438
486
  const isFull = available <= 0;
439
487
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
440
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-[60px] py-4 box-border", children: [
441
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-[120px] shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
442
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-3 min-w-0", children: [
443
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
444
- cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
488
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
489
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
492
+ "span",
493
+ {
494
+ className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
495
+ style: { borderColor: "#0885FE", color: "#0885FE" },
496
+ children: "GROUP"
497
+ }
498
+ ),
499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
501
+ available,
502
+ "/",
503
+ cls.maxParticipants
504
+ ] }),
505
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
506
+ ] }) })
445
507
  ] }),
446
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-14 shrink-0 flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
447
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
448
- available,
449
- "/",
450
- cls.maxParticipants
508
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
509
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
510
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
511
+ cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
451
512
  ] }),
452
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
453
- ] }) }),
454
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
455
- "button",
456
- {
457
- className: "shrink-0 w-[200px] px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
458
- style: isFull ? {
459
- background: "transparent",
460
- border: "1px solid var(--color-dark, #200C02)",
461
- color: "var(--color-dark, #200C02)"
462
- } : {
463
- background: "var(--color-primary, #3D312B)",
464
- border: "none",
465
- color: "var(--color-light, #FEFFF0)"
466
- },
467
- onClick: () => {
468
- if (!cls.id) return;
469
- onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
470
- },
471
- children: isFull ? "Join Waitlist" : "Book Now"
472
- }
473
- )
513
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
515
+ available,
516
+ "/",
517
+ cls.maxParticipants
518
+ ] }),
519
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
520
+ ] }) }),
521
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
522
+ "button",
523
+ {
524
+ className: "w-full sm:w-[200px] sm:shrink-0 px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
525
+ style: isFull ? {
526
+ background: "transparent",
527
+ border: "1px solid var(--color-dark, #200C02)",
528
+ color: "var(--color-dark, #200C02)"
529
+ } : {
530
+ background: "var(--color-primary, #3D312B)",
531
+ border: "none",
532
+ color: "var(--color-light, #FEFFF0)"
533
+ },
534
+ onClick: () => {
535
+ if (!cls.id) return;
536
+ onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
537
+ },
538
+ children: isFull ? "Join Waitlist" : "Book Now"
539
+ }
540
+ )
541
+ ] })
474
542
  ] }),
475
543
  i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
476
544
  ] }, cls.id ?? i);
@@ -500,7 +568,7 @@ function CalendarFoldIcon() {
500
568
  }
501
569
  );
502
570
  }
503
- function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
571
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
504
572
  const [schedule, setSchedule] = (0, import_react2.useState)(null);
505
573
  const [loading, setLoading] = (0, import_react2.useState)(true);
506
574
  const [inEditor, setInEditor] = (0, import_react2.useState)(false);
@@ -542,18 +610,21 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
542
610
  }, 5e3) : null;
543
611
  const handler = (e) => {
544
612
  if (e.data?.type === "ow:clear-scheduling-widget") {
613
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
545
614
  setSchedule(null);
546
615
  setLoading(false);
547
616
  return;
548
617
  }
549
618
  if (e.data?.type === "ow:switch-schedule") {
619
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
550
620
  switchScheduleIdRef.current = e.data.scheduleId ?? null;
551
621
  initialized = false;
552
622
  setLoading(true);
553
- window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
623
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
554
624
  return;
555
625
  }
556
626
  if (e.data?.type !== "ow:user-schedules-response") return;
627
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
557
628
  if (initialized && switchScheduleIdRef.current === null) return;
558
629
  initialized = true;
559
630
  if (timer) clearTimeout(timer);
@@ -564,11 +635,16 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
564
635
  setSchedule(target);
565
636
  setLoading(false);
566
637
  if (notifyOnConnect && target && !isSwitching) {
567
- window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: target.id, name: target.name } }, "*");
638
+ window.parent.postMessage({
639
+ type: "ow:schedule-connected",
640
+ schedule: { id: target.id, name: target.name },
641
+ insertAfter
642
+ }, "*");
643
+ window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
568
644
  }
569
645
  };
570
646
  window.addEventListener("message", handler);
571
- if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
647
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
572
648
  return () => {
573
649
  window.removeEventListener("message", handler);
574
650
  if (timer) clearTimeout(timer);
@@ -629,16 +705,23 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
629
705
  const handleReplaceSchedule = () => {
630
706
  setIsHovered(false);
631
707
  if (schedule) {
632
- window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: schedule.id, name: schedule.name } }, "*");
708
+ window.parent.postMessage({
709
+ type: "ow:schedule-connected",
710
+ schedule: { id: schedule.id, name: schedule.name },
711
+ insertAfter
712
+ }, "*");
633
713
  } else {
634
- window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
714
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
635
715
  }
636
716
  };
717
+ if (!inEditor && !loading && !schedule) return null;
718
+ const sectionId = `scheduling-${insertAfter}`;
637
719
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
638
720
  "section",
639
721
  {
640
- "data-ohw-section": "scheduling",
641
- className: "w-full box-border py-20 px-16 font-body bg-(--color-light,#FEFFF0) relative",
722
+ "data-ohw-section": sectionId,
723
+ "data-ohw-scheduling-anchor": insertAfter,
724
+ className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
642
725
  onMouseEnter: () => inEditor && setIsHovered(true),
643
726
  onMouseLeave: () => setIsHovered(false),
644
727
  children: [
@@ -672,12 +755,12 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
672
755
  ),
673
756
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
674
757
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
675
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
758
+ /* @__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" }),
676
759
  schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
677
760
  ] }),
678
761
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
679
762
  timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
680
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
763
+ /* @__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)(
681
764
  ScheduleView,
682
765
  {
683
766
  schedule,
@@ -4112,7 +4195,7 @@ function ToggleGroupItem({
4112
4195
  }
4113
4196
 
4114
4197
  // src/OhhwellsBridge.tsx
4115
- var import_react_dom = require("react-dom");
4198
+ var import_react_dom2 = require("react-dom");
4116
4199
  var import_navigation = require("next/navigation");
4117
4200
 
4118
4201
  // src/lib/session-search.ts
@@ -4217,24 +4300,136 @@ function getSectionsTracker() {
4217
4300
  }
4218
4301
  return el;
4219
4302
  }
4220
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId) {
4221
- const anchor = document.querySelector(`[data-ohw-section="${insertAfter}"]`);
4222
- if (!anchor) return false;
4223
- if (anchor.nextElementSibling?.dataset.ohwSectionContainer) return false;
4303
+ function updateSectionScheduleId(insertAfter, scheduleId) {
4304
+ const tracker = getSectionsTracker();
4305
+ let sections = [];
4306
+ try {
4307
+ sections = JSON.parse(tracker.textContent || "[]");
4308
+ } catch {
4309
+ }
4310
+ const currentPath = window.location.pathname;
4311
+ const updated = sections.map((s) => {
4312
+ if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
4313
+ if (s.pagePath && s.pagePath !== currentPath) return s;
4314
+ return { ...s, scheduleId };
4315
+ });
4316
+ tracker.textContent = JSON.stringify(updated);
4317
+ return tracker.textContent ?? "[]";
4318
+ }
4319
+ function schedulingSectionId(insertAfter) {
4320
+ return `scheduling-${insertAfter}`;
4321
+ }
4322
+ var INSERT_BEFORE_MARKER = ":before:";
4323
+ function parseSchedulingInsertAfter(insertAfter) {
4324
+ const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
4325
+ if (idx < 0) return { anchor: insertAfter, insertBefore: null };
4326
+ return {
4327
+ anchor: insertAfter.slice(0, idx),
4328
+ insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
4329
+ };
4330
+ }
4331
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
4332
+ const parsed = parseSchedulingInsertAfter(insertAfter);
4333
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
4334
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
4335
+ return { effectiveInsertAfter, insertBefore };
4336
+ }
4337
+ function getSchedulingMountPoint(insertAfter) {
4338
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
4339
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
4340
+ if (!anchorEl && anchor === "scheduling") {
4341
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
4342
+ anchorEl = widgets.at(-1) ?? null;
4343
+ }
4344
+ if (!anchorEl) return null;
4345
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
4346
+ }
4347
+ function schedulingMountDepth(insertAfter) {
4348
+ if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
4349
+ return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
4350
+ }
4351
+ function getPageSchedulingEntries(raw) {
4352
+ if (!raw) return [];
4353
+ try {
4354
+ const entries = JSON.parse(raw);
4355
+ const currentPath = window.location.pathname;
4356
+ return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
4357
+ } catch {
4358
+ return [];
4359
+ }
4360
+ }
4361
+ function isSchedulingWidgetMissing(entry) {
4362
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
4363
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
4364
+ }
4365
+ function hasMissingSchedulingWidgets(entries) {
4366
+ return entries.some(isSchedulingWidgetMissing);
4367
+ }
4368
+ function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
4369
+ if (!hasMissingSchedulingWidgets(entries)) return;
4370
+ mountSchedulingEntries(entries, notifyOnConnect);
4371
+ }
4372
+ function initSectionsFromContent(content, removeExisting = false) {
4373
+ const raw = content["__ohw_sections"];
4374
+ if (!raw) return;
4375
+ try {
4376
+ if (removeExisting) {
4377
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
4378
+ }
4379
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
4380
+ if (inEditor) getSectionsTracker().textContent = raw;
4381
+ const pageEntries = getPageSchedulingEntries(raw);
4382
+ const notifyForEntry = inEditor ? (e) => !e.scheduleId : false;
4383
+ mountSchedulingEntries(pageEntries, notifyForEntry);
4384
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
4385
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
4386
+ } catch {
4387
+ }
4388
+ }
4389
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
4390
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
4391
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
4392
+ if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
4393
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
4394
+ if (!mountPoint) return false;
4224
4395
  const container = document.createElement("div");
4225
4396
  container.dataset.ohwSectionContainer = "scheduling";
4226
- anchor.insertAdjacentElement("afterend", container);
4227
- (0, import_client.createRoot)(container).render(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SchedulingWidget, { notifyOnConnect, initialScheduleId: scheduleId }));
4397
+ if (insertBefore) {
4398
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
4399
+ const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
4400
+ if (!beforePoint) return false;
4401
+ beforePoint.insertAdjacentElement("beforebegin", container);
4402
+ } else {
4403
+ let tail = mountPoint;
4404
+ while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
4405
+ tail = tail.nextElementSibling;
4406
+ }
4407
+ tail.insertAdjacentElement("afterend", container);
4408
+ }
4409
+ const root = (0, import_client.createRoot)(container);
4410
+ (0, import_react_dom.flushSync)(() => {
4411
+ root.render(
4412
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4413
+ SchedulingWidget,
4414
+ {
4415
+ notifyOnConnect,
4416
+ initialScheduleId: scheduleId,
4417
+ insertAfter: effectiveInsertAfter
4418
+ }
4419
+ )
4420
+ );
4421
+ });
4228
4422
  const tracker = getSectionsTracker();
4229
4423
  let sections = [];
4230
4424
  try {
4231
4425
  sections = JSON.parse(tracker.textContent || "[]");
4232
4426
  } catch {
4233
4427
  }
4234
- if (!sections.find((s) => s.type === "scheduling" && s.insertAfter === insertAfter)) {
4428
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
4429
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
4235
4430
  sections.push({
4236
4431
  type: "scheduling",
4237
- insertAfter,
4432
+ insertAfter: effectiveInsertAfter,
4238
4433
  pagePath: window.location.pathname,
4239
4434
  ...scheduleId ? { scheduleId } : {}
4240
4435
  });
@@ -4242,6 +4437,18 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId)
4242
4437
  }
4243
4438
  return true;
4244
4439
  }
4440
+ function mountSchedulingEntries(entries, notifyOnConnect = false) {
4441
+ const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
4442
+ for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
4443
+ for (let i = pending.length - 1; i >= 0; i--) {
4444
+ const entry = pending[i];
4445
+ const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
4446
+ if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
4447
+ pending.splice(i, 1);
4448
+ }
4449
+ }
4450
+ }
4451
+ }
4245
4452
  function collectEditableNodes() {
4246
4453
  return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
4247
4454
  if (el.dataset.ohwEditable === "image") {
@@ -4555,6 +4762,23 @@ function StateToggle({
4555
4762
  );
4556
4763
  }
4557
4764
  var contentCache = /* @__PURE__ */ new Map();
4765
+ function resolveSubdomain(subdomainFromQuery) {
4766
+ if (subdomainFromQuery) return subdomainFromQuery;
4767
+ if (typeof window !== "undefined") {
4768
+ const parts = window.location.hostname.split(".");
4769
+ if (parts.length >= 3 && parts[0] !== "www") return parts[0];
4770
+ }
4771
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
4772
+ if (siteUrl) {
4773
+ try {
4774
+ const host = new URL(siteUrl).hostname;
4775
+ const siteParts = host.split(".");
4776
+ if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
4777
+ } catch {
4778
+ }
4779
+ }
4780
+ return "";
4781
+ }
4558
4782
  function OhhwellsBridge() {
4559
4783
  const pathname = (0, import_navigation.usePathname)();
4560
4784
  const router = (0, import_navigation.useRouter)();
@@ -4584,11 +4808,7 @@ function OhhwellsBridge() {
4584
4808
  };
4585
4809
  }, []);
4586
4810
  const subdomainFromQuery = searchParams.get("subdomain");
4587
- const subdomain = subdomainFromQuery ?? (() => {
4588
- if (typeof window === "undefined") return "";
4589
- const parts = window.location.hostname.split(".");
4590
- return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
4591
- })();
4811
+ const subdomain = resolveSubdomain(subdomainFromQuery);
4592
4812
  const postToParent = (0, import_react3.useCallback)((data) => {
4593
4813
  if (typeof window !== "undefined" && window.parent !== window) {
4594
4814
  window.parent.postMessage(data, "*");
@@ -4698,18 +4918,6 @@ function OhhwellsBridge() {
4698
4918
  }
4699
4919
  const applyContent = (content) => {
4700
4920
  const imageLoads = [];
4701
- document.querySelectorAll("[data-ohw-section-container]").forEach((el) => el.remove());
4702
- if (content["__ohw_sections"]) {
4703
- try {
4704
- const entries = JSON.parse(content["__ohw_sections"]);
4705
- const currentPath = window.location.pathname;
4706
- entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
4707
- if (pagePath && pagePath !== currentPath) return;
4708
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
4709
- });
4710
- } catch {
4711
- }
4712
- }
4713
4921
  for (const [key, val] of Object.entries(content)) {
4714
4922
  if (key === "__ohw_sections") continue;
4715
4923
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -4730,6 +4938,7 @@ function OhhwellsBridge() {
4730
4938
  }
4731
4939
  });
4732
4940
  }
4941
+ initSectionsFromContent(content, true);
4733
4942
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
4734
4943
  }) : Promise.resolve();
4735
4944
  };
@@ -4756,20 +4965,11 @@ function OhhwellsBridge() {
4756
4965
  }, [subdomain, isEditMode, pathname]);
4757
4966
  (0, import_react3.useEffect)(() => {
4758
4967
  if (!subdomain || isEditMode) return;
4968
+ let debounceTimer = null;
4759
4969
  const applyFromCache = () => {
4760
4970
  const content = contentCache.get(subdomain);
4761
4971
  if (!content) return;
4762
- if (content["__ohw_sections"]) {
4763
- try {
4764
- const entries = JSON.parse(content["__ohw_sections"]);
4765
- const currentPath = window.location.pathname;
4766
- entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
4767
- if (pagePath && pagePath !== currentPath) return;
4768
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
4769
- });
4770
- } catch {
4771
- }
4772
- }
4972
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
4773
4973
  for (const [key, val] of Object.entries(content)) {
4774
4974
  if (key === "__ohw_sections") continue;
4775
4975
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -4785,11 +4985,18 @@ function OhhwellsBridge() {
4785
4985
  });
4786
4986
  }
4787
4987
  };
4788
- applyFromCache();
4789
- const observer = new MutationObserver(applyFromCache);
4988
+ const scheduleApply = () => {
4989
+ if (debounceTimer) clearTimeout(debounceTimer);
4990
+ debounceTimer = setTimeout(applyFromCache, 150);
4991
+ };
4992
+ scheduleApply();
4993
+ const observer = new MutationObserver(scheduleApply);
4790
4994
  observer.observe(document.body, { childList: true, subtree: true });
4791
- return () => observer.disconnect();
4792
- }, [subdomain, isEditMode]);
4995
+ return () => {
4996
+ observer.disconnect();
4997
+ if (debounceTimer) clearTimeout(debounceTimer);
4998
+ };
4999
+ }, [subdomain, isEditMode, pathname]);
4793
5000
  (0, import_react3.useLayoutEffect)(() => {
4794
5001
  const el = document.getElementById("ohw-loader");
4795
5002
  if (!el) return;
@@ -5362,17 +5569,10 @@ function OhhwellsBridge() {
5362
5569
  if (e.data?.type !== "ow:hydrate") return;
5363
5570
  const content = e.data.content;
5364
5571
  if (!content) return;
5572
+ let sectionsJson = null;
5365
5573
  for (const [key, val] of Object.entries(content)) {
5366
5574
  if (key === "__ohw_sections") {
5367
- try {
5368
- const entries = JSON.parse(val);
5369
- const tracker = getSectionsTracker();
5370
- tracker.textContent = val;
5371
- entries.forEach(({ type, insertAfter, scheduleId }) => {
5372
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
5373
- });
5374
- } catch {
5375
- }
5575
+ sectionsJson = val;
5376
5576
  continue;
5377
5577
  }
5378
5578
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5386,6 +5586,9 @@ function OhhwellsBridge() {
5386
5586
  }
5387
5587
  });
5388
5588
  }
5589
+ if (sectionsJson) {
5590
+ initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
5591
+ }
5389
5592
  postToParentRef.current({ type: "ow:hydrate-done" });
5390
5593
  };
5391
5594
  window.addEventListener("message", handleHydrate);
@@ -5436,9 +5639,9 @@ function OhhwellsBridge() {
5436
5639
  };
5437
5640
  const handleInsertSection = (e) => {
5438
5641
  if (e.data?.type !== "ow:insert-section") return;
5439
- const { widgetType, insertAfter } = e.data;
5642
+ const { widgetType, insertAfter, insertBefore } = e.data;
5440
5643
  if (widgetType !== "scheduling") return;
5441
- const inserted = mountSchedulingWidget(insertAfter, true);
5644
+ const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
5442
5645
  if (inserted) {
5443
5646
  const tracker = getSectionsTracker();
5444
5647
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
@@ -5449,39 +5652,31 @@ function OhhwellsBridge() {
5449
5652
  const handleSwitchSchedule = (e) => {
5450
5653
  if (e.data?.type !== "ow:switch-schedule") return;
5451
5654
  const scheduleId = e.data.scheduleId;
5452
- if (!scheduleId) return;
5453
- const tracker = getSectionsTracker();
5454
- let sections = [];
5455
- try {
5456
- sections = JSON.parse(tracker.textContent || "[]");
5457
- } catch {
5458
- }
5459
- const currentPath = window.location.pathname;
5460
- const updated = sections.map(
5461
- (s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId } : s
5462
- );
5463
- tracker.textContent = JSON.stringify(updated);
5464
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5655
+ const insertAfter = e.data.insertAfter;
5656
+ if (!scheduleId || !insertAfter) return;
5657
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
5658
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5659
+ };
5660
+ const handleScheduleLinked = (e) => {
5661
+ if (e.data?.type !== "ow:schedule-linked") return;
5662
+ const scheduleId = e.data.scheduleId;
5663
+ const insertAfter = e.data.insertAfter;
5664
+ if (!scheduleId || !insertAfter) return;
5665
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
5666
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5465
5667
  };
5466
5668
  const handleClearSchedulingWidget = (e) => {
5467
5669
  if (e.data?.type !== "ow:clear-scheduling-widget") return;
5468
- const tracker = getSectionsTracker();
5469
- let sections = [];
5470
- try {
5471
- sections = JSON.parse(tracker.textContent || "[]");
5472
- } catch {
5473
- }
5474
- const currentPath = window.location.pathname;
5475
- const updated = sections.map(
5476
- (s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId: null } : s
5477
- );
5478
- tracker.textContent = JSON.stringify(updated);
5479
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5670
+ const insertAfter = e.data.insertAfter;
5671
+ if (!insertAfter) return;
5672
+ const text = updateSectionScheduleId(insertAfter, null);
5673
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5480
5674
  };
5481
5675
  const handleRemoveSchedulingSection = (e) => {
5482
5676
  if (e.data?.type !== "ow:remove-scheduling-section") return;
5483
- const els = document.querySelectorAll('[data-ohw-section="scheduling"]');
5484
- els.forEach((el) => el.parentElement?.removeChild(el));
5677
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
5678
+ el.remove();
5679
+ });
5485
5680
  const tracker = getSectionsTracker();
5486
5681
  let sections = [];
5487
5682
  try {
@@ -5600,6 +5795,7 @@ function OhhwellsBridge() {
5600
5795
  window.addEventListener("message", handleSave);
5601
5796
  window.addEventListener("message", handleInsertSection);
5602
5797
  window.addEventListener("message", handleSwitchSchedule);
5798
+ window.addEventListener("message", handleScheduleLinked);
5603
5799
  window.addEventListener("message", handleClearSchedulingWidget);
5604
5800
  window.addEventListener("message", handleRemoveSchedulingSection);
5605
5801
  window.addEventListener("message", handleImageUrl);
@@ -5638,6 +5834,7 @@ function OhhwellsBridge() {
5638
5834
  window.removeEventListener("message", handleSave);
5639
5835
  window.removeEventListener("message", handleInsertSection);
5640
5836
  window.removeEventListener("message", handleSwitchSchedule);
5837
+ window.removeEventListener("message", handleScheduleLinked);
5641
5838
  window.removeEventListener("message", handleClearSchedulingWidget);
5642
5839
  window.removeEventListener("message", handleRemoveSchedulingSection);
5643
5840
  window.removeEventListener("message", handleImageUrl);
@@ -5695,7 +5892,7 @@ function OhhwellsBridge() {
5695
5892
  }
5696
5893
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
5697
5894
  }, [deactivate]);
5698
- return bridgeRoot ? (0, import_react_dom.createPortal)(
5895
+ return bridgeRoot ? (0, import_react_dom2.createPortal)(
5699
5896
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5700
5897
  toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5701
5898
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),