@ohhwells/bridge 0.1.35 → 0.1.36-next.46
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 +12 -0
- package/dist/index.cjs +105 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +129 -45
- package/dist/index.js.map +1 -1
- package/dist/styles.css +0 -18
- package/package.json +1 -1
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
|
@@ -110,6 +110,7 @@ var import_react3 = require("react");
|
|
|
110
110
|
var import_react2 = require("react");
|
|
111
111
|
var import_radix_ui = require("radix-ui");
|
|
112
112
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
113
|
+
var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
113
114
|
function Spinner() {
|
|
114
115
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
115
116
|
"svg",
|
|
@@ -143,12 +144,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
|
143
144
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
144
145
|
const isBook = title === "Confirm your spot";
|
|
145
146
|
const handleSubmit = async () => {
|
|
146
|
-
|
|
147
|
+
const trimmed = email.trim();
|
|
148
|
+
if (!trimmed) return;
|
|
149
|
+
if (!isValidEmail(trimmed)) {
|
|
150
|
+
setError("Please enter a valid email address.");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
147
153
|
setLoading(true);
|
|
148
154
|
setError(null);
|
|
149
155
|
try {
|
|
150
|
-
await onSubmit(
|
|
151
|
-
setSubmittedEmail(
|
|
156
|
+
await onSubmit(trimmed);
|
|
157
|
+
setSubmittedEmail(trimmed);
|
|
152
158
|
setSuccess(true);
|
|
153
159
|
} catch (e) {
|
|
154
160
|
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
@@ -223,7 +229,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
|
223
229
|
{
|
|
224
230
|
type: "email",
|
|
225
231
|
value: email,
|
|
226
|
-
onChange: (e) =>
|
|
232
|
+
onChange: (e) => {
|
|
233
|
+
setEmail(e.target.value);
|
|
234
|
+
if (error) setError(null);
|
|
235
|
+
},
|
|
227
236
|
onKeyDown: handleKeyDown,
|
|
228
237
|
placeholder: "hello@gmail.com",
|
|
229
238
|
autoFocus: true,
|
|
@@ -280,12 +289,20 @@ function formatClassTime(cls) {
|
|
|
280
289
|
const em = endTotal % 60;
|
|
281
290
|
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
282
291
|
}
|
|
292
|
+
function formatBookingLabel(cls) {
|
|
293
|
+
const price = cls.price;
|
|
294
|
+
const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
|
|
295
|
+
return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
|
|
296
|
+
}
|
|
283
297
|
function getBookingsOnDate(cls, date) {
|
|
284
298
|
if (!cls.bookings?.length) return 0;
|
|
285
|
-
const
|
|
299
|
+
const target = new Date(date);
|
|
300
|
+
target.setHours(0, 0, 0, 0);
|
|
286
301
|
return cls.bookings.filter((b) => {
|
|
287
302
|
try {
|
|
288
|
-
|
|
303
|
+
const bookingDate = new Date(b.classDate);
|
|
304
|
+
bookingDate.setHours(0, 0, 0, 0);
|
|
305
|
+
return bookingDate.getTime() === target.getTime();
|
|
289
306
|
} catch {
|
|
290
307
|
return false;
|
|
291
308
|
}
|
|
@@ -589,7 +606,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
589
606
|
if (!cls.id) return;
|
|
590
607
|
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
591
608
|
},
|
|
592
|
-
children: isFull ? "Join Waitlist" :
|
|
609
|
+
children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
|
|
593
610
|
}
|
|
594
611
|
)
|
|
595
612
|
] })
|
|
@@ -631,6 +648,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
631
648
|
const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
|
|
632
649
|
const [modalState, setModalState] = (0, import_react3.useState)(null);
|
|
633
650
|
const switchScheduleIdRef = (0, import_react3.useRef)(null);
|
|
651
|
+
const liveScheduleIdRef = (0, import_react3.useRef)(null);
|
|
652
|
+
const refetchLiveSchedule = (0, import_react3.useCallback)(async () => {
|
|
653
|
+
const id = liveScheduleIdRef.current;
|
|
654
|
+
if (!id) return;
|
|
655
|
+
try {
|
|
656
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
|
|
657
|
+
const data = await res.json();
|
|
658
|
+
if (data?.id) setSchedule(data);
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
}, []);
|
|
634
662
|
const dates = (0, import_react3.useMemo)(() => {
|
|
635
663
|
const today = /* @__PURE__ */ new Date();
|
|
636
664
|
today.setHours(0, 0, 0, 0);
|
|
@@ -650,6 +678,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
650
678
|
}
|
|
651
679
|
}
|
|
652
680
|
}, [schedule, dates]);
|
|
681
|
+
(0, import_react3.useEffect)(() => {
|
|
682
|
+
if (typeof window === "undefined") return;
|
|
683
|
+
const onVisible = () => {
|
|
684
|
+
if (!document.hidden) void refetchLiveSchedule();
|
|
685
|
+
};
|
|
686
|
+
window.addEventListener("focus", refetchLiveSchedule);
|
|
687
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
688
|
+
return () => {
|
|
689
|
+
window.removeEventListener("focus", refetchLiveSchedule);
|
|
690
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
691
|
+
};
|
|
692
|
+
}, [refetchLiveSchedule]);
|
|
653
693
|
(0, import_react3.useEffect)(() => {
|
|
654
694
|
if (typeof window === "undefined") return;
|
|
655
695
|
const isInEditor = window.self !== window.top;
|
|
@@ -711,7 +751,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
711
751
|
setLoading(false);
|
|
712
752
|
return;
|
|
713
753
|
}
|
|
714
|
-
;
|
|
754
|
+
liveScheduleIdRef.current = effectiveId;
|
|
715
755
|
(async () => {
|
|
716
756
|
try {
|
|
717
757
|
const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
|
|
@@ -768,18 +808,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
768
808
|
const data = await res.json().catch(() => ({}));
|
|
769
809
|
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
770
810
|
}
|
|
811
|
+
await refetchLiveSchedule();
|
|
771
812
|
};
|
|
772
813
|
const handleReplaceSchedule = () => {
|
|
773
814
|
setIsHovered(false);
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
insertAfter
|
|
779
|
-
}, "*");
|
|
780
|
-
} else {
|
|
781
|
-
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
782
|
-
}
|
|
815
|
+
window.parent.postMessage(
|
|
816
|
+
{ type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
|
|
817
|
+
"*"
|
|
818
|
+
);
|
|
783
819
|
};
|
|
784
820
|
if (!inEditor && !loading && !schedule) return null;
|
|
785
821
|
const sectionId = `scheduling-${insertAfter}`;
|
|
@@ -822,7 +858,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
822
858
|
),
|
|
823
859
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
824
860
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
|
|
825
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
861
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
862
|
+
"h2",
|
|
863
|
+
{
|
|
864
|
+
className: "font-display text-center m-0 text-(--color-dark,#200C02)",
|
|
865
|
+
style: {
|
|
866
|
+
fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
|
|
867
|
+
fontWeight: "var(--font-weight-heading, 400)",
|
|
868
|
+
lineHeight: 1.05,
|
|
869
|
+
letterSpacing: "-0.02em"
|
|
870
|
+
},
|
|
871
|
+
children: schedule?.name ?? "Book an appointment"
|
|
872
|
+
}
|
|
873
|
+
),
|
|
826
874
|
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 })
|
|
827
875
|
] }),
|
|
828
876
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
@@ -6085,6 +6133,8 @@ var ICONS = {
|
|
|
6085
6133
|
insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
|
|
6086
6134
|
insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
|
|
6087
6135
|
};
|
|
6136
|
+
var TOOLBAR_PILL_PADDING = 2;
|
|
6137
|
+
var TOOLBAR_TOGGLE_GAP = 4;
|
|
6088
6138
|
var TOOLBAR_GROUPS = [
|
|
6089
6139
|
[
|
|
6090
6140
|
{ cmd: "bold", title: "Bold" },
|
|
@@ -6203,9 +6253,9 @@ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
|
|
|
6203
6253
|
const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
|
|
6204
6254
|
return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
|
|
6205
6255
|
}
|
|
6206
|
-
function calcToolbarPos(rect, parentScroll, approxW =
|
|
6256
|
+
function calcToolbarPos(rect, parentScroll, approxW = 306) {
|
|
6207
6257
|
const GAP = 8;
|
|
6208
|
-
const APPROX_H =
|
|
6258
|
+
const APPROX_H = 32;
|
|
6209
6259
|
const APPROX_W = approxW;
|
|
6210
6260
|
const clip = getIframeVisibleClip(parentScroll);
|
|
6211
6261
|
const clipTop = clip?.top ?? 0;
|
|
@@ -6263,6 +6313,15 @@ function FloatingToolbar({
|
|
|
6263
6313
|
left,
|
|
6264
6314
|
transform,
|
|
6265
6315
|
zIndex: 2147483647,
|
|
6316
|
+
background: "#fff",
|
|
6317
|
+
border: "1px solid #E7E5E4",
|
|
6318
|
+
borderRadius: 6,
|
|
6319
|
+
boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
|
|
6320
|
+
display: "flex",
|
|
6321
|
+
alignItems: "center",
|
|
6322
|
+
padding: TOOLBAR_PILL_PADDING,
|
|
6323
|
+
gap: TOOLBAR_TOGGLE_GAP,
|
|
6324
|
+
fontFamily: "sans-serif",
|
|
6266
6325
|
pointerEvents: "auto"
|
|
6267
6326
|
},
|
|
6268
6327
|
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CustomToolbar, { children: [
|
|
@@ -8179,7 +8238,32 @@ function OhhwellsBridge() {
|
|
|
8179
8238
|
onSubmit: handleLinkPopoverSubmit
|
|
8180
8239
|
},
|
|
8181
8240
|
`${linkPopover.key}-${pathname}`
|
|
8182
|
-
) : null
|
|
8241
|
+
) : null,
|
|
8242
|
+
sectionGap && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
8243
|
+
"div",
|
|
8244
|
+
{
|
|
8245
|
+
"data-ohw-section-insert-line": "",
|
|
8246
|
+
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
8247
|
+
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
8248
|
+
children: [
|
|
8249
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
8250
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
8251
|
+
Badge,
|
|
8252
|
+
{
|
|
8253
|
+
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",
|
|
8254
|
+
onClick: () => {
|
|
8255
|
+
window.parent.postMessage(
|
|
8256
|
+
{ type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
|
|
8257
|
+
"*"
|
|
8258
|
+
);
|
|
8259
|
+
},
|
|
8260
|
+
children: "Add Section"
|
|
8261
|
+
}
|
|
8262
|
+
),
|
|
8263
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
8264
|
+
]
|
|
8265
|
+
}
|
|
8266
|
+
)
|
|
8183
8267
|
] }),
|
|
8184
8268
|
bridgeRoot
|
|
8185
8269
|
) : null;
|