@ohhwells/bridge 0.1.42-next.92 → 0.1.42-next.94

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 CHANGED
@@ -318,6 +318,18 @@ npm run build
318
318
  npm publish --access public
319
319
  ```
320
320
 
321
+ ### Staging → rebound-template auto-bump
322
+
323
+ When `staging` publishes successfully, the bridge workflow dispatches a `bridge-published` event to `TheFlowOps-Eng/rebound-template`. That repo’s `bump-bridge.yml` workflow checks out `staging`, runs `npm install @ohhwells/bridge@<version> --save-exact`, and pushes the lockfile bump.
324
+
325
+ **One-time setup** (in `ohhwells-bridge` GitHub repo → Settings → Secrets):
326
+
327
+ | Secret | Value |
328
+ |--------|--------|
329
+ | `REBOUND_TEMPLATE_DISPATCH_TOKEN` | Fine-grained or classic PAT with `repo` access to `rebound-template` (needs permission to trigger `repository_dispatch`) |
330
+
331
+ The published staging version looks like `0.1.31-next.42` and is tagged `next` on npm.
332
+
321
333
  ---
322
334
 
323
335
  ## Link dialog (Canvas Editor)
package/dist/index.cjs CHANGED
@@ -115,6 +115,7 @@ var import_react3 = require("react");
115
115
  var import_react2 = require("react");
116
116
  var import_radix_ui = require("radix-ui");
117
117
  var import_jsx_runtime = require("react/jsx-runtime");
118
+ var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
118
119
  function Spinner() {
119
120
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
120
121
  "svg",
@@ -148,12 +149,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
148
149
  const [error, setError] = (0, import_react2.useState)(null);
149
150
  const isBook = title === "Confirm your spot";
150
151
  const handleSubmit = async () => {
151
- if (!email.trim()) return;
152
+ const trimmed = email.trim();
153
+ if (!trimmed) return;
154
+ if (!isValidEmail(trimmed)) {
155
+ setError("Please enter a valid email address.");
156
+ return;
157
+ }
152
158
  setLoading(true);
153
159
  setError(null);
154
160
  try {
155
- await onSubmit(email.trim());
156
- setSubmittedEmail(email.trim());
161
+ await onSubmit(trimmed);
162
+ setSubmittedEmail(trimmed);
157
163
  setSuccess(true);
158
164
  } catch (e) {
159
165
  setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
@@ -228,7 +234,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
228
234
  {
229
235
  type: "email",
230
236
  value: email,
231
- onChange: (e) => setEmail(e.target.value),
237
+ onChange: (e) => {
238
+ setEmail(e.target.value);
239
+ if (error) setError(null);
240
+ },
232
241
  onKeyDown: handleKeyDown,
233
242
  placeholder: "hello@gmail.com",
234
243
  autoFocus: true,
@@ -285,12 +294,20 @@ function formatClassTime(cls) {
285
294
  const em = endTotal % 60;
286
295
  return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
287
296
  }
297
+ function formatBookingLabel(cls) {
298
+ const price = cls.price;
299
+ const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
300
+ return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
301
+ }
288
302
  function getBookingsOnDate(cls, date) {
289
303
  if (!cls.bookings?.length) return 0;
290
- const ds = date.toISOString().split("T")[0];
304
+ const target = new Date(date);
305
+ target.setHours(0, 0, 0, 0);
291
306
  return cls.bookings.filter((b) => {
292
307
  try {
293
- return new Date(b.classDate).toISOString().split("T")[0] === ds;
308
+ const bookingDate = new Date(b.classDate);
309
+ bookingDate.setHours(0, 0, 0, 0);
310
+ return bookingDate.getTime() === target.getTime();
294
311
  } catch {
295
312
  return false;
296
313
  }
@@ -555,7 +572,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
555
572
  }
556
573
  )
557
574
  ] }),
558
- /* @__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: [
575
+ cls.showAvailability !== false && /* @__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: [
559
576
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
560
577
  available,
561
578
  "/",
@@ -567,9 +584,17 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
567
584
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
568
585
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
569
586
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
570
- 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 })
587
+ 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 }),
588
+ cls.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
589
+ "span",
590
+ {
591
+ className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block",
592
+ style: { wordBreak: "break-word" },
593
+ children: cls.description
594
+ }
595
+ )
571
596
  ] }),
572
- /* @__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: [
597
+ cls.showAvailability !== false && /* @__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: [
573
598
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
574
599
  available,
575
600
  "/",
@@ -594,7 +619,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
594
619
  if (!cls.id) return;
595
620
  onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
596
621
  },
597
- children: isFull ? "Join Waitlist" : "Book Now"
622
+ children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
598
623
  }
599
624
  )
600
625
  ] })
@@ -636,6 +661,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
636
661
  const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
637
662
  const [modalState, setModalState] = (0, import_react3.useState)(null);
638
663
  const switchScheduleIdRef = (0, import_react3.useRef)(null);
664
+ const liveScheduleIdRef = (0, import_react3.useRef)(null);
665
+ const refetchLiveSchedule = (0, import_react3.useCallback)(async () => {
666
+ const id = liveScheduleIdRef.current;
667
+ if (!id) return;
668
+ try {
669
+ const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
670
+ const data = await res.json();
671
+ if (data?.id) setSchedule(data);
672
+ } catch {
673
+ }
674
+ }, []);
639
675
  const dates = (0, import_react3.useMemo)(() => {
640
676
  const today = /* @__PURE__ */ new Date();
641
677
  today.setHours(0, 0, 0, 0);
@@ -655,6 +691,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
655
691
  }
656
692
  }
657
693
  }, [schedule, dates]);
694
+ (0, import_react3.useEffect)(() => {
695
+ if (typeof window === "undefined") return;
696
+ const onVisible = () => {
697
+ if (!document.hidden) void refetchLiveSchedule();
698
+ };
699
+ window.addEventListener("focus", refetchLiveSchedule);
700
+ document.addEventListener("visibilitychange", onVisible);
701
+ return () => {
702
+ window.removeEventListener("focus", refetchLiveSchedule);
703
+ document.removeEventListener("visibilitychange", onVisible);
704
+ };
705
+ }, [refetchLiveSchedule]);
658
706
  (0, import_react3.useEffect)(() => {
659
707
  if (typeof window === "undefined") return;
660
708
  const isInEditor = window.self !== window.top;
@@ -716,7 +764,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
716
764
  setLoading(false);
717
765
  return;
718
766
  }
719
- ;
767
+ liveScheduleIdRef.current = effectiveId;
720
768
  (async () => {
721
769
  try {
722
770
  const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
@@ -773,18 +821,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
773
821
  const data = await res.json().catch(() => ({}));
774
822
  throw new Error(data?.message ?? "Something went wrong. Please try again.");
775
823
  }
824
+ await refetchLiveSchedule();
776
825
  };
777
826
  const handleReplaceSchedule = () => {
778
827
  setIsHovered(false);
779
- if (schedule) {
780
- window.parent.postMessage({
781
- type: "ow:schedule-connected",
782
- schedule: { id: schedule.id, name: schedule.name },
783
- insertAfter
784
- }, "*");
785
- } else {
786
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
787
- }
828
+ window.parent.postMessage(
829
+ { type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
830
+ "*"
831
+ );
788
832
  };
789
833
  if (!inEditor && !loading && !schedule) return null;
790
834
  const sectionId = `scheduling-${insertAfter}`;
@@ -827,7 +871,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
827
871
  ),
828
872
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
829
873
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
830
- /* @__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" }),
874
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
875
+ "h2",
876
+ {
877
+ className: "font-display text-center m-0 text-(--color-dark,#200C02)",
878
+ style: {
879
+ fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
880
+ fontWeight: "var(--font-weight-heading, 400)",
881
+ lineHeight: 1.05,
882
+ letterSpacing: "-0.02em"
883
+ },
884
+ children: schedule?.name ?? "Book an appointment"
885
+ }
886
+ ),
831
887
  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 })
832
888
  ] }),
833
889
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
@@ -4491,6 +4547,19 @@ var DRAG_SHADOW = "0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0,
4491
4547
  var TOOLBAR_EDGE_MARGIN = 4;
4492
4548
  var SELECTION_CHROME_GAP = 4;
4493
4549
  var TOOLBAR_STROKE_GAP = 4;
4550
+ function getChromeZIndex(state) {
4551
+ switch (state) {
4552
+ case "active-top":
4553
+ case "active-bottom":
4554
+ case "dragging":
4555
+ return 2147483644;
4556
+ case "hover":
4557
+ case "sibling-hint":
4558
+ return 2147483643;
4559
+ default:
4560
+ return 2147483642;
4561
+ }
4562
+ }
4494
4563
  function getChromeStyle(state) {
4495
4564
  switch (state) {
4496
4565
  case "hover":
@@ -4606,7 +4675,7 @@ function ItemInteractionLayer({
4606
4675
  left: rect.left - chromeGap,
4607
4676
  width: rect.width + chromeGap * 2,
4608
4677
  height: rect.height + chromeGap * 2,
4609
- zIndex: 2147483646
4678
+ zIndex: getChromeZIndex(state)
4610
4679
  },
4611
4680
  children: [
4612
4681
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -8996,6 +9065,16 @@ function OhhwellsBridge() {
8996
9065
  return;
8997
9066
  }
8998
9067
  document.documentElement.setAttribute("data-ohw-link-popover-open", "");
9068
+ hoveredItemElRef.current = null;
9069
+ setHoveredItemRect(null);
9070
+ hoveredNavContainerRef.current = null;
9071
+ setHoveredNavContainerRect(null);
9072
+ siblingHintElRef.current = null;
9073
+ setSiblingHintRect(null);
9074
+ setSiblingHintRects([]);
9075
+ if (selectedElRef.current || toolbarVariantRef.current !== "none") {
9076
+ deselectRef.current();
9077
+ }
8999
9078
  if (hoveredImageRef.current) {
9000
9079
  hoveredImageRef.current = null;
9001
9080
  hoveredImageHasTextOverlapRef.current = false;
@@ -11878,8 +11957,8 @@ function OhhwellsBridge() {
11878
11957
  onVideoSettingsChange: handleVideoSettingsChange
11879
11958
  }
11880
11959
  ),
11881
- siblingHintRect && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
11882
- siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
11960
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
11961
+ siblingHintRects.length > 0 && !linkPopover && siblingHintRects.map((rect, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
11883
11962
  isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
11884
11963
  isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11885
11964
  "div",
@@ -11924,8 +12003,9 @@ function OhhwellsBridge() {
11924
12003
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
11925
12004
  )),
11926
12005
  hoveredNavContainerRect && toolbarVariant !== "select-frame" && !linkPopover && !isItemDragging && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
12006
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
11927
12007
  toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer(selectedElRef.current) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
11928
- toolbarRect && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
12008
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame") && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11929
12009
  ItemInteractionLayer,
11930
12010
  {
11931
12011
  rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
@@ -11952,8 +12032,7 @@ function OhhwellsBridge() {
11952
12032
  ) : void 0
11953
12033
  }
11954
12034
  ),
11955
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
11956
- toolbarRect && toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
12035
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
11957
12036
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
11958
12037
  EditGlowChrome,
11959
12038
  {