@founderhq/journeys 0.3.67 → 0.4.1
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 +44 -6
- package/dist/_tsup-dts-rollup.d.cts +2300 -0
- package/dist/_tsup-dts-rollup.d.ts +2300 -0
- package/dist/index.cjs +540 -54
- package/dist/index.d.cts +140 -1446
- package/dist/index.d.ts +140 -1446
- package/dist/index.js +540 -54
- package/dist/styles.css +2 -2
- package/package.json +12 -9
package/dist/index.cjs
CHANGED
|
@@ -676,6 +676,8 @@ function JourneyProvider({
|
|
|
676
676
|
const { trigger } = react$1.useWebHaptics();
|
|
677
677
|
const onEventRef = React.useRef(onEvent);
|
|
678
678
|
const rawAnswersRef = React.useRef({});
|
|
679
|
+
const sessionStartEmittedRef = React.useRef(false);
|
|
680
|
+
const lastViewedStepIdRef = React.useRef(null);
|
|
679
681
|
const [discountCodeDialog, setDiscountCodeDialog] = React.useState(null);
|
|
680
682
|
React.useEffect(() => {
|
|
681
683
|
onEventRef.current = onEvent;
|
|
@@ -735,6 +737,39 @@ function JourneyProvider({
|
|
|
735
737
|
() => normalizedConfig.steps.flatMap(getStepVariables),
|
|
736
738
|
[normalizedConfig.steps]
|
|
737
739
|
);
|
|
740
|
+
React.useEffect(() => {
|
|
741
|
+
var _a, _b;
|
|
742
|
+
const currentStep = config.steps[currentStepIndex];
|
|
743
|
+
if (!currentStep) return;
|
|
744
|
+
const eventAnswers = getEventAnswers(
|
|
745
|
+
config.computedVariables,
|
|
746
|
+
rawAnswersRef.current
|
|
747
|
+
);
|
|
748
|
+
const strippedStep = stripOptionsFromEventStep(currentStep);
|
|
749
|
+
if (!sessionStartEmittedRef.current) {
|
|
750
|
+
sessionStartEmittedRef.current = true;
|
|
751
|
+
(_a = onEventRef.current) == null ? void 0 : _a.call(onEventRef, __spreadProps(__spreadValues({
|
|
752
|
+
type: "session_start",
|
|
753
|
+
step: strippedStep
|
|
754
|
+
}, eventAnswers), {
|
|
755
|
+
variables: allVariables
|
|
756
|
+
}));
|
|
757
|
+
}
|
|
758
|
+
if (lastViewedStepIdRef.current !== currentStep.id) {
|
|
759
|
+
lastViewedStepIdRef.current = currentStep.id;
|
|
760
|
+
(_b = onEventRef.current) == null ? void 0 : _b.call(onEventRef, __spreadProps(__spreadValues({
|
|
761
|
+
type: "step_view",
|
|
762
|
+
step: strippedStep
|
|
763
|
+
}, eventAnswers), {
|
|
764
|
+
variables: allVariables
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
767
|
+
}, [
|
|
768
|
+
allVariables,
|
|
769
|
+
config.computedVariables,
|
|
770
|
+
config.steps,
|
|
771
|
+
currentStepIndex
|
|
772
|
+
]);
|
|
738
773
|
const setAnswer = React.useCallback((stepId, answer) => {
|
|
739
774
|
if (computedVariableIds.has(stepId)) return;
|
|
740
775
|
setRawAnswers((prev) => {
|
|
@@ -2663,9 +2698,11 @@ function ButtonBlock({
|
|
|
2663
2698
|
onGoToStep,
|
|
2664
2699
|
onPurchase,
|
|
2665
2700
|
onOpenDiscountCode,
|
|
2666
|
-
disabled
|
|
2701
|
+
disabled,
|
|
2702
|
+
isLastStep
|
|
2667
2703
|
}) {
|
|
2668
2704
|
const handleClick = () => {
|
|
2705
|
+
var _a;
|
|
2669
2706
|
if (!action) {
|
|
2670
2707
|
onNext == null ? void 0 : onNext();
|
|
2671
2708
|
return;
|
|
@@ -2685,6 +2722,9 @@ function ButtonBlock({
|
|
|
2685
2722
|
}
|
|
2686
2723
|
break;
|
|
2687
2724
|
case "link":
|
|
2725
|
+
if ((_a = action.completeBeforeNavigate) != null ? _a : isLastStep) {
|
|
2726
|
+
onNext == null ? void 0 : onNext();
|
|
2727
|
+
}
|
|
2688
2728
|
if (action.external) {
|
|
2689
2729
|
window.open(action.url, "_blank", "noopener,noreferrer");
|
|
2690
2730
|
} else {
|
|
@@ -4175,7 +4215,8 @@ function ColumnsBlock({
|
|
|
4175
4215
|
answers,
|
|
4176
4216
|
onNext,
|
|
4177
4217
|
onBack,
|
|
4178
|
-
onGoToStep
|
|
4218
|
+
onGoToStep,
|
|
4219
|
+
isLastStep
|
|
4179
4220
|
}) {
|
|
4180
4221
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "jy-columns-container", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4181
4222
|
"div",
|
|
@@ -4201,6 +4242,7 @@ function ColumnsBlock({
|
|
|
4201
4242
|
onBack: onBack != null ? onBack : (() => {
|
|
4202
4243
|
}),
|
|
4203
4244
|
onGoToStep,
|
|
4245
|
+
isLastStep,
|
|
4204
4246
|
wrapInScrollViewport: false
|
|
4205
4247
|
}
|
|
4206
4248
|
)
|
|
@@ -7876,7 +7918,11 @@ function HiddenBlockSlot({ block }) {
|
|
|
7876
7918
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7877
7919
|
"div",
|
|
7878
7920
|
{
|
|
7879
|
-
className: cn(
|
|
7921
|
+
className: cn(
|
|
7922
|
+
widthClass,
|
|
7923
|
+
resolvedMaxWidth ? "mx-auto" : void 0,
|
|
7924
|
+
block.className
|
|
7925
|
+
),
|
|
7880
7926
|
style: __spreadProps(__spreadValues(__spreadValues({}, resolvedMaxWidth ? { maxWidth: resolvedMaxWidth } : null), block.style), {
|
|
7881
7927
|
visibility: "hidden"
|
|
7882
7928
|
}),
|
|
@@ -8059,7 +8105,7 @@ function resolveAppliedDiscountForDialog(answers, discountVariable, planId) {
|
|
|
8059
8105
|
if (!planId) return answer.planId ? void 0 : answer;
|
|
8060
8106
|
return discountAppliesToPlan(answer, planId) ? answer : void 0;
|
|
8061
8107
|
}
|
|
8062
|
-
function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, inputsValid) {
|
|
8108
|
+
function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, inputsValid, isLastStep) {
|
|
8063
8109
|
var _a, _b;
|
|
8064
8110
|
const conditionMet = !block.condition || evaluateCondition(block.condition, answers);
|
|
8065
8111
|
const hasExitAnim = ((_a = block.exitAnimation) == null ? void 0 : _a.preset) && block.exitAnimation.preset !== "none";
|
|
@@ -8086,8 +8132,9 @@ function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToS
|
|
|
8086
8132
|
onGoToStep,
|
|
8087
8133
|
onPurchase,
|
|
8088
8134
|
onOpenDiscountCode,
|
|
8089
|
-
disabled: buttonDisabledForValidity(resolvedProps, inputsValid)
|
|
8090
|
-
|
|
8135
|
+
disabled: buttonDisabledForValidity(resolvedProps, inputsValid),
|
|
8136
|
+
isLastStep
|
|
8137
|
+
}) : block.type === "columns" ? __spreadProps(__spreadValues({}, resolvedProps), { answers, onNext, onBack, onGoToStep, isLastStep }) : block.type === "gravity_bin" ? __spreadProps(__spreadValues({}, resolvedProps), { answers }) : resolvedProps;
|
|
8091
8138
|
const idx = visibleIndexRef.current;
|
|
8092
8139
|
visibleIndexRef.current++;
|
|
8093
8140
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -8109,6 +8156,7 @@ function BlockRenderer({
|
|
|
8109
8156
|
onNext,
|
|
8110
8157
|
onBack,
|
|
8111
8158
|
onGoToStep,
|
|
8159
|
+
isLastStep,
|
|
8112
8160
|
scrollViewportRef,
|
|
8113
8161
|
wrapInScrollViewport = true
|
|
8114
8162
|
}) {
|
|
@@ -8194,7 +8242,8 @@ function BlockRenderer({
|
|
|
8194
8242
|
onGoToStep,
|
|
8195
8243
|
onPurchase,
|
|
8196
8244
|
onOpenDiscountCode,
|
|
8197
|
-
inputsValid
|
|
8245
|
+
inputsValid,
|
|
8246
|
+
isLastStep
|
|
8198
8247
|
);
|
|
8199
8248
|
};
|
|
8200
8249
|
if (!wrapInScrollViewport && stickyBlocks.length === 0) {
|
|
@@ -8217,7 +8266,11 @@ function BlockRenderer({
|
|
|
8217
8266
|
"div",
|
|
8218
8267
|
{
|
|
8219
8268
|
className: layoutClasses(layout),
|
|
8220
|
-
style: {
|
|
8269
|
+
style: {
|
|
8270
|
+
gap: `${gap}rem`,
|
|
8271
|
+
maxWidth: layout == null ? void 0 : layout.maxWidth,
|
|
8272
|
+
width: "100%"
|
|
8273
|
+
},
|
|
8221
8274
|
children: contentBlocks.map((block, i) => renderOne(block, i))
|
|
8222
8275
|
}
|
|
8223
8276
|
)
|
|
@@ -8228,7 +8281,11 @@ function BlockRenderer({
|
|
|
8228
8281
|
) }) : null
|
|
8229
8282
|
] });
|
|
8230
8283
|
}
|
|
8231
|
-
function InfoPageStep({
|
|
8284
|
+
function InfoPageStep({
|
|
8285
|
+
config,
|
|
8286
|
+
onNext,
|
|
8287
|
+
isLastStep
|
|
8288
|
+
}) {
|
|
8232
8289
|
var _a;
|
|
8233
8290
|
const { answers } = useJourneyState();
|
|
8234
8291
|
const { goBack, goToStep } = useJourneyActions();
|
|
@@ -8236,9 +8293,7 @@ function InfoPageStep({ config, onNext }) {
|
|
|
8236
8293
|
const sortedTimelineEvents = React.useMemo(
|
|
8237
8294
|
() => {
|
|
8238
8295
|
var _a2, _b;
|
|
8239
|
-
return [...(_b = (_a2 = config.scrollTimeline) == null ? void 0 : _a2.events) != null ? _b : []].sort(
|
|
8240
|
-
(a, b) => a.at - b.at
|
|
8241
|
-
);
|
|
8296
|
+
return [...(_b = (_a2 = config.scrollTimeline) == null ? void 0 : _a2.events) != null ? _b : []].sort((a, b) => a.at - b.at);
|
|
8242
8297
|
},
|
|
8243
8298
|
[(_a = config.scrollTimeline) == null ? void 0 : _a.events]
|
|
8244
8299
|
);
|
|
@@ -8418,6 +8473,7 @@ function InfoPageStep({ config, onNext }) {
|
|
|
8418
8473
|
onNext,
|
|
8419
8474
|
onBack: goBack,
|
|
8420
8475
|
onGoToStep: goToStep,
|
|
8476
|
+
isLastStep,
|
|
8421
8477
|
scrollViewportRef
|
|
8422
8478
|
}
|
|
8423
8479
|
);
|
|
@@ -9285,7 +9341,7 @@ var STEP_REGISTRY = {
|
|
|
9285
9341
|
function StepRenderer({ config }) {
|
|
9286
9342
|
var _a, _b;
|
|
9287
9343
|
const { answers } = useJourneyState();
|
|
9288
|
-
const { setAnswer, goNext } = useJourneyActions();
|
|
9344
|
+
const { setAnswer, goNext, isLastStep } = useJourneyActions();
|
|
9289
9345
|
const resolvedConfig = React.useMemo(() => {
|
|
9290
9346
|
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
9291
9347
|
const hasTemplates = ((_a2 = config.preface) == null ? void 0 : _a2.includes("${")) || ((_b2 = config.question) == null ? void 0 : _b2.includes("${")) || ((_c = config.description) == null ? void 0 : _c.includes("${")) || ((_d = config.buttonText) == null ? void 0 : _d.includes("${")) || ((_e = config.footerText) == null ? void 0 : _e.includes("${")) || ((_g = (_f = config.swipeLabels) == null ? void 0 : _f.yes) == null ? void 0 : _g.includes("${")) || ((_i = (_h = config.swipeLabels) == null ? void 0 : _h.no) == null ? void 0 : _i.includes("${")) || ((_j = config.swipeCards) != null ? _j : []).some(
|
|
@@ -9338,7 +9394,8 @@ function StepRenderer({ config }) {
|
|
|
9338
9394
|
}
|
|
9339
9395
|
setAnswer(answerKey, answer);
|
|
9340
9396
|
},
|
|
9341
|
-
onNext: () => goNext()
|
|
9397
|
+
onNext: () => goNext(),
|
|
9398
|
+
isLastStep: isLastStep()
|
|
9342
9399
|
}
|
|
9343
9400
|
);
|
|
9344
9401
|
}
|
|
@@ -9635,19 +9692,456 @@ function JourneyShell({ className, theme } = {}) {
|
|
|
9635
9692
|
) })
|
|
9636
9693
|
] });
|
|
9637
9694
|
}
|
|
9695
|
+
var JOURNEY_LIBRARY_NAME = "@founderhq/journeys";
|
|
9696
|
+
var JOURNEY_LIBRARY_VERSION = "0.4.1";
|
|
9697
|
+
function randomId(prefix) {
|
|
9698
|
+
const cryptoRef = globalThis.crypto;
|
|
9699
|
+
if (cryptoRef == null ? void 0 : cryptoRef.randomUUID) return `${prefix}_${cryptoRef.randomUUID()}`;
|
|
9700
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
9701
|
+
}
|
|
9702
|
+
function readJson(storage, key, fallback) {
|
|
9703
|
+
try {
|
|
9704
|
+
const raw = storage.getItem(key);
|
|
9705
|
+
return raw ? JSON.parse(raw) : fallback;
|
|
9706
|
+
} catch (e) {
|
|
9707
|
+
return fallback;
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
function getStoredId(storage, key, prefix) {
|
|
9711
|
+
try {
|
|
9712
|
+
const existing = storage.getItem(key);
|
|
9713
|
+
if (existing) return existing;
|
|
9714
|
+
const next = randomId(prefix);
|
|
9715
|
+
storage.setItem(key, next);
|
|
9716
|
+
return next;
|
|
9717
|
+
} catch (e) {
|
|
9718
|
+
return randomId(prefix);
|
|
9719
|
+
}
|
|
9720
|
+
}
|
|
9721
|
+
function isRecord(value) {
|
|
9722
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9723
|
+
}
|
|
9724
|
+
function cleanObject(value) {
|
|
9725
|
+
if (!value || typeof value !== "object") return {};
|
|
9726
|
+
return __spreadValues({}, value);
|
|
9727
|
+
}
|
|
9728
|
+
var DEFAULT_REDACTED_URL_PARAMS = [
|
|
9729
|
+
"access_token",
|
|
9730
|
+
"api_key",
|
|
9731
|
+
"auth",
|
|
9732
|
+
"code",
|
|
9733
|
+
"email",
|
|
9734
|
+
"key",
|
|
9735
|
+
"otp",
|
|
9736
|
+
"password",
|
|
9737
|
+
"phone",
|
|
9738
|
+
"secret",
|
|
9739
|
+
"signature",
|
|
9740
|
+
"token"
|
|
9741
|
+
];
|
|
9742
|
+
function safeContextFactory(factory) {
|
|
9743
|
+
if (typeof factory !== "function") return void 0;
|
|
9744
|
+
try {
|
|
9745
|
+
const value = factory();
|
|
9746
|
+
return isRecord(value) ? value : void 0;
|
|
9747
|
+
} catch (e) {
|
|
9748
|
+
return void 0;
|
|
9749
|
+
}
|
|
9750
|
+
}
|
|
9751
|
+
function safeTimezone() {
|
|
9752
|
+
try {
|
|
9753
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
9754
|
+
} catch (e) {
|
|
9755
|
+
return void 0;
|
|
9756
|
+
}
|
|
9757
|
+
}
|
|
9758
|
+
function campaignContext(params) {
|
|
9759
|
+
var _a, _b, _c, _d, _e;
|
|
9760
|
+
return cleanObject({
|
|
9761
|
+
source: (_a = params.get("utm_source")) != null ? _a : void 0,
|
|
9762
|
+
medium: (_b = params.get("utm_medium")) != null ? _b : void 0,
|
|
9763
|
+
name: (_c = params.get("utm_campaign")) != null ? _c : void 0,
|
|
9764
|
+
term: (_d = params.get("utm_term")) != null ? _d : void 0,
|
|
9765
|
+
content: (_e = params.get("utm_content")) != null ? _e : void 0
|
|
9766
|
+
});
|
|
9767
|
+
}
|
|
9768
|
+
function redactedSearch(params, redactUrlParams) {
|
|
9769
|
+
if ([...params.keys()].length === 0) return "";
|
|
9770
|
+
const next = new URLSearchParams(params);
|
|
9771
|
+
const redactAll = redactUrlParams === true;
|
|
9772
|
+
const names = Array.isArray(redactUrlParams) && redactUrlParams.length > 0 ? redactUrlParams : DEFAULT_REDACTED_URL_PARAMS;
|
|
9773
|
+
const redactedNames = new Set(names.map((name) => name.toLowerCase()));
|
|
9774
|
+
for (const key of [...next.keys()]) {
|
|
9775
|
+
if (redactAll || redactedNames.has(key.toLowerCase())) {
|
|
9776
|
+
next.set(key, "[redacted]");
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9779
|
+
const value = next.toString();
|
|
9780
|
+
return value ? `?${value}` : "";
|
|
9781
|
+
}
|
|
9782
|
+
function pageContext(redactUrlParams) {
|
|
9783
|
+
if (typeof window === "undefined") return void 0;
|
|
9784
|
+
const params = new URLSearchParams(window.location.search);
|
|
9785
|
+
const campaign = campaignContext(params);
|
|
9786
|
+
const search = redactedSearch(params, redactUrlParams);
|
|
9787
|
+
return cleanObject({
|
|
9788
|
+
url: `${window.location.origin}${window.location.pathname}${search}${window.location.hash}`,
|
|
9789
|
+
path: window.location.pathname,
|
|
9790
|
+
search: search || void 0,
|
|
9791
|
+
title: document.title,
|
|
9792
|
+
referrer: document.referrer,
|
|
9793
|
+
campaign
|
|
9794
|
+
});
|
|
9795
|
+
}
|
|
9796
|
+
function captureContext(capture, runtime) {
|
|
9797
|
+
var _a, _b;
|
|
9798
|
+
const customContext = cleanObject(__spreadValues(__spreadValues({}, (_a = capture.context) != null ? _a : {}), (_b = safeContextFactory(capture.contextFactory)) != null ? _b : {}));
|
|
9799
|
+
if (capture.captureContext === false) {
|
|
9800
|
+
return customContext;
|
|
9801
|
+
}
|
|
9802
|
+
const page = pageContext(capture.redactUrlParams);
|
|
9803
|
+
const navigatorRef = typeof window !== "undefined" ? window.navigator : void 0;
|
|
9804
|
+
const screenRef = typeof window !== "undefined" ? window.screen : void 0;
|
|
9805
|
+
return cleanObject(__spreadValues({
|
|
9806
|
+
source: { type: "embed", product: "journeys" },
|
|
9807
|
+
library: {
|
|
9808
|
+
name: JOURNEY_LIBRARY_NAME,
|
|
9809
|
+
version: JOURNEY_LIBRARY_VERSION
|
|
9810
|
+
},
|
|
9811
|
+
anonymousId: runtime.visitorId,
|
|
9812
|
+
sessionId: runtime.clientSessionId,
|
|
9813
|
+
locale: navigatorRef == null ? void 0 : navigatorRef.language,
|
|
9814
|
+
timezone: safeTimezone(),
|
|
9815
|
+
userAgent: navigatorRef == null ? void 0 : navigatorRef.userAgent,
|
|
9816
|
+
page,
|
|
9817
|
+
campaign: isRecord(page) ? page.campaign : void 0,
|
|
9818
|
+
screen: screenRef ? { width: screenRef.width, height: screenRef.height } : void 0
|
|
9819
|
+
}, customContext));
|
|
9820
|
+
}
|
|
9821
|
+
function toJsonRecord(value) {
|
|
9822
|
+
return isRecord(value) ? value : {};
|
|
9823
|
+
}
|
|
9824
|
+
function answerValue(answers, variable) {
|
|
9825
|
+
return Object.prototype.hasOwnProperty.call(answers, variable) ? answers[variable] : void 0;
|
|
9826
|
+
}
|
|
9827
|
+
function stepAnswerVariables(step) {
|
|
9828
|
+
var _a, _b, _c, _d, _e;
|
|
9829
|
+
if (step.type === "swipe_cards") {
|
|
9830
|
+
return ((_a = step.swipeCards) != null ? _a : []).map((card) => ({
|
|
9831
|
+
stepId: step.id,
|
|
9832
|
+
variable: card.variable,
|
|
9833
|
+
label: card.text
|
|
9834
|
+
}));
|
|
9835
|
+
}
|
|
9836
|
+
if (step.type === "input") {
|
|
9837
|
+
return ((_b = step.fields) != null ? _b : []).map((field) => {
|
|
9838
|
+
var _a2;
|
|
9839
|
+
return {
|
|
9840
|
+
stepId: step.id,
|
|
9841
|
+
variable: (_a2 = field.variable) != null ? _a2 : field.id,
|
|
9842
|
+
label: field.label,
|
|
9843
|
+
fieldType: field.type
|
|
9844
|
+
};
|
|
9845
|
+
});
|
|
9846
|
+
}
|
|
9847
|
+
return [
|
|
9848
|
+
{
|
|
9849
|
+
stepId: step.id,
|
|
9850
|
+
variable: (_c = step.variable) != null ? _c : step.id,
|
|
9851
|
+
label: (_e = (_d = step.question) != null ? _d : step.preface) != null ? _e : step.id
|
|
9852
|
+
}
|
|
9853
|
+
];
|
|
9854
|
+
}
|
|
9855
|
+
function buildAnswerSnapshot(config, answers) {
|
|
9856
|
+
const items = [];
|
|
9857
|
+
for (const step of config.steps) {
|
|
9858
|
+
for (const item of stepAnswerVariables(step)) {
|
|
9859
|
+
const value = answerValue(answers, item.variable);
|
|
9860
|
+
if (value === void 0) continue;
|
|
9861
|
+
items.push({
|
|
9862
|
+
stepId: item.stepId,
|
|
9863
|
+
variable: item.variable,
|
|
9864
|
+
label: item.label,
|
|
9865
|
+
value
|
|
9866
|
+
});
|
|
9867
|
+
}
|
|
9868
|
+
}
|
|
9869
|
+
return { items };
|
|
9870
|
+
}
|
|
9871
|
+
function buildRespondentSnapshot(config, answers) {
|
|
9872
|
+
var _a, _b;
|
|
9873
|
+
const snapshot = {};
|
|
9874
|
+
for (const step of config.steps) {
|
|
9875
|
+
if (step.type !== "input") continue;
|
|
9876
|
+
for (const field of (_a = step.fields) != null ? _a : []) {
|
|
9877
|
+
const variable = (_b = field.variable) != null ? _b : field.id;
|
|
9878
|
+
const value = answerValue(answers, variable);
|
|
9879
|
+
if (value !== void 0) snapshot[variable] = value;
|
|
9880
|
+
}
|
|
9881
|
+
}
|
|
9882
|
+
return snapshot;
|
|
9883
|
+
}
|
|
9884
|
+
function buildContactRef(config, answers) {
|
|
9885
|
+
var _a, _b;
|
|
9886
|
+
const contact = {};
|
|
9887
|
+
for (const step of config.steps) {
|
|
9888
|
+
if (step.type !== "input") continue;
|
|
9889
|
+
for (const field of (_a = step.fields) != null ? _a : []) {
|
|
9890
|
+
const variable = (_b = field.variable) != null ? _b : field.id;
|
|
9891
|
+
const value = answerValue(answers, variable);
|
|
9892
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
9893
|
+
const normalizedVariable = variable.toLowerCase().replace(/[-_\s]/g, "");
|
|
9894
|
+
if (field.type === "email" || normalizedVariable.includes("email")) {
|
|
9895
|
+
contact.email = value;
|
|
9896
|
+
} else if (field.type === "tel" || normalizedVariable.includes("phone")) {
|
|
9897
|
+
contact.phone = value;
|
|
9898
|
+
} else if (normalizedVariable === "externalid") {
|
|
9899
|
+
contact.externalId = value;
|
|
9900
|
+
}
|
|
9901
|
+
}
|
|
9902
|
+
}
|
|
9903
|
+
return Object.keys(contact).length > 0 ? contact : void 0;
|
|
9904
|
+
}
|
|
9905
|
+
function eventStepId(event) {
|
|
9906
|
+
if ("step" in event) return event.step.id;
|
|
9907
|
+
if (event.type === "navigate") return event.to.id;
|
|
9908
|
+
if (event.type === "purchase_intent") return event.stepId;
|
|
9909
|
+
return void 0;
|
|
9910
|
+
}
|
|
9911
|
+
function eventProperties(event) {
|
|
9912
|
+
if (event.type === "step_submit") {
|
|
9913
|
+
return { submitted: event.submitted };
|
|
9914
|
+
}
|
|
9915
|
+
if (event.type === "navigate") {
|
|
9916
|
+
return {
|
|
9917
|
+
fromStepId: event.from.id,
|
|
9918
|
+
toStepId: event.to.id,
|
|
9919
|
+
direction: event.direction
|
|
9920
|
+
};
|
|
9921
|
+
}
|
|
9922
|
+
if (event.type === "purchase_intent") {
|
|
9923
|
+
return __spreadValues({
|
|
9924
|
+
variable: event.variable,
|
|
9925
|
+
plan: event.plan
|
|
9926
|
+
}, event.discount ? { discount: event.discount } : {});
|
|
9927
|
+
}
|
|
9928
|
+
return {};
|
|
9929
|
+
}
|
|
9930
|
+
function toCaptureEvent(config, event, sequence, runtime) {
|
|
9931
|
+
const answers = "answers" in event ? event.answers : {};
|
|
9932
|
+
const payload = {
|
|
9933
|
+
id: createCaptureEventId(runtime, event, sequence),
|
|
9934
|
+
type: event.type,
|
|
9935
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9936
|
+
stepId: eventStepId(event),
|
|
9937
|
+
properties: eventProperties(event),
|
|
9938
|
+
answers: toJsonRecord(answers),
|
|
9939
|
+
computedVariables: "computedVariables" in event ? toJsonRecord(event.computedVariables) : void 0
|
|
9940
|
+
};
|
|
9941
|
+
if (event.type === "complete") {
|
|
9942
|
+
payload.respondentSnapshot = buildRespondentSnapshot(config, answers);
|
|
9943
|
+
payload.answerSnapshot = buildAnswerSnapshot(config, answers);
|
|
9944
|
+
payload.contact = buildContactRef(config, answers);
|
|
9945
|
+
}
|
|
9946
|
+
return payload;
|
|
9947
|
+
}
|
|
9948
|
+
function createCaptureEventId(runtime, event, sequence) {
|
|
9949
|
+
return `${runtime.clientSessionId}:${event.type}:${sequence.toString(
|
|
9950
|
+
36
|
|
9951
|
+
)}:${randomId("fhqe")}`;
|
|
9952
|
+
}
|
|
9953
|
+
function useJourneyCapture(params) {
|
|
9954
|
+
const onEventRef = React.useRef(params.onEvent);
|
|
9955
|
+
const configRef = React.useRef(params.config);
|
|
9956
|
+
const captureRef = React.useRef(params.capture);
|
|
9957
|
+
const runtimeRef = React.useRef(null);
|
|
9958
|
+
const queueRef = React.useRef([]);
|
|
9959
|
+
const sequenceRef = React.useRef(0);
|
|
9960
|
+
const flushingRef = React.useRef(false);
|
|
9961
|
+
onEventRef.current = params.onEvent;
|
|
9962
|
+
configRef.current = params.config;
|
|
9963
|
+
captureRef.current = params.capture;
|
|
9964
|
+
const persistQueue = React.useCallback(() => {
|
|
9965
|
+
const runtime = runtimeRef.current;
|
|
9966
|
+
if (!runtime || typeof window === "undefined") return;
|
|
9967
|
+
try {
|
|
9968
|
+
sessionStorage.setItem(
|
|
9969
|
+
runtime.queueKey,
|
|
9970
|
+
JSON.stringify(queueRef.current)
|
|
9971
|
+
);
|
|
9972
|
+
} catch (e) {
|
|
9973
|
+
}
|
|
9974
|
+
}, []);
|
|
9975
|
+
const ensureRuntime = React.useCallback((capture) => {
|
|
9976
|
+
if (typeof window === "undefined") return null;
|
|
9977
|
+
const existing = runtimeRef.current;
|
|
9978
|
+
if ((existing == null ? void 0 : existing.journeyId) === capture.journeyId) return existing;
|
|
9979
|
+
const visitorId = getStoredId(
|
|
9980
|
+
localStorage,
|
|
9981
|
+
"fhq_journey_visitor_id",
|
|
9982
|
+
"fhqv"
|
|
9983
|
+
);
|
|
9984
|
+
const clientSessionId = getStoredId(
|
|
9985
|
+
sessionStorage,
|
|
9986
|
+
`fhq_journey_session:${capture.journeyId}`,
|
|
9987
|
+
"fhqs"
|
|
9988
|
+
);
|
|
9989
|
+
const queueKey = `fhq_journey_queue:${capture.journeyId}:${clientSessionId}`;
|
|
9990
|
+
const runtime = {
|
|
9991
|
+
journeyId: capture.journeyId,
|
|
9992
|
+
visitorId,
|
|
9993
|
+
clientSessionId,
|
|
9994
|
+
queueKey
|
|
9995
|
+
};
|
|
9996
|
+
runtimeRef.current = runtime;
|
|
9997
|
+
queueRef.current = readJson(
|
|
9998
|
+
sessionStorage,
|
|
9999
|
+
queueKey,
|
|
10000
|
+
[]
|
|
10001
|
+
);
|
|
10002
|
+
return runtime;
|
|
10003
|
+
}, []);
|
|
10004
|
+
const flush = React.useCallback(async () => {
|
|
10005
|
+
var _a, _b, _c;
|
|
10006
|
+
const capture = captureRef.current;
|
|
10007
|
+
if (!capture || !configRef.current || flushingRef.current) return;
|
|
10008
|
+
const runtime = ensureRuntime(capture);
|
|
10009
|
+
if (!runtime || queueRef.current.length === 0) return;
|
|
10010
|
+
flushingRef.current = true;
|
|
10011
|
+
const batchSize = Math.max(1, (_a = capture.batchSize) != null ? _a : 10);
|
|
10012
|
+
const maxRetries = Math.max(1, (_b = capture.maxRetries) != null ? _b : 3);
|
|
10013
|
+
const batch = queueRef.current.slice(0, batchSize);
|
|
10014
|
+
const baseUrl = (_c = capture.baseUrl) != null ? _c : "https://getfounderhq.com";
|
|
10015
|
+
try {
|
|
10016
|
+
const response = await fetch(
|
|
10017
|
+
`${baseUrl}/api/v1/journeys/${encodeURIComponent(
|
|
10018
|
+
capture.journeyId
|
|
10019
|
+
)}/capture`,
|
|
10020
|
+
{
|
|
10021
|
+
method: "POST",
|
|
10022
|
+
headers: {
|
|
10023
|
+
Authorization: `Bearer ${capture.apiKey}`,
|
|
10024
|
+
"Content-Type": "application/json"
|
|
10025
|
+
},
|
|
10026
|
+
body: JSON.stringify({
|
|
10027
|
+
clientSessionId: runtime.clientSessionId,
|
|
10028
|
+
visitorId: runtime.visitorId,
|
|
10029
|
+
context: captureContext(capture, runtime),
|
|
10030
|
+
events: batch.map((item) => item.event)
|
|
10031
|
+
}),
|
|
10032
|
+
keepalive: batch.length <= 5
|
|
10033
|
+
}
|
|
10034
|
+
);
|
|
10035
|
+
if (!response.ok) throw new Error(`Capture failed: ${response.status}`);
|
|
10036
|
+
queueRef.current = queueRef.current.slice(batch.length);
|
|
10037
|
+
} catch (e) {
|
|
10038
|
+
const failedIds = new Set(batch.map((item) => item.event.id));
|
|
10039
|
+
queueRef.current = queueRef.current.map(
|
|
10040
|
+
(item) => failedIds.has(item.event.id) ? __spreadProps(__spreadValues({}, item), { attempts: item.attempts + 1 }) : item
|
|
10041
|
+
).filter((item) => item.attempts < maxRetries);
|
|
10042
|
+
} finally {
|
|
10043
|
+
persistQueue();
|
|
10044
|
+
flushingRef.current = false;
|
|
10045
|
+
}
|
|
10046
|
+
}, [ensureRuntime, persistQueue]);
|
|
10047
|
+
React.useEffect(() => {
|
|
10048
|
+
var _a;
|
|
10049
|
+
const capture = captureRef.current;
|
|
10050
|
+
if (!capture || typeof window === "undefined") return;
|
|
10051
|
+
ensureRuntime(capture);
|
|
10052
|
+
const interval = window.setInterval(
|
|
10053
|
+
() => void flush(),
|
|
10054
|
+
Math.max(1e3, (_a = capture.flushIntervalMs) != null ? _a : 3e3)
|
|
10055
|
+
);
|
|
10056
|
+
const handleVisibility = () => {
|
|
10057
|
+
if (document.visibilityState === "hidden") void flush();
|
|
10058
|
+
};
|
|
10059
|
+
window.addEventListener("beforeunload", persistQueue);
|
|
10060
|
+
document.addEventListener("visibilitychange", handleVisibility);
|
|
10061
|
+
void flush();
|
|
10062
|
+
return () => {
|
|
10063
|
+
window.clearInterval(interval);
|
|
10064
|
+
window.removeEventListener("beforeunload", persistQueue);
|
|
10065
|
+
document.removeEventListener("visibilitychange", handleVisibility);
|
|
10066
|
+
persistQueue();
|
|
10067
|
+
};
|
|
10068
|
+
}, [ensureRuntime, flush, persistQueue, params.capture]);
|
|
10069
|
+
return React.useCallback(
|
|
10070
|
+
(event) => {
|
|
10071
|
+
var _a, _b;
|
|
10072
|
+
(_a = onEventRef.current) == null ? void 0 : _a.call(onEventRef, event);
|
|
10073
|
+
const capture = captureRef.current;
|
|
10074
|
+
const config = configRef.current;
|
|
10075
|
+
if (!capture || !config) return;
|
|
10076
|
+
const runtime = ensureRuntime(capture);
|
|
10077
|
+
if (!runtime) return;
|
|
10078
|
+
sequenceRef.current += 1;
|
|
10079
|
+
queueRef.current.push({
|
|
10080
|
+
event: toCaptureEvent(config, event, sequenceRef.current, runtime),
|
|
10081
|
+
attempts: 0
|
|
10082
|
+
});
|
|
10083
|
+
persistQueue();
|
|
10084
|
+
const batchSize = Math.max(1, (_b = capture.batchSize) != null ? _b : 10);
|
|
10085
|
+
if (event.type === "complete" || queueRef.current.length >= batchSize) {
|
|
10086
|
+
void flush();
|
|
10087
|
+
}
|
|
10088
|
+
},
|
|
10089
|
+
[ensureRuntime, flush, persistQueue]
|
|
10090
|
+
);
|
|
10091
|
+
}
|
|
10092
|
+
var FOUNDERHQ_BASE_URL = "https://getfounderhq.com";
|
|
10093
|
+
function resolveJourneyBaseUrl(baseUrl) {
|
|
10094
|
+
if (!baseUrl) return FOUNDERHQ_BASE_URL;
|
|
10095
|
+
try {
|
|
10096
|
+
const parsed = new URL(baseUrl);
|
|
10097
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
10098
|
+
const isLocal = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
10099
|
+
return isLocal ? parsed.origin : FOUNDERHQ_BASE_URL;
|
|
10100
|
+
} catch (e) {
|
|
10101
|
+
return FOUNDERHQ_BASE_URL;
|
|
10102
|
+
}
|
|
10103
|
+
}
|
|
9638
10104
|
function useJourneyConfig({
|
|
9639
10105
|
apiKey,
|
|
9640
10106
|
journeyId,
|
|
9641
|
-
baseUrl
|
|
10107
|
+
baseUrl,
|
|
10108
|
+
config,
|
|
10109
|
+
capture = true
|
|
9642
10110
|
}) {
|
|
9643
10111
|
const [state, setState] = React.useState({ status: "loading" });
|
|
9644
10112
|
React.useEffect(() => {
|
|
9645
10113
|
let cancelled = false;
|
|
9646
|
-
|
|
9647
|
-
|
|
10114
|
+
const resolvedBaseUrl = resolveJourneyBaseUrl(baseUrl);
|
|
10115
|
+
async function loadConfig() {
|
|
10116
|
+
var _a, _b;
|
|
9648
10117
|
setState({ status: "loading" });
|
|
9649
10118
|
try {
|
|
9650
|
-
const
|
|
10119
|
+
const validateUrl = `${resolvedBaseUrl}/api/v1/journeys/${encodeURIComponent(
|
|
10120
|
+
journeyId
|
|
10121
|
+
)}/validate`;
|
|
10122
|
+
const validateRes = await fetch(validateUrl, {
|
|
10123
|
+
method: "POST",
|
|
10124
|
+
headers: {
|
|
10125
|
+
Authorization: `Bearer ${apiKey}`,
|
|
10126
|
+
"Content-Type": "application/json"
|
|
10127
|
+
},
|
|
10128
|
+
body: JSON.stringify({ capture })
|
|
10129
|
+
});
|
|
10130
|
+
if (!validateRes.ok) {
|
|
10131
|
+
const body = await validateRes.json().catch(() => ({}));
|
|
10132
|
+
throw new Error(
|
|
10133
|
+
(_a = body.error) != null ? _a : `Journey is unavailable (HTTP ${validateRes.status})`
|
|
10134
|
+
);
|
|
10135
|
+
}
|
|
10136
|
+
if (config) {
|
|
10137
|
+
if (!cancelled) {
|
|
10138
|
+
setState({ status: "ready", config });
|
|
10139
|
+
}
|
|
10140
|
+
return;
|
|
10141
|
+
}
|
|
10142
|
+
const url = `${resolvedBaseUrl}/api/v1/journeys/${encodeURIComponent(
|
|
10143
|
+
journeyId
|
|
10144
|
+
)}`;
|
|
9651
10145
|
const res = await fetch(url, {
|
|
9652
10146
|
headers: {
|
|
9653
10147
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -9657,7 +10151,7 @@ function useJourneyConfig({
|
|
|
9657
10151
|
if (!res.ok) {
|
|
9658
10152
|
const body = await res.json().catch(() => ({}));
|
|
9659
10153
|
throw new Error(
|
|
9660
|
-
(
|
|
10154
|
+
(_b = body.error) != null ? _b : `Failed to fetch journey config (HTTP ${res.status})`
|
|
9661
10155
|
);
|
|
9662
10156
|
}
|
|
9663
10157
|
const data = await res.json();
|
|
@@ -9673,11 +10167,11 @@ function useJourneyConfig({
|
|
|
9673
10167
|
}
|
|
9674
10168
|
}
|
|
9675
10169
|
}
|
|
9676
|
-
|
|
10170
|
+
loadConfig();
|
|
9677
10171
|
return () => {
|
|
9678
10172
|
cancelled = true;
|
|
9679
10173
|
};
|
|
9680
|
-
}, [apiKey, journeyId, baseUrl]);
|
|
10174
|
+
}, [apiKey, journeyId, baseUrl, config, capture]);
|
|
9681
10175
|
return state;
|
|
9682
10176
|
}
|
|
9683
10177
|
function DefaultLoading() {
|
|
@@ -9726,15 +10220,16 @@ function DefaultError({ error }) {
|
|
|
9726
10220
|
textAlign: "center"
|
|
9727
10221
|
},
|
|
9728
10222
|
children: [
|
|
9729
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "1.125rem", fontWeight: 600 }, children: "
|
|
10223
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "1.125rem", fontWeight: 600 }, children: "This Journey is unavailable" }),
|
|
9730
10224
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.875rem", marginTop: "0.5rem", opacity: 0.7 }, children: error.message })
|
|
9731
10225
|
]
|
|
9732
10226
|
}
|
|
9733
10227
|
);
|
|
9734
10228
|
}
|
|
9735
|
-
function
|
|
10229
|
+
function Journey({
|
|
9736
10230
|
apiKey,
|
|
9737
10231
|
journeyId,
|
|
10232
|
+
config,
|
|
9738
10233
|
baseUrl,
|
|
9739
10234
|
storageKey,
|
|
9740
10235
|
onEvent,
|
|
@@ -9744,9 +10239,28 @@ function JourneyRemote({
|
|
|
9744
10239
|
initialOptions,
|
|
9745
10240
|
onDiscountCodeApply,
|
|
9746
10241
|
loadingComponent,
|
|
9747
|
-
errorComponent
|
|
10242
|
+
errorComponent,
|
|
10243
|
+
capture
|
|
9748
10244
|
}) {
|
|
9749
|
-
const
|
|
10245
|
+
const resolvedBaseUrl = resolveJourneyBaseUrl(baseUrl);
|
|
10246
|
+
const captureEnabled = capture !== false;
|
|
10247
|
+
const state = useJourneyConfig({
|
|
10248
|
+
apiKey,
|
|
10249
|
+
journeyId,
|
|
10250
|
+
baseUrl: resolvedBaseUrl,
|
|
10251
|
+
config,
|
|
10252
|
+
capture: captureEnabled
|
|
10253
|
+
});
|
|
10254
|
+
const captureOption = capture === false ? false : __spreadValues({
|
|
10255
|
+
apiKey,
|
|
10256
|
+
journeyId,
|
|
10257
|
+
baseUrl: resolvedBaseUrl
|
|
10258
|
+
}, capture != null ? capture : {});
|
|
10259
|
+
const capturedOnEvent = useJourneyCapture({
|
|
10260
|
+
config: state.status === "ready" ? state.config : null,
|
|
10261
|
+
capture: captureOption,
|
|
10262
|
+
onEvent
|
|
10263
|
+
});
|
|
9750
10264
|
if (state.status === "loading") {
|
|
9751
10265
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent != null ? loadingComponent : /* @__PURE__ */ jsxRuntime.jsx(DefaultLoading, {}) });
|
|
9752
10266
|
}
|
|
@@ -9759,35 +10273,7 @@ function JourneyRemote({
|
|
|
9759
10273
|
config: state.config,
|
|
9760
10274
|
storageKey,
|
|
9761
10275
|
theme,
|
|
9762
|
-
onEvent,
|
|
9763
|
-
initialAnswers,
|
|
9764
|
-
initialOptions,
|
|
9765
|
-
onDiscountCodeApply,
|
|
9766
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(JourneyShell, { className, theme })
|
|
9767
|
-
}
|
|
9768
|
-
);
|
|
9769
|
-
}
|
|
9770
|
-
function Journey(props) {
|
|
9771
|
-
if ("apiKey" in props && props.apiKey) {
|
|
9772
|
-
return /* @__PURE__ */ jsxRuntime.jsx(JourneyRemote, __spreadValues({}, props));
|
|
9773
|
-
}
|
|
9774
|
-
const {
|
|
9775
|
-
config,
|
|
9776
|
-
storageKey,
|
|
9777
|
-
onEvent,
|
|
9778
|
-
className,
|
|
9779
|
-
theme,
|
|
9780
|
-
initialAnswers,
|
|
9781
|
-
initialOptions,
|
|
9782
|
-
onDiscountCodeApply
|
|
9783
|
-
} = props;
|
|
9784
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9785
|
-
JourneyProvider,
|
|
9786
|
-
{
|
|
9787
|
-
config,
|
|
9788
|
-
storageKey,
|
|
9789
|
-
theme,
|
|
9790
|
-
onEvent,
|
|
10276
|
+
onEvent: capturedOnEvent,
|
|
9791
10277
|
initialAnswers,
|
|
9792
10278
|
initialOptions,
|
|
9793
10279
|
onDiscountCodeApply,
|