@funnelsgrove/runtime 0.1.0 → 0.1.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 +20 -1
- package/dist/components/FunnelContext.d.ts +5 -2
- package/dist/components/FunnelContext.js +3 -0
- package/dist/components/FunnelEditorPanel.d.ts +3 -5
- package/dist/components/FunnelEditorPanel.js +3 -3
- package/dist/components/ManageSubscriptionScreen.d.ts +51 -0
- package/dist/components/ManageSubscriptionScreen.js +349 -0
- package/dist/components/RuntimeDevInfoBox.d.ts +23 -0
- package/dist/components/RuntimeDevInfoBox.js +363 -0
- package/dist/components/SubscriptionHandoffScreen.d.ts +31 -0
- package/dist/components/SubscriptionHandoffScreen.js +338 -0
- package/dist/config/builder-preview.protocol.d.ts +73 -0
- package/dist/config/builder-preview.protocol.js +3 -0
- package/dist/config/env.config.d.ts +44 -0
- package/dist/config/env.config.js +161 -0
- package/dist/config/font-config.d.ts +14 -0
- package/dist/config/font-config.js +101 -0
- package/dist/config/funnel-theme.d.ts +61 -10
- package/dist/config/funnel-theme.js +355 -35
- package/dist/config/funnel.manifest.types.d.ts +13 -7
- package/dist/content/step-content.d.ts +130 -0
- package/dist/content/step-content.js +381 -0
- package/dist/index.d.ts +33 -21
- package/dist/index.js +33 -21
- package/dist/runtime/browser-helpers.d.ts +1 -0
- package/dist/runtime/browser-helpers.js +14 -0
- package/dist/runtime/experiment-assignment.d.ts +13 -4
- package/dist/runtime/experiment-assignment.js +9 -27
- package/dist/runtime/funnel-attribution.d.ts +18 -0
- package/dist/runtime/funnel-attribution.js +226 -0
- package/dist/runtime/funnel-flow.d.ts +9 -10
- package/dist/runtime/funnel-flow.js +4 -18
- package/dist/runtime/funnel-manifest.validation.d.ts +1 -1
- package/dist/runtime/funnel-manifest.validation.js +2 -6
- package/dist/runtime/funnel-runtime.d.ts +2 -3
- package/dist/runtime/funnel-runtime.js +6 -13
- package/dist/runtime/posthog-flags.d.ts +30 -0
- package/dist/runtime/posthog-flags.js +71 -0
- package/dist/runtime/preview-bridge.d.ts +13 -3
- package/dist/runtime/preview-bridge.js +96 -4
- package/dist/runtime/preview-definition-overrides.d.ts +20 -0
- package/dist/runtime/preview-definition-overrides.js +148 -0
- package/dist/runtime/route-resolver.d.ts +2 -3
- package/dist/runtime/route-resolver.js +15 -26
- package/dist/runtime/subscription-handoff.d.ts +32 -0
- package/dist/runtime/subscription-handoff.js +113 -0
- package/dist/runtime/use-funnel-flow-controller.d.ts +19 -10
- package/dist/runtime/use-funnel-flow-controller.js +190 -159
- package/dist/sdk/userAnswers.d.ts +2 -2
- package/dist/services/api.service.d.ts +21 -4
- package/dist/services/api.service.js +165 -35
- package/dist/services/funnel-state.service.d.ts +8 -0
- package/dist/services/funnel-state.service.js +44 -0
- package/dist/services/preview-frame.service.d.ts +2 -2
- package/dist/services/preview-frame.service.js +2 -2
- package/dist/services/public-env.d.ts +69 -0
- package/dist/services/public-env.js +105 -0
- package/dist/services/runtime-api.config.d.ts +5 -0
- package/dist/services/runtime-api.config.js +12 -7
- package/dist/services/runtime-mode.service.d.ts +3 -0
- package/dist/services/runtime-mode.service.js +142 -4
- package/package.json +8 -2
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
-
import { BUILDER_PREVIEW_ACTIVE_STEP_CHANGED, BUILDER_PREVIEW_GO_TO_STEP, BUILDER_PREVIEW_READY, BUILDER_PREVIEW_RUNTIME_MODE_CHANGED, } from '../config/builder-preview.protocol';
|
|
4
|
-
import { isPreviewFrameRuntime } from '../services/preview-frame.service';
|
|
5
|
-
import { logger } from '../services/logger';
|
|
3
|
+
import { BUILDER_PREVIEW_ACTIVE_STEP_CHANGED, BUILDER_PREVIEW_DEFINITION_PATCH, BUILDER_PREVIEW_GO_TO_STEP, BUILDER_PREVIEW_PAYWALL_PLANS_CHANGED, BUILDER_PREVIEW_READY, BUILDER_PREVIEW_RUNTIME_MODE_CHANGED, BUILDER_PREVIEW_VARIABLE_VALUES_CHANGED, } from '../config/builder-preview.protocol.js';
|
|
4
|
+
import { isPreviewFrameRuntime } from '../services/preview-frame.service.js';
|
|
5
|
+
import { logger } from '../services/logger.js';
|
|
6
|
+
import { applyPreviewDefinitionPatch, applyPreviewPaywallPlansPatch } from './preview-definition-overrides.js';
|
|
6
7
|
const isRecord = (value) => {
|
|
7
8
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
8
9
|
};
|
|
10
|
+
const parsePreviewDefinitionPatch = (value) => {
|
|
11
|
+
if (!isRecord(value)) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const kind = typeof value.kind === 'string' ? value.kind : '';
|
|
15
|
+
const stepId = typeof value.stepId === 'string' ? value.stepId.trim() : '';
|
|
16
|
+
if (!kind || !stepId) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (kind === 'content') {
|
|
20
|
+
const locale = typeof value.locale === 'string' ? value.locale.trim() : '';
|
|
21
|
+
if (!locale || !isRecord(value.content)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
kind,
|
|
26
|
+
stepId,
|
|
27
|
+
locale,
|
|
28
|
+
content: value.content,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (kind === 'pricing') {
|
|
32
|
+
if (!isRecord(value.pricing)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
kind,
|
|
37
|
+
stepId,
|
|
38
|
+
pricing: value.pricing,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
};
|
|
9
43
|
const isPositiveIndex = (value) => {
|
|
10
44
|
return typeof value === 'number' && Number.isInteger(value) && value >= 1;
|
|
11
45
|
};
|
|
@@ -85,12 +119,36 @@ export const parsePreviewBridgeMessage = (value) => {
|
|
|
85
119
|
const patch = parsePreviewQuickEditPatch(value.patch);
|
|
86
120
|
return patch ? { kind: 'quickEditPatch', patch } : null;
|
|
87
121
|
}
|
|
122
|
+
if (messageType === BUILDER_PREVIEW_DEFINITION_PATCH) {
|
|
123
|
+
const patch = parsePreviewDefinitionPatch(value.patch);
|
|
124
|
+
return patch ? { kind: 'definitionPatch', patch } : null;
|
|
125
|
+
}
|
|
88
126
|
if (messageType === BUILDER_PREVIEW_RUNTIME_MODE_CHANGED) {
|
|
89
127
|
return {
|
|
90
128
|
kind: 'runtimeModeChanged',
|
|
91
129
|
mode: value.mode === 'live' ? 'live' : 'test',
|
|
92
130
|
};
|
|
93
131
|
}
|
|
132
|
+
if (messageType === BUILDER_PREVIEW_PAYWALL_PLANS_CHANGED) {
|
|
133
|
+
const stepId = typeof value.stepId === 'string' ? value.stepId.trim() : '';
|
|
134
|
+
const plans = Array.isArray(value.plans)
|
|
135
|
+
? value.plans.filter((plan) => isRecord(plan) &&
|
|
136
|
+
typeof plan.id === 'string' &&
|
|
137
|
+
typeof plan.title === 'string' &&
|
|
138
|
+
typeof plan.priceLabel === 'string' &&
|
|
139
|
+
typeof plan.perDayAmount === 'string' &&
|
|
140
|
+
typeof plan.perDayLabel === 'string' &&
|
|
141
|
+
typeof plan.amountCents === 'number' &&
|
|
142
|
+
(plan.source === 'stripe' || plan.source === 'config'))
|
|
143
|
+
: [];
|
|
144
|
+
return stepId
|
|
145
|
+
? {
|
|
146
|
+
kind: 'paywallPlansChanged',
|
|
147
|
+
stepId,
|
|
148
|
+
plans,
|
|
149
|
+
}
|
|
150
|
+
: null;
|
|
151
|
+
}
|
|
94
152
|
if (messageType === BUILDER_PREVIEW_GO_TO_STEP) {
|
|
95
153
|
const stepId = typeof value.stepId === 'string' ? value.stepId.trim() : '';
|
|
96
154
|
return stepId ? { kind: 'goToStep', stepId } : null;
|
|
@@ -181,6 +239,25 @@ function emitPreviewReady(stepId) {
|
|
|
181
239
|
stepId,
|
|
182
240
|
}, '*');
|
|
183
241
|
}
|
|
242
|
+
export function emitPreviewVariableValues(stepId, values) {
|
|
243
|
+
if (typeof window === 'undefined' || window.parent === window) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
window.parent.postMessage({
|
|
247
|
+
type: BUILDER_PREVIEW_VARIABLE_VALUES_CHANGED,
|
|
248
|
+
stepId,
|
|
249
|
+
values,
|
|
250
|
+
}, '*');
|
|
251
|
+
}
|
|
252
|
+
export function usePreviewVariableValues(stepId, values) {
|
|
253
|
+
const serializedValues = JSON.stringify(values);
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
if (!isPreviewFrameRuntime()) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
emitPreviewVariableValues(stepId, values);
|
|
259
|
+
}, [serializedValues, stepId, values]);
|
|
260
|
+
}
|
|
184
261
|
export function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableStepId, setRuntimeMode, shouldLockToInitialStep, }) {
|
|
185
262
|
const previewReadySentRef = useRef(false);
|
|
186
263
|
useEffect(() => {
|
|
@@ -209,6 +286,14 @@ export function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableSt
|
|
|
209
286
|
applyPreviewQuickEditPatch(action.patch);
|
|
210
287
|
return;
|
|
211
288
|
}
|
|
289
|
+
if (action.kind === 'definitionPatch') {
|
|
290
|
+
applyPreviewDefinitionPatch(action.patch);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (action.kind === 'paywallPlansChanged') {
|
|
294
|
+
applyPreviewPaywallPlansPatch(action.stepId, action.plans);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
212
297
|
if (action.kind === 'runtimeModeChanged') {
|
|
213
298
|
setRuntimeMode(action.mode);
|
|
214
299
|
return;
|
|
@@ -226,5 +311,12 @@ export function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableSt
|
|
|
226
311
|
return () => {
|
|
227
312
|
window.removeEventListener('message', handlePreviewMessage);
|
|
228
313
|
};
|
|
229
|
-
}, [
|
|
314
|
+
}, [
|
|
315
|
+
activeStepId,
|
|
316
|
+
onGoToStep,
|
|
317
|
+
resolveRenderableStepId,
|
|
318
|
+
setRuntimeMode,
|
|
319
|
+
shouldLockToInitialStep,
|
|
320
|
+
]);
|
|
230
321
|
}
|
|
322
|
+
export { applyPreviewDefinitionPatch, clearPreviewDefinitionPatches, resolvePreviewStepPaywallPlans, resolvePreviewStepCountryPricing, resolvePreviewStepLocalizedContent, usePreviewDefinitionOverrideRevision, usePreviewStepPaywallPlans, usePreviewStepCountryPricing, usePreviewStepLocalizedContent, } from './preview-definition-overrides.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CountryPricingProfile, type LocalizedStepContent, type ResolvedCountryPricing, type StepEditorSection } from '../content/step-content.js';
|
|
2
|
+
import type { BuilderPreviewDefinitionPatch } from '../config/builder-preview.protocol.js';
|
|
3
|
+
export declare const usePreviewDefinitionOverrideRevision: () => number;
|
|
4
|
+
export declare const clearPreviewDefinitionPatches: () => void;
|
|
5
|
+
export declare const applyPreviewDefinitionPatch: (patch: BuilderPreviewDefinitionPatch) => void;
|
|
6
|
+
export declare const applyPreviewPaywallPlansPatch: (stepId: string, plans: readonly unknown[]) => void;
|
|
7
|
+
export declare const resolvePreviewStepLocalizedContent: <TLocaleContent>(stepId: string, definition: LocalizedStepContent<TLocaleContent>, requestedLocale?: string | null, options?: {
|
|
8
|
+
editorSections?: readonly StepEditorSection[];
|
|
9
|
+
variableValues?: Record<string, string>;
|
|
10
|
+
universalVariables?: Record<string, string>;
|
|
11
|
+
}) => TLocaleContent;
|
|
12
|
+
export declare const resolvePreviewStepCountryPricing: <TOfferKey extends string>(stepId: string, pricingProfile: CountryPricingProfile<TOfferKey>, requestedCountryCode?: string | null) => ResolvedCountryPricing<TOfferKey>;
|
|
13
|
+
export declare const usePreviewStepLocalizedContent: <TLocaleContent>(stepId: string, definition: LocalizedStepContent<TLocaleContent>, requestedLocale?: string | null, options?: {
|
|
14
|
+
editorSections?: readonly StepEditorSection[];
|
|
15
|
+
variableValues?: Record<string, string>;
|
|
16
|
+
universalVariables?: Record<string, string>;
|
|
17
|
+
}) => TLocaleContent;
|
|
18
|
+
export declare const usePreviewStepCountryPricing: <TOfferKey extends string>(stepId: string, pricingProfile: CountryPricingProfile<TOfferKey>, requestedCountryCode?: string | null) => ResolvedCountryPricing<TOfferKey>;
|
|
19
|
+
export declare const resolvePreviewStepPaywallPlans: <TPlan>(stepId: string) => readonly TPlan[] | null;
|
|
20
|
+
export declare const usePreviewStepPaywallPlans: <TPlan>(stepId: string) => readonly TPlan[] | null;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useMemo, useSyncExternalStore } from 'react';
|
|
3
|
+
import { resolveStepContentVariables, resolveUniversalContentVariables, resolveCountryPricing, resolveLocalizedStepContent, } from '../content/step-content.js';
|
|
4
|
+
import { useOptionalFunnel } from '../components/FunnelContext.js';
|
|
5
|
+
import { apiService } from '../services/api.service.js';
|
|
6
|
+
const PREVIEW_DEFINITION_OVERRIDE_STORE_KEY = '__funnelsgrovePreviewDefinitionOverrides';
|
|
7
|
+
const getPreviewDefinitionOverrideStore = () => {
|
|
8
|
+
const globalState = globalThis;
|
|
9
|
+
const existingStore = globalState[PREVIEW_DEFINITION_OVERRIDE_STORE_KEY];
|
|
10
|
+
if (existingStore) {
|
|
11
|
+
return existingStore;
|
|
12
|
+
}
|
|
13
|
+
const nextStore = {
|
|
14
|
+
listeners: new Set(),
|
|
15
|
+
state: {
|
|
16
|
+
revision: 0,
|
|
17
|
+
contentByStepId: {},
|
|
18
|
+
pricingByStepId: {},
|
|
19
|
+
paywallPlansByStepId: {},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
globalState[PREVIEW_DEFINITION_OVERRIDE_STORE_KEY] = nextStore;
|
|
23
|
+
return nextStore;
|
|
24
|
+
};
|
|
25
|
+
const normalizeStepId = (value) => {
|
|
26
|
+
return value.trim();
|
|
27
|
+
};
|
|
28
|
+
const normalizeLocale = (value) => {
|
|
29
|
+
return value.trim().toLowerCase();
|
|
30
|
+
};
|
|
31
|
+
const subscribePreviewDefinitionOverrides = (listener) => {
|
|
32
|
+
const store = getPreviewDefinitionOverrideStore();
|
|
33
|
+
store.listeners.add(listener);
|
|
34
|
+
return () => {
|
|
35
|
+
store.listeners.delete(listener);
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const emitPreviewDefinitionOverrideChange = () => {
|
|
39
|
+
for (const listener of getPreviewDefinitionOverrideStore().listeners) {
|
|
40
|
+
listener();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const getPreviewDefinitionOverrideRevision = () => {
|
|
44
|
+
return getPreviewDefinitionOverrideStore().state.revision;
|
|
45
|
+
};
|
|
46
|
+
export const usePreviewDefinitionOverrideRevision = () => {
|
|
47
|
+
return useSyncExternalStore(subscribePreviewDefinitionOverrides, getPreviewDefinitionOverrideRevision, () => 0);
|
|
48
|
+
};
|
|
49
|
+
const buildMergedLocalizedContentDefinition = (stepId, definition) => {
|
|
50
|
+
const stepOverrides = getPreviewDefinitionOverrideStore().state.contentByStepId[normalizeStepId(stepId)];
|
|
51
|
+
if (!stepOverrides) {
|
|
52
|
+
return definition;
|
|
53
|
+
}
|
|
54
|
+
const mergedLocales = Object.assign({}, definition.locales);
|
|
55
|
+
for (const [locale, localeOverride] of Object.entries(stepOverrides)) {
|
|
56
|
+
const currentLocaleValue = definition.locales[locale];
|
|
57
|
+
if (currentLocaleValue && typeof currentLocaleValue === 'object' && localeOverride) {
|
|
58
|
+
mergedLocales[locale] = Object.assign(Object.assign({}, currentLocaleValue), localeOverride);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
mergedLocales[locale] = localeOverride;
|
|
62
|
+
}
|
|
63
|
+
return Object.assign(Object.assign({}, definition), { locales: mergedLocales });
|
|
64
|
+
};
|
|
65
|
+
export const clearPreviewDefinitionPatches = () => {
|
|
66
|
+
const store = getPreviewDefinitionOverrideStore();
|
|
67
|
+
store.state = {
|
|
68
|
+
revision: store.state.revision + 1,
|
|
69
|
+
contentByStepId: {},
|
|
70
|
+
pricingByStepId: {},
|
|
71
|
+
paywallPlansByStepId: {},
|
|
72
|
+
};
|
|
73
|
+
emitPreviewDefinitionOverrideChange();
|
|
74
|
+
};
|
|
75
|
+
export const applyPreviewDefinitionPatch = (patch) => {
|
|
76
|
+
const store = getPreviewDefinitionOverrideStore();
|
|
77
|
+
const stepId = normalizeStepId(patch.stepId);
|
|
78
|
+
if (!stepId) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (patch.kind === 'content') {
|
|
82
|
+
const locale = normalizeLocale(patch.locale);
|
|
83
|
+
if (!locale) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
store.state = Object.assign(Object.assign({}, store.state), { revision: store.state.revision + 1, contentByStepId: Object.assign(Object.assign({}, store.state.contentByStepId), { [stepId]: Object.assign(Object.assign({}, (store.state.contentByStepId[stepId] || {})), { [locale]: patch.content }) }) });
|
|
87
|
+
emitPreviewDefinitionOverrideChange();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
store.state = Object.assign(Object.assign({}, store.state), { revision: store.state.revision + 1, pricingByStepId: Object.assign(Object.assign({}, store.state.pricingByStepId), { [stepId]: patch.pricing }) });
|
|
91
|
+
emitPreviewDefinitionOverrideChange();
|
|
92
|
+
};
|
|
93
|
+
export const applyPreviewPaywallPlansPatch = (stepId, plans) => {
|
|
94
|
+
const normalizedStepId = normalizeStepId(stepId);
|
|
95
|
+
if (!normalizedStepId) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const store = getPreviewDefinitionOverrideStore();
|
|
99
|
+
store.state = Object.assign(Object.assign({}, store.state), { revision: store.state.revision + 1, paywallPlansByStepId: Object.assign(Object.assign({}, store.state.paywallPlansByStepId), { [normalizedStepId]: [...plans] }) });
|
|
100
|
+
emitPreviewDefinitionOverrideChange();
|
|
101
|
+
};
|
|
102
|
+
export const resolvePreviewStepLocalizedContent = (stepId, definition, requestedLocale, options) => {
|
|
103
|
+
var _a;
|
|
104
|
+
const localizedContent = resolveLocalizedStepContent(buildMergedLocalizedContentDefinition(stepId, definition), requestedLocale);
|
|
105
|
+
if (!((_a = options === null || options === void 0 ? void 0 : options.editorSections) === null || _a === void 0 ? void 0 : _a.length) || !options.variableValues) {
|
|
106
|
+
if (!(options === null || options === void 0 ? void 0 : options.universalVariables)) {
|
|
107
|
+
return localizedContent;
|
|
108
|
+
}
|
|
109
|
+
return resolveUniversalContentVariables(localizedContent, options.universalVariables);
|
|
110
|
+
}
|
|
111
|
+
const contentWithEditorVariables = resolveStepContentVariables(localizedContent, options.editorSections, options.variableValues);
|
|
112
|
+
if (!options.universalVariables) {
|
|
113
|
+
return contentWithEditorVariables;
|
|
114
|
+
}
|
|
115
|
+
return resolveUniversalContentVariables(contentWithEditorVariables, options.universalVariables);
|
|
116
|
+
};
|
|
117
|
+
export const resolvePreviewStepCountryPricing = (stepId, pricingProfile, requestedCountryCode) => {
|
|
118
|
+
const pricingOverride = getPreviewDefinitionOverrideStore().state.pricingByStepId[normalizeStepId(stepId)];
|
|
119
|
+
return resolveCountryPricing(pricingOverride || pricingProfile, requestedCountryCode);
|
|
120
|
+
};
|
|
121
|
+
export const usePreviewStepLocalizedContent = (stepId, definition, requestedLocale, options) => {
|
|
122
|
+
const revision = usePreviewDefinitionOverrideRevision();
|
|
123
|
+
const funnel = useOptionalFunnel();
|
|
124
|
+
const currentUserId = (funnel === null || funnel === void 0 ? void 0 : funnel.user.id) || apiService.getOrCreateClientUserId();
|
|
125
|
+
return useMemo(() => {
|
|
126
|
+
var _a;
|
|
127
|
+
void revision;
|
|
128
|
+
return resolvePreviewStepLocalizedContent(stepId, definition, requestedLocale, Object.assign(Object.assign({}, options), { universalVariables: Object.assign(Object.assign({}, ((_a = options === null || options === void 0 ? void 0 : options.universalVariables) !== null && _a !== void 0 ? _a : {})), { user_id: currentUserId }) }));
|
|
129
|
+
}, [currentUserId, definition, options, requestedLocale, revision, stepId]);
|
|
130
|
+
};
|
|
131
|
+
export const usePreviewStepCountryPricing = (stepId, pricingProfile, requestedCountryCode) => {
|
|
132
|
+
const revision = usePreviewDefinitionOverrideRevision();
|
|
133
|
+
return useMemo(() => {
|
|
134
|
+
void revision;
|
|
135
|
+
return resolvePreviewStepCountryPricing(stepId, pricingProfile, requestedCountryCode);
|
|
136
|
+
}, [pricingProfile, requestedCountryCode, revision, stepId]);
|
|
137
|
+
};
|
|
138
|
+
export const resolvePreviewStepPaywallPlans = (stepId) => {
|
|
139
|
+
const plans = getPreviewDefinitionOverrideStore().state.paywallPlansByStepId[normalizeStepId(stepId)];
|
|
140
|
+
return Array.isArray(plans) ? plans : null;
|
|
141
|
+
};
|
|
142
|
+
export const usePreviewStepPaywallPlans = (stepId) => {
|
|
143
|
+
const revision = usePreviewDefinitionOverrideRevision();
|
|
144
|
+
return useMemo(() => {
|
|
145
|
+
void revision;
|
|
146
|
+
return resolvePreviewStepPaywallPlans(stepId);
|
|
147
|
+
}, [revision, stepId]);
|
|
148
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FunnelManifest, FunnelManifestExperiment } from '../config/funnel.manifest.types';
|
|
1
|
+
import type { FunnelManifest, FunnelManifestExperiment } from '../config/funnel.manifest.types.js';
|
|
2
2
|
export declare const getChoiceTargetsFromEdges: (manifest: FunnelManifest, stepId: string) => {
|
|
3
3
|
yes?: string;
|
|
4
4
|
no?: string;
|
|
@@ -8,11 +8,10 @@ export declare const getPathForStep: (manifest: FunnelManifest, stepId: string)
|
|
|
8
8
|
export declare const resolveInitialStepId: (input: {
|
|
9
9
|
manifest: FunnelManifest;
|
|
10
10
|
requestedStepId?: string | null;
|
|
11
|
-
entryPointId?: string | null;
|
|
12
11
|
}) => string;
|
|
13
12
|
export declare const resolveNextStepId: (input: {
|
|
14
13
|
manifest: FunnelManifest;
|
|
15
14
|
currentStepId: string;
|
|
16
15
|
attributes: Record<string, unknown>;
|
|
17
|
-
|
|
16
|
+
resolveExperimentVariantKey: (experiment: FunnelManifestExperiment) => string | null;
|
|
18
17
|
}) => string | null;
|
|
@@ -4,21 +4,12 @@ const getStepById = (manifest, stepId) => {
|
|
|
4
4
|
}
|
|
5
5
|
return manifest.steps.find((step) => step.id === stepId) || null;
|
|
6
6
|
};
|
|
7
|
-
const
|
|
7
|
+
const getDefaultManifestStepId = (manifest) => {
|
|
8
8
|
var _a;
|
|
9
|
-
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
return ((_a = manifest.entryPoints.find((entryPoint) => entryPoint.id === entryPointId)) === null || _a === void 0 ? void 0 : _a.stepId) || null;
|
|
9
|
+
return ((_a = manifest.steps[0]) === null || _a === void 0 ? void 0 : _a.id) || '';
|
|
13
10
|
};
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
return (((_a = manifest.entryPoints.find((entryPoint) => 'isDefault' in entryPoint && entryPoint.isDefault)) === null || _a === void 0 ? void 0 : _a.stepId) ||
|
|
17
|
-
((_b = manifest.steps[0]) === null || _b === void 0 ? void 0 : _b.id) ||
|
|
18
|
-
'');
|
|
19
|
-
};
|
|
20
|
-
const getActiveExperimentForStep = (manifest, stepId) => {
|
|
21
|
-
return (manifest.experiments.find((experiment) => experiment.stepId === stepId && experiment.status === 'active') || null);
|
|
11
|
+
const getExperimentForStep = (manifest, stepId) => {
|
|
12
|
+
return manifest.experiments.find((experiment) => experiment.stepId === stepId) || null;
|
|
22
13
|
};
|
|
23
14
|
const resolveNextStepFromEdges = (edges, attributes) => {
|
|
24
15
|
var _a;
|
|
@@ -69,23 +60,21 @@ export const resolveInitialStepId = (input) => {
|
|
|
69
60
|
if (requestedStep) {
|
|
70
61
|
return requestedStep.id;
|
|
71
62
|
}
|
|
72
|
-
|
|
73
|
-
const entryPointStep = getStepById(input.manifest, entryPointStepId);
|
|
74
|
-
if (entryPointStep) {
|
|
75
|
-
return entryPointStep.id;
|
|
76
|
-
}
|
|
77
|
-
return getDefaultEntryPointStepId(input.manifest);
|
|
63
|
+
return getDefaultManifestStepId(input.manifest);
|
|
78
64
|
};
|
|
79
65
|
export const resolveNextStepId = (input) => {
|
|
80
66
|
var _a;
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
67
|
+
const experiment = getExperimentForStep(input.manifest, input.currentStepId);
|
|
68
|
+
if (experiment) {
|
|
69
|
+
const variantKey = input.resolveExperimentVariantKey(experiment);
|
|
70
|
+
const matchedVariant = variantKey
|
|
71
|
+
? experiment.variants.find((variant) => variant.variantKey === variantKey)
|
|
72
|
+
: undefined;
|
|
73
|
+
if (matchedVariant) {
|
|
74
|
+
return matchedVariant.routeToStepId;
|
|
87
75
|
}
|
|
88
|
-
|
|
76
|
+
const controlVariant = (_a = experiment.variants.find((variant) => variant.variantKey === 'control')) !== null && _a !== void 0 ? _a : experiment.variants[0];
|
|
77
|
+
return (controlVariant === null || controlVariant === void 0 ? void 0 : controlVariant.routeToStepId) || null;
|
|
89
78
|
}
|
|
90
79
|
return resolveNextStepFromEdges(input.manifest.edgesByStepId[input.currentStepId], input.attributes);
|
|
91
80
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FunnelUserAttribution } from './funnel-attribution.js';
|
|
2
|
+
export type SubscriptionHandoffPlatform = 'ios' | 'android' | 'desktop';
|
|
3
|
+
export type SubscriptionHandoffConfig = {
|
|
4
|
+
iosAppStoreUrl: string | null;
|
|
5
|
+
androidPlayStoreUrl: string | null;
|
|
6
|
+
universalLink: string | null;
|
|
7
|
+
iosDeepLink: string | null;
|
|
8
|
+
androidDeepLink: string | null;
|
|
9
|
+
};
|
|
10
|
+
export type ResolvedSubscriptionHandoff = {
|
|
11
|
+
platform: SubscriptionHandoffPlatform;
|
|
12
|
+
iosStoreUrl: string | null;
|
|
13
|
+
androidStoreUrl: string | null;
|
|
14
|
+
iosDeepLinkUrl: string | null;
|
|
15
|
+
androidDeepLinkUrl: string | null;
|
|
16
|
+
openAppUrl: string | null;
|
|
17
|
+
webLinkUrl: string | null;
|
|
18
|
+
emailOpenAppUrl: string | null;
|
|
19
|
+
emailIosDeepLinkUrl: string | null;
|
|
20
|
+
emailAndroidDeepLinkUrl: string | null;
|
|
21
|
+
emailIosStoreUrl: string | null;
|
|
22
|
+
emailAndroidStoreUrl: string | null;
|
|
23
|
+
qrConfirmationUrl: string | null;
|
|
24
|
+
};
|
|
25
|
+
export declare const resolveSubscriptionHandoff: (input: {
|
|
26
|
+
userId: string | null | undefined;
|
|
27
|
+
email?: string | null;
|
|
28
|
+
confirmationUrl: string | null | undefined;
|
|
29
|
+
userAgent?: string | null;
|
|
30
|
+
attribution?: FunnelUserAttribution | null;
|
|
31
|
+
config: SubscriptionHandoffConfig;
|
|
32
|
+
}) => ResolvedSubscriptionHandoff;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const asTrimmedString = (value) => {
|
|
2
|
+
if (typeof value !== 'string') {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
return trimmed || null;
|
|
7
|
+
};
|
|
8
|
+
const resolvePlatform = (userAgent) => {
|
|
9
|
+
var _a;
|
|
10
|
+
const normalized = ((_a = asTrimmedString(userAgent)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
|
|
11
|
+
if (/iphone|ipad|ipod/.test(normalized)) {
|
|
12
|
+
return 'ios';
|
|
13
|
+
}
|
|
14
|
+
if (/android/.test(normalized)) {
|
|
15
|
+
return 'android';
|
|
16
|
+
}
|
|
17
|
+
return 'desktop';
|
|
18
|
+
};
|
|
19
|
+
const applyLinkTemplates = (value, variables) => {
|
|
20
|
+
return value
|
|
21
|
+
.replace(/\{user_id\}/g, encodeURIComponent(variables.userId))
|
|
22
|
+
.replace(/\{email\}/g, variables.email ? encodeURIComponent(variables.email) : '');
|
|
23
|
+
};
|
|
24
|
+
const resolveUrl = (value, variables, firstTouch) => {
|
|
25
|
+
const normalized = asTrimmedString(value);
|
|
26
|
+
if (!normalized) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(applyLinkTemplates(normalized, variables));
|
|
31
|
+
if (!url.searchParams.has('user_id')) {
|
|
32
|
+
url.searchParams.set('user_id', variables.userId);
|
|
33
|
+
}
|
|
34
|
+
for (const [key, rawValue] of Object.entries(firstTouch)) {
|
|
35
|
+
const nextKey = key.trim();
|
|
36
|
+
const nextValue = rawValue.trim();
|
|
37
|
+
if (!nextKey || !nextValue || url.searchParams.has(nextKey)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
url.searchParams.set(nextKey, nextValue);
|
|
41
|
+
}
|
|
42
|
+
return url.toString();
|
|
43
|
+
}
|
|
44
|
+
catch (_a) {
|
|
45
|
+
return applyLinkTemplates(normalized, variables);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const buildQrConfirmationUrl = (confirmationUrl, userId) => {
|
|
49
|
+
const normalized = asTrimmedString(confirmationUrl);
|
|
50
|
+
if (!normalized) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const url = new URL(normalized);
|
|
55
|
+
url.search = '';
|
|
56
|
+
url.hash = '';
|
|
57
|
+
url.searchParams.set('user_id', userId);
|
|
58
|
+
return url.toString();
|
|
59
|
+
}
|
|
60
|
+
catch (_a) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
export const resolveSubscriptionHandoff = (input) => {
|
|
65
|
+
var _a, _b;
|
|
66
|
+
const userId = asTrimmedString(input.userId);
|
|
67
|
+
const email = asTrimmedString(input.email);
|
|
68
|
+
const platform = resolvePlatform(input.userAgent);
|
|
69
|
+
const firstTouch = (_b = (_a = input.attribution) === null || _a === void 0 ? void 0 : _a.firstTouch) !== null && _b !== void 0 ? _b : {};
|
|
70
|
+
if (!userId) {
|
|
71
|
+
return {
|
|
72
|
+
platform,
|
|
73
|
+
iosStoreUrl: null,
|
|
74
|
+
androidStoreUrl: null,
|
|
75
|
+
iosDeepLinkUrl: null,
|
|
76
|
+
androidDeepLinkUrl: null,
|
|
77
|
+
openAppUrl: null,
|
|
78
|
+
webLinkUrl: null,
|
|
79
|
+
emailOpenAppUrl: null,
|
|
80
|
+
emailIosDeepLinkUrl: null,
|
|
81
|
+
emailAndroidDeepLinkUrl: null,
|
|
82
|
+
emailIosStoreUrl: null,
|
|
83
|
+
emailAndroidStoreUrl: null,
|
|
84
|
+
qrConfirmationUrl: null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const variables = { userId, email };
|
|
88
|
+
const iosStoreUrl = resolveUrl(input.config.iosAppStoreUrl, variables, firstTouch);
|
|
89
|
+
const androidStoreUrl = resolveUrl(input.config.androidPlayStoreUrl, variables, firstTouch);
|
|
90
|
+
const iosDeepLink = resolveUrl(input.config.iosDeepLink, variables, firstTouch);
|
|
91
|
+
const androidDeepLink = resolveUrl(input.config.androidDeepLink, variables, firstTouch);
|
|
92
|
+
const universalLink = resolveUrl(input.config.universalLink, variables, firstTouch);
|
|
93
|
+
const openAppUrl = platform === 'ios'
|
|
94
|
+
? iosDeepLink || universalLink || iosStoreUrl
|
|
95
|
+
: platform === 'android'
|
|
96
|
+
? androidDeepLink || universalLink || androidStoreUrl
|
|
97
|
+
: null;
|
|
98
|
+
return {
|
|
99
|
+
platform,
|
|
100
|
+
iosStoreUrl,
|
|
101
|
+
androidStoreUrl,
|
|
102
|
+
iosDeepLinkUrl: iosDeepLink,
|
|
103
|
+
androidDeepLinkUrl: androidDeepLink,
|
|
104
|
+
openAppUrl,
|
|
105
|
+
webLinkUrl: universalLink,
|
|
106
|
+
emailOpenAppUrl: universalLink || iosDeepLink || androidDeepLink,
|
|
107
|
+
emailIosDeepLinkUrl: iosDeepLink,
|
|
108
|
+
emailAndroidDeepLinkUrl: androidDeepLink,
|
|
109
|
+
emailIosStoreUrl: iosStoreUrl,
|
|
110
|
+
emailAndroidStoreUrl: androidStoreUrl,
|
|
111
|
+
qrConfirmationUrl: buildQrConfirmationUrl(input.confirmationUrl, userId),
|
|
112
|
+
};
|
|
113
|
+
};
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { type Dispatch, type SetStateAction
|
|
2
|
-
import type { FunnelContextValue } from '../components/FunnelContext';
|
|
3
|
-
import type { FunnelManifestExperiment } from '../config/funnel.manifest.types';
|
|
4
|
-
import type { FunnelStepMeta } from '../steps/types';
|
|
1
|
+
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
|
+
import type { FunnelContextValue } from '../components/FunnelContext.js';
|
|
3
|
+
import type { FunnelManifestExperiment } from '../config/funnel.manifest.types.js';
|
|
4
|
+
import type { FunnelStepMeta } from '../steps/types.js';
|
|
5
5
|
export type FunnelFlowControllerExperiment<StepId extends string> = FunnelManifestExperiment & {
|
|
6
6
|
stepId: StepId;
|
|
7
7
|
variants: readonly (FunnelManifestExperiment['variants'][number] & {
|
|
8
8
|
routeToStepId: StepId;
|
|
9
9
|
})[];
|
|
10
10
|
};
|
|
11
|
+
type StepComponentRegistry = Record<string, unknown>;
|
|
11
12
|
type UseFunnelFlowControllerInput<StepId extends string> = {
|
|
12
13
|
initialStepId?: StepId;
|
|
13
14
|
lockToInitialStep?: boolean;
|
|
14
15
|
defaultStepId: StepId;
|
|
15
16
|
stepSequence: readonly StepId[];
|
|
16
17
|
stepById: Record<string, FunnelStepMeta>;
|
|
17
|
-
stepComponentById:
|
|
18
|
+
stepComponentById: StepComponentRegistry;
|
|
18
19
|
funnelExperiments: readonly FunnelFlowControllerExperiment<StepId>[];
|
|
19
20
|
getPathForStep: (stepId: StepId) => string;
|
|
20
21
|
getStepIdFromPath: (pathname: string) => StepId | null;
|
|
@@ -26,16 +27,14 @@ type UseFunnelFlowControllerInput<StepId extends string> = {
|
|
|
26
27
|
isFunnelStepId: (value: string) => value is StepId;
|
|
27
28
|
resolveConfiguredNextStep: (stepId: StepId, context: {
|
|
28
29
|
attributes: Record<string, unknown>;
|
|
29
|
-
|
|
30
|
-
getAssignedVariantId: (experiment: FunnelManifestExperiment) => string | null;
|
|
30
|
+
resolveExperimentVariantKey: (experiment: FunnelManifestExperiment) => string | null;
|
|
31
31
|
}) => StepId | null;
|
|
32
32
|
resolveRuntimeInitialStepId: (input: {
|
|
33
33
|
requestedStepId?: string | null;
|
|
34
|
-
entryPointId?: string | null;
|
|
35
34
|
}) => StepId;
|
|
36
35
|
};
|
|
37
36
|
type UseFunnelFlowControllerResult<StepId extends string> = {
|
|
38
|
-
activeStepComponent:
|
|
37
|
+
activeStepComponent: unknown;
|
|
39
38
|
activeStepId: StepId;
|
|
40
39
|
activeStepMeta: FunnelStepMeta | undefined;
|
|
41
40
|
buttonText: string;
|
|
@@ -46,13 +45,23 @@ type UseFunnelFlowControllerResult<StepId extends string> = {
|
|
|
46
45
|
experimentAssignmentForEditor: (experiment: FunnelFlowControllerExperiment<StepId>) => string;
|
|
47
46
|
funnelExperiments: readonly FunnelFlowControllerExperiment<StepId>[];
|
|
48
47
|
handleContinue: () => void;
|
|
48
|
+
postHogReady: boolean;
|
|
49
|
+
renderSuspended: boolean;
|
|
49
50
|
renderedStepId: StepId;
|
|
50
51
|
renderedStepMeta: FunnelStepMeta | undefined;
|
|
51
52
|
runtimeMode: 'test' | 'live';
|
|
52
53
|
setEditorPanelExpanded: Dispatch<SetStateAction<boolean>>;
|
|
53
|
-
setEditorVariantSelection: (experiment: FunnelFlowControllerExperiment<StepId>,
|
|
54
|
+
setEditorVariantSelection: (experiment: FunnelFlowControllerExperiment<StepId>, variantKey: string) => void;
|
|
54
55
|
setRuntimeMode: (mode: 'test' | 'live') => void;
|
|
55
56
|
showShellContinue: boolean;
|
|
56
57
|
};
|
|
58
|
+
export declare function computeRenderSuspended(input: {
|
|
59
|
+
activeStepId: string;
|
|
60
|
+
postHogReady: boolean;
|
|
61
|
+
experiments: ReadonlyArray<{
|
|
62
|
+
stepId: string;
|
|
63
|
+
}>;
|
|
64
|
+
isPreviewRuntime?: boolean;
|
|
65
|
+
}): boolean;
|
|
57
66
|
export declare function useFunnelFlowController<StepId extends string>({ initialStepId, lockToInitialStep, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }: UseFunnelFlowControllerInput<StepId>): UseFunnelFlowControllerResult<StepId>;
|
|
58
67
|
export {};
|