@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.js
CHANGED
|
@@ -651,6 +651,8 @@ function JourneyProvider({
|
|
|
651
651
|
const { trigger } = useWebHaptics();
|
|
652
652
|
const onEventRef = useRef(onEvent);
|
|
653
653
|
const rawAnswersRef = useRef({});
|
|
654
|
+
const sessionStartEmittedRef = useRef(false);
|
|
655
|
+
const lastViewedStepIdRef = useRef(null);
|
|
654
656
|
const [discountCodeDialog, setDiscountCodeDialog] = useState(null);
|
|
655
657
|
useEffect(() => {
|
|
656
658
|
onEventRef.current = onEvent;
|
|
@@ -710,6 +712,39 @@ function JourneyProvider({
|
|
|
710
712
|
() => normalizedConfig.steps.flatMap(getStepVariables),
|
|
711
713
|
[normalizedConfig.steps]
|
|
712
714
|
);
|
|
715
|
+
useEffect(() => {
|
|
716
|
+
var _a, _b;
|
|
717
|
+
const currentStep = config.steps[currentStepIndex];
|
|
718
|
+
if (!currentStep) return;
|
|
719
|
+
const eventAnswers = getEventAnswers(
|
|
720
|
+
config.computedVariables,
|
|
721
|
+
rawAnswersRef.current
|
|
722
|
+
);
|
|
723
|
+
const strippedStep = stripOptionsFromEventStep(currentStep);
|
|
724
|
+
if (!sessionStartEmittedRef.current) {
|
|
725
|
+
sessionStartEmittedRef.current = true;
|
|
726
|
+
(_a = onEventRef.current) == null ? void 0 : _a.call(onEventRef, __spreadProps(__spreadValues({
|
|
727
|
+
type: "session_start",
|
|
728
|
+
step: strippedStep
|
|
729
|
+
}, eventAnswers), {
|
|
730
|
+
variables: allVariables
|
|
731
|
+
}));
|
|
732
|
+
}
|
|
733
|
+
if (lastViewedStepIdRef.current !== currentStep.id) {
|
|
734
|
+
lastViewedStepIdRef.current = currentStep.id;
|
|
735
|
+
(_b = onEventRef.current) == null ? void 0 : _b.call(onEventRef, __spreadProps(__spreadValues({
|
|
736
|
+
type: "step_view",
|
|
737
|
+
step: strippedStep
|
|
738
|
+
}, eventAnswers), {
|
|
739
|
+
variables: allVariables
|
|
740
|
+
}));
|
|
741
|
+
}
|
|
742
|
+
}, [
|
|
743
|
+
allVariables,
|
|
744
|
+
config.computedVariables,
|
|
745
|
+
config.steps,
|
|
746
|
+
currentStepIndex
|
|
747
|
+
]);
|
|
713
748
|
const setAnswer = useCallback((stepId, answer) => {
|
|
714
749
|
if (computedVariableIds.has(stepId)) return;
|
|
715
750
|
setRawAnswers((prev) => {
|
|
@@ -2638,9 +2673,11 @@ function ButtonBlock({
|
|
|
2638
2673
|
onGoToStep,
|
|
2639
2674
|
onPurchase,
|
|
2640
2675
|
onOpenDiscountCode,
|
|
2641
|
-
disabled
|
|
2676
|
+
disabled,
|
|
2677
|
+
isLastStep
|
|
2642
2678
|
}) {
|
|
2643
2679
|
const handleClick = () => {
|
|
2680
|
+
var _a;
|
|
2644
2681
|
if (!action) {
|
|
2645
2682
|
onNext == null ? void 0 : onNext();
|
|
2646
2683
|
return;
|
|
@@ -2660,6 +2697,9 @@ function ButtonBlock({
|
|
|
2660
2697
|
}
|
|
2661
2698
|
break;
|
|
2662
2699
|
case "link":
|
|
2700
|
+
if ((_a = action.completeBeforeNavigate) != null ? _a : isLastStep) {
|
|
2701
|
+
onNext == null ? void 0 : onNext();
|
|
2702
|
+
}
|
|
2663
2703
|
if (action.external) {
|
|
2664
2704
|
window.open(action.url, "_blank", "noopener,noreferrer");
|
|
2665
2705
|
} else {
|
|
@@ -4150,7 +4190,8 @@ function ColumnsBlock({
|
|
|
4150
4190
|
answers,
|
|
4151
4191
|
onNext,
|
|
4152
4192
|
onBack,
|
|
4153
|
-
onGoToStep
|
|
4193
|
+
onGoToStep,
|
|
4194
|
+
isLastStep
|
|
4154
4195
|
}) {
|
|
4155
4196
|
return /* @__PURE__ */ jsx("div", { className: "jy-columns-container", children: /* @__PURE__ */ jsx(
|
|
4156
4197
|
"div",
|
|
@@ -4176,6 +4217,7 @@ function ColumnsBlock({
|
|
|
4176
4217
|
onBack: onBack != null ? onBack : (() => {
|
|
4177
4218
|
}),
|
|
4178
4219
|
onGoToStep,
|
|
4220
|
+
isLastStep,
|
|
4179
4221
|
wrapInScrollViewport: false
|
|
4180
4222
|
}
|
|
4181
4223
|
)
|
|
@@ -7851,7 +7893,11 @@ function HiddenBlockSlot({ block }) {
|
|
|
7851
7893
|
return /* @__PURE__ */ jsx(
|
|
7852
7894
|
"div",
|
|
7853
7895
|
{
|
|
7854
|
-
className: cn(
|
|
7896
|
+
className: cn(
|
|
7897
|
+
widthClass,
|
|
7898
|
+
resolvedMaxWidth ? "mx-auto" : void 0,
|
|
7899
|
+
block.className
|
|
7900
|
+
),
|
|
7855
7901
|
style: __spreadProps(__spreadValues(__spreadValues({}, resolvedMaxWidth ? { maxWidth: resolvedMaxWidth } : null), block.style), {
|
|
7856
7902
|
visibility: "hidden"
|
|
7857
7903
|
}),
|
|
@@ -8034,7 +8080,7 @@ function resolveAppliedDiscountForDialog(answers, discountVariable, planId) {
|
|
|
8034
8080
|
if (!planId) return answer.planId ? void 0 : answer;
|
|
8035
8081
|
return discountAppliesToPlan(answer, planId) ? answer : void 0;
|
|
8036
8082
|
}
|
|
8037
|
-
function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, inputsValid) {
|
|
8083
|
+
function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToStep, onPurchase, onOpenDiscountCode, inputsValid, isLastStep) {
|
|
8038
8084
|
var _a, _b;
|
|
8039
8085
|
const conditionMet = !block.condition || evaluateCondition(block.condition, answers);
|
|
8040
8086
|
const hasExitAnim = ((_a = block.exitAnimation) == null ? void 0 : _a.preset) && block.exitAnimation.preset !== "none";
|
|
@@ -8061,8 +8107,9 @@ function renderBlock(block, i, visibleIndexRef, answers, onNext, onBack, onGoToS
|
|
|
8061
8107
|
onGoToStep,
|
|
8062
8108
|
onPurchase,
|
|
8063
8109
|
onOpenDiscountCode,
|
|
8064
|
-
disabled: buttonDisabledForValidity(resolvedProps, inputsValid)
|
|
8065
|
-
|
|
8110
|
+
disabled: buttonDisabledForValidity(resolvedProps, inputsValid),
|
|
8111
|
+
isLastStep
|
|
8112
|
+
}) : block.type === "columns" ? __spreadProps(__spreadValues({}, resolvedProps), { answers, onNext, onBack, onGoToStep, isLastStep }) : block.type === "gravity_bin" ? __spreadProps(__spreadValues({}, resolvedProps), { answers }) : resolvedProps;
|
|
8066
8113
|
const idx = visibleIndexRef.current;
|
|
8067
8114
|
visibleIndexRef.current++;
|
|
8068
8115
|
return /* @__PURE__ */ jsx(
|
|
@@ -8084,6 +8131,7 @@ function BlockRenderer({
|
|
|
8084
8131
|
onNext,
|
|
8085
8132
|
onBack,
|
|
8086
8133
|
onGoToStep,
|
|
8134
|
+
isLastStep,
|
|
8087
8135
|
scrollViewportRef,
|
|
8088
8136
|
wrapInScrollViewport = true
|
|
8089
8137
|
}) {
|
|
@@ -8169,7 +8217,8 @@ function BlockRenderer({
|
|
|
8169
8217
|
onGoToStep,
|
|
8170
8218
|
onPurchase,
|
|
8171
8219
|
onOpenDiscountCode,
|
|
8172
|
-
inputsValid
|
|
8220
|
+
inputsValid,
|
|
8221
|
+
isLastStep
|
|
8173
8222
|
);
|
|
8174
8223
|
};
|
|
8175
8224
|
if (!wrapInScrollViewport && stickyBlocks.length === 0) {
|
|
@@ -8192,7 +8241,11 @@ function BlockRenderer({
|
|
|
8192
8241
|
"div",
|
|
8193
8242
|
{
|
|
8194
8243
|
className: layoutClasses(layout),
|
|
8195
|
-
style: {
|
|
8244
|
+
style: {
|
|
8245
|
+
gap: `${gap}rem`,
|
|
8246
|
+
maxWidth: layout == null ? void 0 : layout.maxWidth,
|
|
8247
|
+
width: "100%"
|
|
8248
|
+
},
|
|
8196
8249
|
children: contentBlocks.map((block, i) => renderOne(block, i))
|
|
8197
8250
|
}
|
|
8198
8251
|
)
|
|
@@ -8203,7 +8256,11 @@ function BlockRenderer({
|
|
|
8203
8256
|
) }) : null
|
|
8204
8257
|
] });
|
|
8205
8258
|
}
|
|
8206
|
-
function InfoPageStep({
|
|
8259
|
+
function InfoPageStep({
|
|
8260
|
+
config,
|
|
8261
|
+
onNext,
|
|
8262
|
+
isLastStep
|
|
8263
|
+
}) {
|
|
8207
8264
|
var _a;
|
|
8208
8265
|
const { answers } = useJourneyState();
|
|
8209
8266
|
const { goBack, goToStep } = useJourneyActions();
|
|
@@ -8211,9 +8268,7 @@ function InfoPageStep({ config, onNext }) {
|
|
|
8211
8268
|
const sortedTimelineEvents = useMemo(
|
|
8212
8269
|
() => {
|
|
8213
8270
|
var _a2, _b;
|
|
8214
|
-
return [...(_b = (_a2 = config.scrollTimeline) == null ? void 0 : _a2.events) != null ? _b : []].sort(
|
|
8215
|
-
(a, b) => a.at - b.at
|
|
8216
|
-
);
|
|
8271
|
+
return [...(_b = (_a2 = config.scrollTimeline) == null ? void 0 : _a2.events) != null ? _b : []].sort((a, b) => a.at - b.at);
|
|
8217
8272
|
},
|
|
8218
8273
|
[(_a = config.scrollTimeline) == null ? void 0 : _a.events]
|
|
8219
8274
|
);
|
|
@@ -8393,6 +8448,7 @@ function InfoPageStep({ config, onNext }) {
|
|
|
8393
8448
|
onNext,
|
|
8394
8449
|
onBack: goBack,
|
|
8395
8450
|
onGoToStep: goToStep,
|
|
8451
|
+
isLastStep,
|
|
8396
8452
|
scrollViewportRef
|
|
8397
8453
|
}
|
|
8398
8454
|
);
|
|
@@ -9260,7 +9316,7 @@ var STEP_REGISTRY = {
|
|
|
9260
9316
|
function StepRenderer({ config }) {
|
|
9261
9317
|
var _a, _b;
|
|
9262
9318
|
const { answers } = useJourneyState();
|
|
9263
|
-
const { setAnswer, goNext } = useJourneyActions();
|
|
9319
|
+
const { setAnswer, goNext, isLastStep } = useJourneyActions();
|
|
9264
9320
|
const resolvedConfig = useMemo(() => {
|
|
9265
9321
|
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
9266
9322
|
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(
|
|
@@ -9313,7 +9369,8 @@ function StepRenderer({ config }) {
|
|
|
9313
9369
|
}
|
|
9314
9370
|
setAnswer(answerKey, answer);
|
|
9315
9371
|
},
|
|
9316
|
-
onNext: () => goNext()
|
|
9372
|
+
onNext: () => goNext(),
|
|
9373
|
+
isLastStep: isLastStep()
|
|
9317
9374
|
}
|
|
9318
9375
|
);
|
|
9319
9376
|
}
|
|
@@ -9610,19 +9667,456 @@ function JourneyShell({ className, theme } = {}) {
|
|
|
9610
9667
|
) })
|
|
9611
9668
|
] });
|
|
9612
9669
|
}
|
|
9670
|
+
var JOURNEY_LIBRARY_NAME = "@founderhq/journeys";
|
|
9671
|
+
var JOURNEY_LIBRARY_VERSION = "0.4.1";
|
|
9672
|
+
function randomId(prefix) {
|
|
9673
|
+
const cryptoRef = globalThis.crypto;
|
|
9674
|
+
if (cryptoRef == null ? void 0 : cryptoRef.randomUUID) return `${prefix}_${cryptoRef.randomUUID()}`;
|
|
9675
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
9676
|
+
}
|
|
9677
|
+
function readJson(storage, key, fallback) {
|
|
9678
|
+
try {
|
|
9679
|
+
const raw = storage.getItem(key);
|
|
9680
|
+
return raw ? JSON.parse(raw) : fallback;
|
|
9681
|
+
} catch (e) {
|
|
9682
|
+
return fallback;
|
|
9683
|
+
}
|
|
9684
|
+
}
|
|
9685
|
+
function getStoredId(storage, key, prefix) {
|
|
9686
|
+
try {
|
|
9687
|
+
const existing = storage.getItem(key);
|
|
9688
|
+
if (existing) return existing;
|
|
9689
|
+
const next = randomId(prefix);
|
|
9690
|
+
storage.setItem(key, next);
|
|
9691
|
+
return next;
|
|
9692
|
+
} catch (e) {
|
|
9693
|
+
return randomId(prefix);
|
|
9694
|
+
}
|
|
9695
|
+
}
|
|
9696
|
+
function isRecord(value) {
|
|
9697
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9698
|
+
}
|
|
9699
|
+
function cleanObject(value) {
|
|
9700
|
+
if (!value || typeof value !== "object") return {};
|
|
9701
|
+
return __spreadValues({}, value);
|
|
9702
|
+
}
|
|
9703
|
+
var DEFAULT_REDACTED_URL_PARAMS = [
|
|
9704
|
+
"access_token",
|
|
9705
|
+
"api_key",
|
|
9706
|
+
"auth",
|
|
9707
|
+
"code",
|
|
9708
|
+
"email",
|
|
9709
|
+
"key",
|
|
9710
|
+
"otp",
|
|
9711
|
+
"password",
|
|
9712
|
+
"phone",
|
|
9713
|
+
"secret",
|
|
9714
|
+
"signature",
|
|
9715
|
+
"token"
|
|
9716
|
+
];
|
|
9717
|
+
function safeContextFactory(factory) {
|
|
9718
|
+
if (typeof factory !== "function") return void 0;
|
|
9719
|
+
try {
|
|
9720
|
+
const value = factory();
|
|
9721
|
+
return isRecord(value) ? value : void 0;
|
|
9722
|
+
} catch (e) {
|
|
9723
|
+
return void 0;
|
|
9724
|
+
}
|
|
9725
|
+
}
|
|
9726
|
+
function safeTimezone() {
|
|
9727
|
+
try {
|
|
9728
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
9729
|
+
} catch (e) {
|
|
9730
|
+
return void 0;
|
|
9731
|
+
}
|
|
9732
|
+
}
|
|
9733
|
+
function campaignContext(params) {
|
|
9734
|
+
var _a, _b, _c, _d, _e;
|
|
9735
|
+
return cleanObject({
|
|
9736
|
+
source: (_a = params.get("utm_source")) != null ? _a : void 0,
|
|
9737
|
+
medium: (_b = params.get("utm_medium")) != null ? _b : void 0,
|
|
9738
|
+
name: (_c = params.get("utm_campaign")) != null ? _c : void 0,
|
|
9739
|
+
term: (_d = params.get("utm_term")) != null ? _d : void 0,
|
|
9740
|
+
content: (_e = params.get("utm_content")) != null ? _e : void 0
|
|
9741
|
+
});
|
|
9742
|
+
}
|
|
9743
|
+
function redactedSearch(params, redactUrlParams) {
|
|
9744
|
+
if ([...params.keys()].length === 0) return "";
|
|
9745
|
+
const next = new URLSearchParams(params);
|
|
9746
|
+
const redactAll = redactUrlParams === true;
|
|
9747
|
+
const names = Array.isArray(redactUrlParams) && redactUrlParams.length > 0 ? redactUrlParams : DEFAULT_REDACTED_URL_PARAMS;
|
|
9748
|
+
const redactedNames = new Set(names.map((name) => name.toLowerCase()));
|
|
9749
|
+
for (const key of [...next.keys()]) {
|
|
9750
|
+
if (redactAll || redactedNames.has(key.toLowerCase())) {
|
|
9751
|
+
next.set(key, "[redacted]");
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9754
|
+
const value = next.toString();
|
|
9755
|
+
return value ? `?${value}` : "";
|
|
9756
|
+
}
|
|
9757
|
+
function pageContext(redactUrlParams) {
|
|
9758
|
+
if (typeof window === "undefined") return void 0;
|
|
9759
|
+
const params = new URLSearchParams(window.location.search);
|
|
9760
|
+
const campaign = campaignContext(params);
|
|
9761
|
+
const search = redactedSearch(params, redactUrlParams);
|
|
9762
|
+
return cleanObject({
|
|
9763
|
+
url: `${window.location.origin}${window.location.pathname}${search}${window.location.hash}`,
|
|
9764
|
+
path: window.location.pathname,
|
|
9765
|
+
search: search || void 0,
|
|
9766
|
+
title: document.title,
|
|
9767
|
+
referrer: document.referrer,
|
|
9768
|
+
campaign
|
|
9769
|
+
});
|
|
9770
|
+
}
|
|
9771
|
+
function captureContext(capture, runtime) {
|
|
9772
|
+
var _a, _b;
|
|
9773
|
+
const customContext = cleanObject(__spreadValues(__spreadValues({}, (_a = capture.context) != null ? _a : {}), (_b = safeContextFactory(capture.contextFactory)) != null ? _b : {}));
|
|
9774
|
+
if (capture.captureContext === false) {
|
|
9775
|
+
return customContext;
|
|
9776
|
+
}
|
|
9777
|
+
const page = pageContext(capture.redactUrlParams);
|
|
9778
|
+
const navigatorRef = typeof window !== "undefined" ? window.navigator : void 0;
|
|
9779
|
+
const screenRef = typeof window !== "undefined" ? window.screen : void 0;
|
|
9780
|
+
return cleanObject(__spreadValues({
|
|
9781
|
+
source: { type: "embed", product: "journeys" },
|
|
9782
|
+
library: {
|
|
9783
|
+
name: JOURNEY_LIBRARY_NAME,
|
|
9784
|
+
version: JOURNEY_LIBRARY_VERSION
|
|
9785
|
+
},
|
|
9786
|
+
anonymousId: runtime.visitorId,
|
|
9787
|
+
sessionId: runtime.clientSessionId,
|
|
9788
|
+
locale: navigatorRef == null ? void 0 : navigatorRef.language,
|
|
9789
|
+
timezone: safeTimezone(),
|
|
9790
|
+
userAgent: navigatorRef == null ? void 0 : navigatorRef.userAgent,
|
|
9791
|
+
page,
|
|
9792
|
+
campaign: isRecord(page) ? page.campaign : void 0,
|
|
9793
|
+
screen: screenRef ? { width: screenRef.width, height: screenRef.height } : void 0
|
|
9794
|
+
}, customContext));
|
|
9795
|
+
}
|
|
9796
|
+
function toJsonRecord(value) {
|
|
9797
|
+
return isRecord(value) ? value : {};
|
|
9798
|
+
}
|
|
9799
|
+
function answerValue(answers, variable) {
|
|
9800
|
+
return Object.prototype.hasOwnProperty.call(answers, variable) ? answers[variable] : void 0;
|
|
9801
|
+
}
|
|
9802
|
+
function stepAnswerVariables(step) {
|
|
9803
|
+
var _a, _b, _c, _d, _e;
|
|
9804
|
+
if (step.type === "swipe_cards") {
|
|
9805
|
+
return ((_a = step.swipeCards) != null ? _a : []).map((card) => ({
|
|
9806
|
+
stepId: step.id,
|
|
9807
|
+
variable: card.variable,
|
|
9808
|
+
label: card.text
|
|
9809
|
+
}));
|
|
9810
|
+
}
|
|
9811
|
+
if (step.type === "input") {
|
|
9812
|
+
return ((_b = step.fields) != null ? _b : []).map((field) => {
|
|
9813
|
+
var _a2;
|
|
9814
|
+
return {
|
|
9815
|
+
stepId: step.id,
|
|
9816
|
+
variable: (_a2 = field.variable) != null ? _a2 : field.id,
|
|
9817
|
+
label: field.label,
|
|
9818
|
+
fieldType: field.type
|
|
9819
|
+
};
|
|
9820
|
+
});
|
|
9821
|
+
}
|
|
9822
|
+
return [
|
|
9823
|
+
{
|
|
9824
|
+
stepId: step.id,
|
|
9825
|
+
variable: (_c = step.variable) != null ? _c : step.id,
|
|
9826
|
+
label: (_e = (_d = step.question) != null ? _d : step.preface) != null ? _e : step.id
|
|
9827
|
+
}
|
|
9828
|
+
];
|
|
9829
|
+
}
|
|
9830
|
+
function buildAnswerSnapshot(config, answers) {
|
|
9831
|
+
const items = [];
|
|
9832
|
+
for (const step of config.steps) {
|
|
9833
|
+
for (const item of stepAnswerVariables(step)) {
|
|
9834
|
+
const value = answerValue(answers, item.variable);
|
|
9835
|
+
if (value === void 0) continue;
|
|
9836
|
+
items.push({
|
|
9837
|
+
stepId: item.stepId,
|
|
9838
|
+
variable: item.variable,
|
|
9839
|
+
label: item.label,
|
|
9840
|
+
value
|
|
9841
|
+
});
|
|
9842
|
+
}
|
|
9843
|
+
}
|
|
9844
|
+
return { items };
|
|
9845
|
+
}
|
|
9846
|
+
function buildRespondentSnapshot(config, answers) {
|
|
9847
|
+
var _a, _b;
|
|
9848
|
+
const snapshot = {};
|
|
9849
|
+
for (const step of config.steps) {
|
|
9850
|
+
if (step.type !== "input") continue;
|
|
9851
|
+
for (const field of (_a = step.fields) != null ? _a : []) {
|
|
9852
|
+
const variable = (_b = field.variable) != null ? _b : field.id;
|
|
9853
|
+
const value = answerValue(answers, variable);
|
|
9854
|
+
if (value !== void 0) snapshot[variable] = value;
|
|
9855
|
+
}
|
|
9856
|
+
}
|
|
9857
|
+
return snapshot;
|
|
9858
|
+
}
|
|
9859
|
+
function buildContactRef(config, answers) {
|
|
9860
|
+
var _a, _b;
|
|
9861
|
+
const contact = {};
|
|
9862
|
+
for (const step of config.steps) {
|
|
9863
|
+
if (step.type !== "input") continue;
|
|
9864
|
+
for (const field of (_a = step.fields) != null ? _a : []) {
|
|
9865
|
+
const variable = (_b = field.variable) != null ? _b : field.id;
|
|
9866
|
+
const value = answerValue(answers, variable);
|
|
9867
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
9868
|
+
const normalizedVariable = variable.toLowerCase().replace(/[-_\s]/g, "");
|
|
9869
|
+
if (field.type === "email" || normalizedVariable.includes("email")) {
|
|
9870
|
+
contact.email = value;
|
|
9871
|
+
} else if (field.type === "tel" || normalizedVariable.includes("phone")) {
|
|
9872
|
+
contact.phone = value;
|
|
9873
|
+
} else if (normalizedVariable === "externalid") {
|
|
9874
|
+
contact.externalId = value;
|
|
9875
|
+
}
|
|
9876
|
+
}
|
|
9877
|
+
}
|
|
9878
|
+
return Object.keys(contact).length > 0 ? contact : void 0;
|
|
9879
|
+
}
|
|
9880
|
+
function eventStepId(event) {
|
|
9881
|
+
if ("step" in event) return event.step.id;
|
|
9882
|
+
if (event.type === "navigate") return event.to.id;
|
|
9883
|
+
if (event.type === "purchase_intent") return event.stepId;
|
|
9884
|
+
return void 0;
|
|
9885
|
+
}
|
|
9886
|
+
function eventProperties(event) {
|
|
9887
|
+
if (event.type === "step_submit") {
|
|
9888
|
+
return { submitted: event.submitted };
|
|
9889
|
+
}
|
|
9890
|
+
if (event.type === "navigate") {
|
|
9891
|
+
return {
|
|
9892
|
+
fromStepId: event.from.id,
|
|
9893
|
+
toStepId: event.to.id,
|
|
9894
|
+
direction: event.direction
|
|
9895
|
+
};
|
|
9896
|
+
}
|
|
9897
|
+
if (event.type === "purchase_intent") {
|
|
9898
|
+
return __spreadValues({
|
|
9899
|
+
variable: event.variable,
|
|
9900
|
+
plan: event.plan
|
|
9901
|
+
}, event.discount ? { discount: event.discount } : {});
|
|
9902
|
+
}
|
|
9903
|
+
return {};
|
|
9904
|
+
}
|
|
9905
|
+
function toCaptureEvent(config, event, sequence, runtime) {
|
|
9906
|
+
const answers = "answers" in event ? event.answers : {};
|
|
9907
|
+
const payload = {
|
|
9908
|
+
id: createCaptureEventId(runtime, event, sequence),
|
|
9909
|
+
type: event.type,
|
|
9910
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9911
|
+
stepId: eventStepId(event),
|
|
9912
|
+
properties: eventProperties(event),
|
|
9913
|
+
answers: toJsonRecord(answers),
|
|
9914
|
+
computedVariables: "computedVariables" in event ? toJsonRecord(event.computedVariables) : void 0
|
|
9915
|
+
};
|
|
9916
|
+
if (event.type === "complete") {
|
|
9917
|
+
payload.respondentSnapshot = buildRespondentSnapshot(config, answers);
|
|
9918
|
+
payload.answerSnapshot = buildAnswerSnapshot(config, answers);
|
|
9919
|
+
payload.contact = buildContactRef(config, answers);
|
|
9920
|
+
}
|
|
9921
|
+
return payload;
|
|
9922
|
+
}
|
|
9923
|
+
function createCaptureEventId(runtime, event, sequence) {
|
|
9924
|
+
return `${runtime.clientSessionId}:${event.type}:${sequence.toString(
|
|
9925
|
+
36
|
|
9926
|
+
)}:${randomId("fhqe")}`;
|
|
9927
|
+
}
|
|
9928
|
+
function useJourneyCapture(params) {
|
|
9929
|
+
const onEventRef = useRef(params.onEvent);
|
|
9930
|
+
const configRef = useRef(params.config);
|
|
9931
|
+
const captureRef = useRef(params.capture);
|
|
9932
|
+
const runtimeRef = useRef(null);
|
|
9933
|
+
const queueRef = useRef([]);
|
|
9934
|
+
const sequenceRef = useRef(0);
|
|
9935
|
+
const flushingRef = useRef(false);
|
|
9936
|
+
onEventRef.current = params.onEvent;
|
|
9937
|
+
configRef.current = params.config;
|
|
9938
|
+
captureRef.current = params.capture;
|
|
9939
|
+
const persistQueue = useCallback(() => {
|
|
9940
|
+
const runtime = runtimeRef.current;
|
|
9941
|
+
if (!runtime || typeof window === "undefined") return;
|
|
9942
|
+
try {
|
|
9943
|
+
sessionStorage.setItem(
|
|
9944
|
+
runtime.queueKey,
|
|
9945
|
+
JSON.stringify(queueRef.current)
|
|
9946
|
+
);
|
|
9947
|
+
} catch (e) {
|
|
9948
|
+
}
|
|
9949
|
+
}, []);
|
|
9950
|
+
const ensureRuntime = useCallback((capture) => {
|
|
9951
|
+
if (typeof window === "undefined") return null;
|
|
9952
|
+
const existing = runtimeRef.current;
|
|
9953
|
+
if ((existing == null ? void 0 : existing.journeyId) === capture.journeyId) return existing;
|
|
9954
|
+
const visitorId = getStoredId(
|
|
9955
|
+
localStorage,
|
|
9956
|
+
"fhq_journey_visitor_id",
|
|
9957
|
+
"fhqv"
|
|
9958
|
+
);
|
|
9959
|
+
const clientSessionId = getStoredId(
|
|
9960
|
+
sessionStorage,
|
|
9961
|
+
`fhq_journey_session:${capture.journeyId}`,
|
|
9962
|
+
"fhqs"
|
|
9963
|
+
);
|
|
9964
|
+
const queueKey = `fhq_journey_queue:${capture.journeyId}:${clientSessionId}`;
|
|
9965
|
+
const runtime = {
|
|
9966
|
+
journeyId: capture.journeyId,
|
|
9967
|
+
visitorId,
|
|
9968
|
+
clientSessionId,
|
|
9969
|
+
queueKey
|
|
9970
|
+
};
|
|
9971
|
+
runtimeRef.current = runtime;
|
|
9972
|
+
queueRef.current = readJson(
|
|
9973
|
+
sessionStorage,
|
|
9974
|
+
queueKey,
|
|
9975
|
+
[]
|
|
9976
|
+
);
|
|
9977
|
+
return runtime;
|
|
9978
|
+
}, []);
|
|
9979
|
+
const flush = useCallback(async () => {
|
|
9980
|
+
var _a, _b, _c;
|
|
9981
|
+
const capture = captureRef.current;
|
|
9982
|
+
if (!capture || !configRef.current || flushingRef.current) return;
|
|
9983
|
+
const runtime = ensureRuntime(capture);
|
|
9984
|
+
if (!runtime || queueRef.current.length === 0) return;
|
|
9985
|
+
flushingRef.current = true;
|
|
9986
|
+
const batchSize = Math.max(1, (_a = capture.batchSize) != null ? _a : 10);
|
|
9987
|
+
const maxRetries = Math.max(1, (_b = capture.maxRetries) != null ? _b : 3);
|
|
9988
|
+
const batch = queueRef.current.slice(0, batchSize);
|
|
9989
|
+
const baseUrl = (_c = capture.baseUrl) != null ? _c : "https://getfounderhq.com";
|
|
9990
|
+
try {
|
|
9991
|
+
const response = await fetch(
|
|
9992
|
+
`${baseUrl}/api/v1/journeys/${encodeURIComponent(
|
|
9993
|
+
capture.journeyId
|
|
9994
|
+
)}/capture`,
|
|
9995
|
+
{
|
|
9996
|
+
method: "POST",
|
|
9997
|
+
headers: {
|
|
9998
|
+
Authorization: `Bearer ${capture.apiKey}`,
|
|
9999
|
+
"Content-Type": "application/json"
|
|
10000
|
+
},
|
|
10001
|
+
body: JSON.stringify({
|
|
10002
|
+
clientSessionId: runtime.clientSessionId,
|
|
10003
|
+
visitorId: runtime.visitorId,
|
|
10004
|
+
context: captureContext(capture, runtime),
|
|
10005
|
+
events: batch.map((item) => item.event)
|
|
10006
|
+
}),
|
|
10007
|
+
keepalive: batch.length <= 5
|
|
10008
|
+
}
|
|
10009
|
+
);
|
|
10010
|
+
if (!response.ok) throw new Error(`Capture failed: ${response.status}`);
|
|
10011
|
+
queueRef.current = queueRef.current.slice(batch.length);
|
|
10012
|
+
} catch (e) {
|
|
10013
|
+
const failedIds = new Set(batch.map((item) => item.event.id));
|
|
10014
|
+
queueRef.current = queueRef.current.map(
|
|
10015
|
+
(item) => failedIds.has(item.event.id) ? __spreadProps(__spreadValues({}, item), { attempts: item.attempts + 1 }) : item
|
|
10016
|
+
).filter((item) => item.attempts < maxRetries);
|
|
10017
|
+
} finally {
|
|
10018
|
+
persistQueue();
|
|
10019
|
+
flushingRef.current = false;
|
|
10020
|
+
}
|
|
10021
|
+
}, [ensureRuntime, persistQueue]);
|
|
10022
|
+
useEffect(() => {
|
|
10023
|
+
var _a;
|
|
10024
|
+
const capture = captureRef.current;
|
|
10025
|
+
if (!capture || typeof window === "undefined") return;
|
|
10026
|
+
ensureRuntime(capture);
|
|
10027
|
+
const interval = window.setInterval(
|
|
10028
|
+
() => void flush(),
|
|
10029
|
+
Math.max(1e3, (_a = capture.flushIntervalMs) != null ? _a : 3e3)
|
|
10030
|
+
);
|
|
10031
|
+
const handleVisibility = () => {
|
|
10032
|
+
if (document.visibilityState === "hidden") void flush();
|
|
10033
|
+
};
|
|
10034
|
+
window.addEventListener("beforeunload", persistQueue);
|
|
10035
|
+
document.addEventListener("visibilitychange", handleVisibility);
|
|
10036
|
+
void flush();
|
|
10037
|
+
return () => {
|
|
10038
|
+
window.clearInterval(interval);
|
|
10039
|
+
window.removeEventListener("beforeunload", persistQueue);
|
|
10040
|
+
document.removeEventListener("visibilitychange", handleVisibility);
|
|
10041
|
+
persistQueue();
|
|
10042
|
+
};
|
|
10043
|
+
}, [ensureRuntime, flush, persistQueue, params.capture]);
|
|
10044
|
+
return useCallback(
|
|
10045
|
+
(event) => {
|
|
10046
|
+
var _a, _b;
|
|
10047
|
+
(_a = onEventRef.current) == null ? void 0 : _a.call(onEventRef, event);
|
|
10048
|
+
const capture = captureRef.current;
|
|
10049
|
+
const config = configRef.current;
|
|
10050
|
+
if (!capture || !config) return;
|
|
10051
|
+
const runtime = ensureRuntime(capture);
|
|
10052
|
+
if (!runtime) return;
|
|
10053
|
+
sequenceRef.current += 1;
|
|
10054
|
+
queueRef.current.push({
|
|
10055
|
+
event: toCaptureEvent(config, event, sequenceRef.current, runtime),
|
|
10056
|
+
attempts: 0
|
|
10057
|
+
});
|
|
10058
|
+
persistQueue();
|
|
10059
|
+
const batchSize = Math.max(1, (_b = capture.batchSize) != null ? _b : 10);
|
|
10060
|
+
if (event.type === "complete" || queueRef.current.length >= batchSize) {
|
|
10061
|
+
void flush();
|
|
10062
|
+
}
|
|
10063
|
+
},
|
|
10064
|
+
[ensureRuntime, flush, persistQueue]
|
|
10065
|
+
);
|
|
10066
|
+
}
|
|
10067
|
+
var FOUNDERHQ_BASE_URL = "https://getfounderhq.com";
|
|
10068
|
+
function resolveJourneyBaseUrl(baseUrl) {
|
|
10069
|
+
if (!baseUrl) return FOUNDERHQ_BASE_URL;
|
|
10070
|
+
try {
|
|
10071
|
+
const parsed = new URL(baseUrl);
|
|
10072
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
10073
|
+
const isLocal = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
10074
|
+
return isLocal ? parsed.origin : FOUNDERHQ_BASE_URL;
|
|
10075
|
+
} catch (e) {
|
|
10076
|
+
return FOUNDERHQ_BASE_URL;
|
|
10077
|
+
}
|
|
10078
|
+
}
|
|
9613
10079
|
function useJourneyConfig({
|
|
9614
10080
|
apiKey,
|
|
9615
10081
|
journeyId,
|
|
9616
|
-
baseUrl
|
|
10082
|
+
baseUrl,
|
|
10083
|
+
config,
|
|
10084
|
+
capture = true
|
|
9617
10085
|
}) {
|
|
9618
10086
|
const [state, setState] = useState({ status: "loading" });
|
|
9619
10087
|
useEffect(() => {
|
|
9620
10088
|
let cancelled = false;
|
|
9621
|
-
|
|
9622
|
-
|
|
10089
|
+
const resolvedBaseUrl = resolveJourneyBaseUrl(baseUrl);
|
|
10090
|
+
async function loadConfig() {
|
|
10091
|
+
var _a, _b;
|
|
9623
10092
|
setState({ status: "loading" });
|
|
9624
10093
|
try {
|
|
9625
|
-
const
|
|
10094
|
+
const validateUrl = `${resolvedBaseUrl}/api/v1/journeys/${encodeURIComponent(
|
|
10095
|
+
journeyId
|
|
10096
|
+
)}/validate`;
|
|
10097
|
+
const validateRes = await fetch(validateUrl, {
|
|
10098
|
+
method: "POST",
|
|
10099
|
+
headers: {
|
|
10100
|
+
Authorization: `Bearer ${apiKey}`,
|
|
10101
|
+
"Content-Type": "application/json"
|
|
10102
|
+
},
|
|
10103
|
+
body: JSON.stringify({ capture })
|
|
10104
|
+
});
|
|
10105
|
+
if (!validateRes.ok) {
|
|
10106
|
+
const body = await validateRes.json().catch(() => ({}));
|
|
10107
|
+
throw new Error(
|
|
10108
|
+
(_a = body.error) != null ? _a : `Journey is unavailable (HTTP ${validateRes.status})`
|
|
10109
|
+
);
|
|
10110
|
+
}
|
|
10111
|
+
if (config) {
|
|
10112
|
+
if (!cancelled) {
|
|
10113
|
+
setState({ status: "ready", config });
|
|
10114
|
+
}
|
|
10115
|
+
return;
|
|
10116
|
+
}
|
|
10117
|
+
const url = `${resolvedBaseUrl}/api/v1/journeys/${encodeURIComponent(
|
|
10118
|
+
journeyId
|
|
10119
|
+
)}`;
|
|
9626
10120
|
const res = await fetch(url, {
|
|
9627
10121
|
headers: {
|
|
9628
10122
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -9632,7 +10126,7 @@ function useJourneyConfig({
|
|
|
9632
10126
|
if (!res.ok) {
|
|
9633
10127
|
const body = await res.json().catch(() => ({}));
|
|
9634
10128
|
throw new Error(
|
|
9635
|
-
(
|
|
10129
|
+
(_b = body.error) != null ? _b : `Failed to fetch journey config (HTTP ${res.status})`
|
|
9636
10130
|
);
|
|
9637
10131
|
}
|
|
9638
10132
|
const data = await res.json();
|
|
@@ -9648,11 +10142,11 @@ function useJourneyConfig({
|
|
|
9648
10142
|
}
|
|
9649
10143
|
}
|
|
9650
10144
|
}
|
|
9651
|
-
|
|
10145
|
+
loadConfig();
|
|
9652
10146
|
return () => {
|
|
9653
10147
|
cancelled = true;
|
|
9654
10148
|
};
|
|
9655
|
-
}, [apiKey, journeyId, baseUrl]);
|
|
10149
|
+
}, [apiKey, journeyId, baseUrl, config, capture]);
|
|
9656
10150
|
return state;
|
|
9657
10151
|
}
|
|
9658
10152
|
function DefaultLoading() {
|
|
@@ -9701,15 +10195,16 @@ function DefaultError({ error }) {
|
|
|
9701
10195
|
textAlign: "center"
|
|
9702
10196
|
},
|
|
9703
10197
|
children: [
|
|
9704
|
-
/* @__PURE__ */ jsx("p", { style: { fontSize: "1.125rem", fontWeight: 600 }, children: "
|
|
10198
|
+
/* @__PURE__ */ jsx("p", { style: { fontSize: "1.125rem", fontWeight: 600 }, children: "This Journey is unavailable" }),
|
|
9705
10199
|
/* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", marginTop: "0.5rem", opacity: 0.7 }, children: error.message })
|
|
9706
10200
|
]
|
|
9707
10201
|
}
|
|
9708
10202
|
);
|
|
9709
10203
|
}
|
|
9710
|
-
function
|
|
10204
|
+
function Journey({
|
|
9711
10205
|
apiKey,
|
|
9712
10206
|
journeyId,
|
|
10207
|
+
config,
|
|
9713
10208
|
baseUrl,
|
|
9714
10209
|
storageKey,
|
|
9715
10210
|
onEvent,
|
|
@@ -9719,9 +10214,28 @@ function JourneyRemote({
|
|
|
9719
10214
|
initialOptions,
|
|
9720
10215
|
onDiscountCodeApply,
|
|
9721
10216
|
loadingComponent,
|
|
9722
|
-
errorComponent
|
|
10217
|
+
errorComponent,
|
|
10218
|
+
capture
|
|
9723
10219
|
}) {
|
|
9724
|
-
const
|
|
10220
|
+
const resolvedBaseUrl = resolveJourneyBaseUrl(baseUrl);
|
|
10221
|
+
const captureEnabled = capture !== false;
|
|
10222
|
+
const state = useJourneyConfig({
|
|
10223
|
+
apiKey,
|
|
10224
|
+
journeyId,
|
|
10225
|
+
baseUrl: resolvedBaseUrl,
|
|
10226
|
+
config,
|
|
10227
|
+
capture: captureEnabled
|
|
10228
|
+
});
|
|
10229
|
+
const captureOption = capture === false ? false : __spreadValues({
|
|
10230
|
+
apiKey,
|
|
10231
|
+
journeyId,
|
|
10232
|
+
baseUrl: resolvedBaseUrl
|
|
10233
|
+
}, capture != null ? capture : {});
|
|
10234
|
+
const capturedOnEvent = useJourneyCapture({
|
|
10235
|
+
config: state.status === "ready" ? state.config : null,
|
|
10236
|
+
capture: captureOption,
|
|
10237
|
+
onEvent
|
|
10238
|
+
});
|
|
9725
10239
|
if (state.status === "loading") {
|
|
9726
10240
|
return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent != null ? loadingComponent : /* @__PURE__ */ jsx(DefaultLoading, {}) });
|
|
9727
10241
|
}
|
|
@@ -9734,35 +10248,7 @@ function JourneyRemote({
|
|
|
9734
10248
|
config: state.config,
|
|
9735
10249
|
storageKey,
|
|
9736
10250
|
theme,
|
|
9737
|
-
onEvent,
|
|
9738
|
-
initialAnswers,
|
|
9739
|
-
initialOptions,
|
|
9740
|
-
onDiscountCodeApply,
|
|
9741
|
-
children: /* @__PURE__ */ jsx(JourneyShell, { className, theme })
|
|
9742
|
-
}
|
|
9743
|
-
);
|
|
9744
|
-
}
|
|
9745
|
-
function Journey(props) {
|
|
9746
|
-
if ("apiKey" in props && props.apiKey) {
|
|
9747
|
-
return /* @__PURE__ */ jsx(JourneyRemote, __spreadValues({}, props));
|
|
9748
|
-
}
|
|
9749
|
-
const {
|
|
9750
|
-
config,
|
|
9751
|
-
storageKey,
|
|
9752
|
-
onEvent,
|
|
9753
|
-
className,
|
|
9754
|
-
theme,
|
|
9755
|
-
initialAnswers,
|
|
9756
|
-
initialOptions,
|
|
9757
|
-
onDiscountCodeApply
|
|
9758
|
-
} = props;
|
|
9759
|
-
return /* @__PURE__ */ jsx(
|
|
9760
|
-
JourneyProvider,
|
|
9761
|
-
{
|
|
9762
|
-
config,
|
|
9763
|
-
storageKey,
|
|
9764
|
-
theme,
|
|
9765
|
-
onEvent,
|
|
10251
|
+
onEvent: capturedOnEvent,
|
|
9766
10252
|
initialAnswers,
|
|
9767
10253
|
initialOptions,
|
|
9768
10254
|
onDiscountCodeApply,
|