@ohhwells/bridge 0.1.49 → 0.1.50
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 +77 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +129 -73
- 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
|
@@ -116,6 +116,7 @@ var import_react3 = require("react");
|
|
|
116
116
|
var import_react2 = require("react");
|
|
117
117
|
var import_radix_ui = require("radix-ui");
|
|
118
118
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
119
|
+
var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
119
120
|
function Spinner() {
|
|
120
121
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
121
122
|
"svg",
|
|
@@ -149,12 +150,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
|
149
150
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
150
151
|
const isBook = title === "Confirm your spot";
|
|
151
152
|
const handleSubmit = async () => {
|
|
152
|
-
|
|
153
|
+
const trimmed = email.trim();
|
|
154
|
+
if (!trimmed) return;
|
|
155
|
+
if (!isValidEmail(trimmed)) {
|
|
156
|
+
setError("Please enter a valid email address.");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
153
159
|
setLoading(true);
|
|
154
160
|
setError(null);
|
|
155
161
|
try {
|
|
156
|
-
await onSubmit(
|
|
157
|
-
setSubmittedEmail(
|
|
162
|
+
await onSubmit(trimmed);
|
|
163
|
+
setSubmittedEmail(trimmed);
|
|
158
164
|
setSuccess(true);
|
|
159
165
|
} catch (e) {
|
|
160
166
|
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
@@ -229,7 +235,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
|
229
235
|
{
|
|
230
236
|
type: "email",
|
|
231
237
|
value: email,
|
|
232
|
-
onChange: (e) =>
|
|
238
|
+
onChange: (e) => {
|
|
239
|
+
setEmail(e.target.value);
|
|
240
|
+
if (error) setError(null);
|
|
241
|
+
},
|
|
233
242
|
onKeyDown: handleKeyDown,
|
|
234
243
|
placeholder: "hello@gmail.com",
|
|
235
244
|
autoFocus: true,
|
|
@@ -286,12 +295,20 @@ function formatClassTime(cls) {
|
|
|
286
295
|
const em = endTotal % 60;
|
|
287
296
|
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
288
297
|
}
|
|
298
|
+
function formatBookingLabel(cls) {
|
|
299
|
+
const price = cls.price;
|
|
300
|
+
const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
|
|
301
|
+
return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
|
|
302
|
+
}
|
|
289
303
|
function getBookingsOnDate(cls, date) {
|
|
290
304
|
if (!cls.bookings?.length) return 0;
|
|
291
|
-
const
|
|
305
|
+
const target = new Date(date);
|
|
306
|
+
target.setHours(0, 0, 0, 0);
|
|
292
307
|
return cls.bookings.filter((b) => {
|
|
293
308
|
try {
|
|
294
|
-
|
|
309
|
+
const bookingDate = new Date(b.classDate);
|
|
310
|
+
bookingDate.setHours(0, 0, 0, 0);
|
|
311
|
+
return bookingDate.getTime() === target.getTime();
|
|
295
312
|
} catch {
|
|
296
313
|
return false;
|
|
297
314
|
}
|
|
@@ -556,7 +573,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
556
573
|
}
|
|
557
574
|
)
|
|
558
575
|
] }),
|
|
559
|
-
/* @__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: [
|
|
576
|
+
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: [
|
|
560
577
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
561
578
|
available,
|
|
562
579
|
"/",
|
|
@@ -568,9 +585,17 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
568
585
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
|
|
569
586
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
|
|
570
587
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
571
|
-
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.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 }),
|
|
589
|
+
cls.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
590
|
+
"span",
|
|
591
|
+
{
|
|
592
|
+
className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block",
|
|
593
|
+
style: { wordBreak: "break-word" },
|
|
594
|
+
children: cls.description
|
|
595
|
+
}
|
|
596
|
+
)
|
|
572
597
|
] }),
|
|
573
|
-
/* @__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: [
|
|
598
|
+
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: [
|
|
574
599
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
575
600
|
available,
|
|
576
601
|
"/",
|
|
@@ -595,7 +620,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
595
620
|
if (!cls.id) return;
|
|
596
621
|
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
597
622
|
},
|
|
598
|
-
children: isFull ? "Join Waitlist" :
|
|
623
|
+
children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
|
|
599
624
|
}
|
|
600
625
|
)
|
|
601
626
|
] })
|
|
@@ -637,6 +662,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
637
662
|
const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
|
|
638
663
|
const [modalState, setModalState] = (0, import_react3.useState)(null);
|
|
639
664
|
const switchScheduleIdRef = (0, import_react3.useRef)(null);
|
|
665
|
+
const liveScheduleIdRef = (0, import_react3.useRef)(null);
|
|
666
|
+
const refetchLiveSchedule = (0, import_react3.useCallback)(async () => {
|
|
667
|
+
const id = liveScheduleIdRef.current;
|
|
668
|
+
if (!id) return;
|
|
669
|
+
try {
|
|
670
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
|
|
671
|
+
const data = await res.json();
|
|
672
|
+
if (data?.id) setSchedule(data);
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
675
|
+
}, []);
|
|
640
676
|
const dates = (0, import_react3.useMemo)(() => {
|
|
641
677
|
const today = /* @__PURE__ */ new Date();
|
|
642
678
|
today.setHours(0, 0, 0, 0);
|
|
@@ -656,6 +692,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
656
692
|
}
|
|
657
693
|
}
|
|
658
694
|
}, [schedule, dates]);
|
|
695
|
+
(0, import_react3.useEffect)(() => {
|
|
696
|
+
if (typeof window === "undefined") return;
|
|
697
|
+
const onVisible = () => {
|
|
698
|
+
if (!document.hidden) void refetchLiveSchedule();
|
|
699
|
+
};
|
|
700
|
+
window.addEventListener("focus", refetchLiveSchedule);
|
|
701
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
702
|
+
return () => {
|
|
703
|
+
window.removeEventListener("focus", refetchLiveSchedule);
|
|
704
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
705
|
+
};
|
|
706
|
+
}, [refetchLiveSchedule]);
|
|
659
707
|
(0, import_react3.useEffect)(() => {
|
|
660
708
|
if (typeof window === "undefined") return;
|
|
661
709
|
const isInEditor = window.self !== window.top;
|
|
@@ -717,7 +765,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
717
765
|
setLoading(false);
|
|
718
766
|
return;
|
|
719
767
|
}
|
|
720
|
-
;
|
|
768
|
+
liveScheduleIdRef.current = effectiveId;
|
|
721
769
|
(async () => {
|
|
722
770
|
try {
|
|
723
771
|
const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
|
|
@@ -774,18 +822,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
774
822
|
const data = await res.json().catch(() => ({}));
|
|
775
823
|
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
776
824
|
}
|
|
825
|
+
await refetchLiveSchedule();
|
|
777
826
|
};
|
|
778
827
|
const handleReplaceSchedule = () => {
|
|
779
828
|
setIsHovered(false);
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
insertAfter
|
|
785
|
-
}, "*");
|
|
786
|
-
} else {
|
|
787
|
-
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
788
|
-
}
|
|
829
|
+
window.parent.postMessage(
|
|
830
|
+
{ type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
|
|
831
|
+
"*"
|
|
832
|
+
);
|
|
789
833
|
};
|
|
790
834
|
if (!inEditor && !loading && !schedule) return null;
|
|
791
835
|
const sectionId = `scheduling-${insertAfter}`;
|
|
@@ -828,7 +872,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
828
872
|
),
|
|
829
873
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
830
874
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
|
|
831
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
875
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
876
|
+
"h2",
|
|
877
|
+
{
|
|
878
|
+
className: "font-display text-center m-0 text-(--color-dark,#200C02)",
|
|
879
|
+
style: {
|
|
880
|
+
fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
|
|
881
|
+
fontWeight: "var(--font-weight-heading, 400)",
|
|
882
|
+
lineHeight: 1.05,
|
|
883
|
+
letterSpacing: "-0.02em"
|
|
884
|
+
},
|
|
885
|
+
children: schedule?.name ?? "Book an appointment"
|
|
886
|
+
}
|
|
887
|
+
),
|
|
832
888
|
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 })
|
|
833
889
|
] }),
|
|
834
890
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|