@funnelsgrove/runtime 0.1.62 → 0.2.0
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/dist/components/FunnelEditorPanel.d.ts +2 -5
- package/dist/components/FunnelEditorPanel.js +8 -32
- package/dist/components/PaymentRuntimeBoundary.d.ts +7 -0
- package/dist/components/PaymentRuntimeBoundary.js +16 -0
- package/dist/components/RuntimeDevInfoBox.d.ts +2 -3
- package/dist/components/RuntimeDevInfoBox.js +7 -20
- package/dist/config/builder-preview.protocol.d.ts +0 -5
- package/dist/config/builder-preview.protocol.js +0 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/runtime/offer-set-runtime.d.ts +1 -0
- package/dist/runtime/offer-set-runtime.js +1 -1
- package/dist/runtime/preview-bridge.d.ts +1 -6
- package/dist/runtime/preview-bridge.js +2 -13
- package/dist/runtime/use-funnel-flow-controller.d.ts +0 -1
- package/dist/runtime/use-funnel-flow-controller.js +1 -3
- package/dist/services/api.service.js +3 -51
- package/dist/services/funnel-sdk.service.d.ts +4 -0
- package/dist/services/funnel-sdk.service.js +4 -0
- package/dist/services/runtime-mode.service.d.ts +3 -3
- package/dist/services/runtime-mode.service.js +42 -216
- package/package.json +1 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { RuntimeMode } from '../services/runtime-mode.service.js';
|
|
2
1
|
export type FunnelEditorPanelExperiment = {
|
|
3
2
|
experimentId: string;
|
|
4
3
|
variants: readonly {
|
|
@@ -10,11 +9,9 @@ type FunnelEditorPanelProps<Experiment extends FunnelEditorPanelExperiment> = {
|
|
|
10
9
|
editorPanelExpanded: boolean;
|
|
11
10
|
experimentAssignmentForEditor: (experiment: Experiment) => string;
|
|
12
11
|
funnelExperiments: readonly Experiment[];
|
|
13
|
-
onRuntimeModeChange: (mode: RuntimeMode) => void;
|
|
14
12
|
onSelectVariant: (experiment: Experiment, variantKey: string) => void;
|
|
15
13
|
onToggleExpanded: () => void;
|
|
16
|
-
runtimeMode: RuntimeMode;
|
|
17
14
|
};
|
|
18
|
-
export declare function FunnelEditorPanel<Experiment extends FunnelEditorPanelExperiment>({ editorModeEnabled, editorPanelExpanded, experimentAssignmentForEditor, funnelExperiments,
|
|
19
|
-
export declare const funnelEditorPanelStyles = "\n.funnel-editor-panel {\n position: fixed;\n top: 12px;\n right: 12px;\n z-index: 1200;\n border-radius: 12px;\n border: 1px solid rgba(33, 41, 82, 0.25);\n background: rgba(255, 255, 255, 0.98);\n color: #202538;\n box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);\n}\n\n.funnel-editor-panel.is-collapsed {\n width: auto;\n}\n\n.funnel-editor-panel.is-expanded {\n width: 184px;\n}\n\n.funnel-editor-panel-toggle {\n width: 100%;\n border: 0;\n background: transparent;\n color: inherit;\n font-size: 13px;\n font-weight: 700;\n line-height: 1;\n padding: 10px 12px;\n cursor: pointer;\n text-align: left;\n}\n\n.funnel-editor-panel-content {\n border-top: 1px solid rgba(33, 41, 82, 0.15);\n padding: 10px 12px 12px;\n}\n\n.funnel-editor-panel-label {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n color: #53608a;\n}\n\n.funnel-editor-panel-
|
|
15
|
+
export declare function FunnelEditorPanel<Experiment extends FunnelEditorPanelExperiment>({ editorModeEnabled, editorPanelExpanded, experimentAssignmentForEditor, funnelExperiments, onSelectVariant, onToggleExpanded, }: FunnelEditorPanelProps<Experiment>): import("react/jsx-runtime").JSX.Element | null;
|
|
16
|
+
export declare const funnelEditorPanelStyles = "\n.funnel-editor-panel {\n position: fixed;\n top: 12px;\n right: 12px;\n z-index: 1200;\n border-radius: 12px;\n border: 1px solid rgba(33, 41, 82, 0.25);\n background: rgba(255, 255, 255, 0.98);\n color: #202538;\n box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);\n}\n\n.funnel-editor-panel.is-collapsed {\n width: auto;\n}\n\n.funnel-editor-panel.is-expanded {\n width: 184px;\n}\n\n.funnel-editor-panel-toggle {\n width: 100%;\n border: 0;\n background: transparent;\n color: inherit;\n font-size: 13px;\n font-weight: 700;\n line-height: 1;\n padding: 10px 12px;\n cursor: pointer;\n text-align: left;\n}\n\n.funnel-editor-panel-content {\n border-top: 1px solid rgba(33, 41, 82, 0.15);\n padding: 10px 12px 12px;\n}\n\n.funnel-editor-panel-label {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n color: #53608a;\n}\n\n.funnel-editor-panel-tests {\n margin-top: 0;\n}\n\n.funnel-editor-panel-helper {\n margin: 0;\n font-size: 11px;\n color: #5a6387;\n}\n\n.funnel-editor-panel-test-list {\n display: grid;\n gap: 8px;\n}\n\n.funnel-editor-panel-test-item {\n display: grid;\n gap: 6px;\n font-size: 11px;\n color: #445073;\n}\n\n.funnel-editor-panel-test-item select {\n height: 28px;\n border: 1px solid rgba(63, 81, 181, 0.25);\n border-radius: 8px;\n background: #f5f7ff;\n color: #26315a;\n font-size: 12px;\n padding: 0 8px;\n}\n";
|
|
20
17
|
export {};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
export function FunnelEditorPanel({ editorModeEnabled, editorPanelExpanded, experimentAssignmentForEditor, funnelExperiments,
|
|
3
|
+
export function FunnelEditorPanel({ editorModeEnabled, editorPanelExpanded, experimentAssignmentForEditor, funnelExperiments, onSelectVariant, onToggleExpanded, }) {
|
|
4
4
|
if (!editorModeEnabled) {
|
|
5
5
|
return null;
|
|
6
6
|
}
|
|
7
|
-
return (_jsxs("aside", { className: `funnel-editor-panel ${editorPanelExpanded ? 'is-expanded' : 'is-collapsed'}`, children: [_jsx("button", { type: 'button', className: 'funnel-editor-panel-toggle', onClick: onToggleExpanded, children: editorPanelExpanded ? 'Hide editor' : 'Editor' }), editorPanelExpanded ? (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
return (_jsxs("aside", { className: `funnel-editor-panel ${editorPanelExpanded ? 'is-expanded' : 'is-collapsed'}`, children: [_jsx("button", { type: 'button', className: 'funnel-editor-panel-toggle', onClick: onToggleExpanded, children: editorPanelExpanded ? 'Hide editor' : 'Editor' }), editorPanelExpanded ? (_jsx("div", { className: 'funnel-editor-panel-content', children: _jsxs("div", { className: 'funnel-editor-panel-tests', children: [_jsx("p", { className: 'funnel-editor-panel-label', children: "A/B TESTS" }), funnelExperiments.length === 0 ? (_jsx("p", { className: 'funnel-editor-panel-helper', children: "No configured tests." })) : (_jsx("div", { className: 'funnel-editor-panel-test-list', children: funnelExperiments.map((experiment) => {
|
|
8
|
+
const assignedVariantKey = experimentAssignmentForEditor(experiment);
|
|
9
|
+
return (_jsxs("label", { className: 'funnel-editor-panel-test-item', children: [_jsx("span", { children: experiment.experimentId }), _jsx("select", { value: assignedVariantKey, onChange: (event) => {
|
|
10
|
+
onSelectVariant(experiment, event.target.value);
|
|
11
|
+
}, children: experiment.variants.map((variant) => (_jsx("option", { value: variant.variantKey, children: variant.variantKey }, variant.variantKey))) })] }, experiment.experimentId));
|
|
12
|
+
}) }))] }) })) : null] }));
|
|
13
13
|
}
|
|
14
14
|
export const funnelEditorPanelStyles = `
|
|
15
15
|
.funnel-editor-panel {
|
|
@@ -58,32 +58,8 @@ export const funnelEditorPanelStyles = `
|
|
|
58
58
|
color: #53608a;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
.funnel-editor-panel-options {
|
|
62
|
-
display: grid;
|
|
63
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
64
|
-
gap: 8px;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.funnel-editor-panel-options button {
|
|
68
|
-
border: 1px solid rgba(63, 81, 181, 0.3);
|
|
69
|
-
border-radius: 8px;
|
|
70
|
-
background: #eef1ff;
|
|
71
|
-
color: #3f51b5;
|
|
72
|
-
font-size: 12px;
|
|
73
|
-
font-weight: 700;
|
|
74
|
-
line-height: 1;
|
|
75
|
-
padding: 10px 0;
|
|
76
|
-
cursor: pointer;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.funnel-editor-panel-options button.is-active {
|
|
80
|
-
border-color: #3f51b5;
|
|
81
|
-
background: #3f51b5;
|
|
82
|
-
color: #ffffff;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
61
|
.funnel-editor-panel-tests {
|
|
86
|
-
margin-top:
|
|
62
|
+
margin-top: 0;
|
|
87
63
|
}
|
|
88
64
|
|
|
89
65
|
.funnel-editor-panel-helper {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useRuntimeEnvironmentReady } from '../services/runtime-mode.service.js';
|
|
4
|
+
const defaultFallback = (_jsx("section", { role: 'status', "aria-live": 'polite', "aria-busy": 'true', style: {
|
|
5
|
+
alignItems: 'center',
|
|
6
|
+
background: 'inherit',
|
|
7
|
+
color: 'inherit',
|
|
8
|
+
display: 'grid',
|
|
9
|
+
justifyItems: 'center',
|
|
10
|
+
minHeight: '100dvh',
|
|
11
|
+
width: '100%',
|
|
12
|
+
}, children: "Loading secure checkout" }));
|
|
13
|
+
export function PaymentRuntimeBoundary({ children, fallback = defaultFallback, }) {
|
|
14
|
+
const runtimeEnvironmentReady = useRuntimeEnvironmentReady();
|
|
15
|
+
return runtimeEnvironmentReady ? children : fallback;
|
|
16
|
+
}
|
|
@@ -9,7 +9,6 @@ export type RuntimeDevInfoValue = {
|
|
|
9
9
|
type RuntimeDevInfoBoxProps = {
|
|
10
10
|
runtimeMode: RuntimeMode;
|
|
11
11
|
user: Pick<FunnelUser, 'id' | 'email'>;
|
|
12
|
-
onSwitchToLiveMode: () => void;
|
|
13
12
|
configValues?: readonly RuntimeDevInfoValue[];
|
|
14
13
|
initialConfigOpen?: boolean;
|
|
15
14
|
initialExpanded?: boolean;
|
|
@@ -18,6 +17,6 @@ type RuntimeDevInfoBoxProps = {
|
|
|
18
17
|
title?: string;
|
|
19
18
|
};
|
|
20
19
|
export declare const copyRuntimeDevInfoValue: (value: string) => Promise<boolean>;
|
|
21
|
-
export declare function RuntimeDevInfoBox({ runtimeMode, user,
|
|
22
|
-
export declare const runtimeDevInfoBoxStyles = "\n.runtime-dev-info {\n position: fixed;\n right: 16px;\n bottom: 16px;\n z-index: 100000;\n width: min(392px, calc(100vw - 32px));\n color: #f8fafc;\n font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n pointer-events: none;\n}\n\n.runtime-dev-info * {\n box-sizing: border-box;\n}\n\n.runtime-dev-info-pill,\n.runtime-dev-info-panel {\n pointer-events: auto;\n}\n\n.runtime-dev-info.is-collapsed {\n width: auto;\n}\n\n.runtime-dev-info-pill {\n min-height: 56px;\n display: inline-flex;\n align-items: center;\n gap: 10px;\n border: 1px solid rgb(255 255 255 / 12%);\n border-radius: 28px;\n background: #11161d;\n color: inherit;\n padding: 0 20px;\n box-shadow: 0 18px 42px rgb(15 23 42 / 24%);\n cursor: pointer;\n}\n\n.runtime-dev-info-wordmark {\n font-size: 17px;\n font-weight: 800;\n letter-spacing: 0;\n}\n\n.runtime-dev-info-chevron {\n color: #cbd5e1;\n font-size: 18px;\n font-weight: 700;\n line-height: 1;\n}\n\n.runtime-dev-info-panel {\n margin-top: 10px;\n border: 1px solid rgb(255 255 255 / 12%);\n border-radius: 20px;\n background: #11161d;\n box-shadow: 0 22px 50px rgb(15 23 42 / 30%);\n overflow: hidden;\n}\n\n.runtime-dev-info-status-row,\n.runtime-dev-info-config-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.runtime-dev-info-status-row {\n padding: 18px 20px 14px;\n}\n\n.runtime-dev-info-title,\n.runtime-dev-info-config-head p
|
|
20
|
+
export declare function RuntimeDevInfoBox({ runtimeMode, user, configValues, initialConfigOpen, initialExpanded, paywallStateOptions, reloadOnReset, title, }: RuntimeDevInfoBoxProps): import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
export declare const runtimeDevInfoBoxStyles = "\n.runtime-dev-info {\n position: fixed;\n right: 16px;\n bottom: 16px;\n z-index: 100000;\n width: min(392px, calc(100vw - 32px));\n color: #f8fafc;\n font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n pointer-events: none;\n}\n\n.runtime-dev-info * {\n box-sizing: border-box;\n}\n\n.runtime-dev-info-pill,\n.runtime-dev-info-panel {\n pointer-events: auto;\n}\n\n.runtime-dev-info.is-collapsed {\n width: auto;\n}\n\n.runtime-dev-info-pill {\n min-height: 56px;\n display: inline-flex;\n align-items: center;\n gap: 10px;\n border: 1px solid rgb(255 255 255 / 12%);\n border-radius: 28px;\n background: #11161d;\n color: inherit;\n padding: 0 20px;\n box-shadow: 0 18px 42px rgb(15 23 42 / 24%);\n cursor: pointer;\n}\n\n.runtime-dev-info-wordmark {\n font-size: 17px;\n font-weight: 800;\n letter-spacing: 0;\n}\n\n.runtime-dev-info-chevron {\n color: #cbd5e1;\n font-size: 18px;\n font-weight: 700;\n line-height: 1;\n}\n\n.runtime-dev-info-panel {\n margin-top: 10px;\n border: 1px solid rgb(255 255 255 / 12%);\n border-radius: 20px;\n background: #11161d;\n box-shadow: 0 22px 50px rgb(15 23 42 / 30%);\n overflow: hidden;\n}\n\n.runtime-dev-info-status-row,\n.runtime-dev-info-config-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.runtime-dev-info-status-row {\n padding: 18px 20px 14px;\n}\n\n.runtime-dev-info-title,\n.runtime-dev-info-config-head p {\n margin: 0;\n}\n\n.runtime-dev-info-title {\n color: #e5e7eb;\n font-size: 16px;\n font-weight: 750;\n line-height: 1.2;\n}\n\n.runtime-dev-info-badge {\n border: 1px solid #3b82f6;\n border-radius: 999px;\n color: #60a5fa;\n font-size: 12px;\n font-weight: 750;\n line-height: 1;\n padding: 7px 10px;\n}\n\n.runtime-dev-info-identities {\n display: grid;\n gap: 10px;\n padding: 0 20px 18px;\n}\n\n.runtime-dev-info-identity-card {\n min-width: 0;\n border: 1px solid rgb(255 255 255 / 10%);\n border-radius: 10px;\n background: rgb(255 255 255 / 4%);\n padding: 10px;\n}\n\n.runtime-dev-info-identity-main {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.runtime-dev-info-identity-value {\n min-width: 0;\n flex: 1;\n color: #f8fafc;\n overflow-wrap: anywhere;\n white-space: normal;\n}\n\n.runtime-dev-info-identity-label,\n.runtime-dev-info-config-name {\n display: block;\n margin-top: 5px;\n color: #94a3b8;\n font-size: 12px;\n line-height: 1.25;\n}\n\n.runtime-dev-info-identity-value,\n.runtime-dev-info-config-value {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace;\n font-size: 12px;\n letter-spacing: 0;\n}\n\n.runtime-dev-info-config-value {\n color: #dbeafe;\n}\n\n.runtime-dev-info-identity-main button,\n.runtime-dev-info-actions button,\n.runtime-dev-info-config-head button {\n min-height: 30px;\n border: 1px solid rgb(255 255 255 / 14%);\n border-radius: 8px;\n background: rgb(255 255 255 / 6%);\n color: #e5e7eb;\n font: inherit;\n font-size: 12px;\n font-weight: 700;\n cursor: pointer;\n}\n\n.runtime-dev-info-identity-main button {\n flex: 0 0 auto;\n padding: 0 9px;\n}\n\n.runtime-dev-info-actions {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n border-top: 1px solid rgb(255 255 255 / 10%);\n padding: 14px 20px;\n}\n\n.runtime-dev-info-actions button {\n width: 100%;\n padding: 0 8px;\n}\n\n.runtime-dev-info-actions button:hover,\n.runtime-dev-info-identity-main button:hover,\n.runtime-dev-info-config-head button:hover {\n background: rgb(255 255 255 / 12%);\n}\n\n.runtime-dev-info-config {\n border-top: 1px solid rgb(255 255 255 / 10%);\n padding: 14px 20px;\n}\n\n.runtime-dev-info-config-head {\n margin-bottom: 10px;\n color: #e5e7eb;\n font-size: 13px;\n font-weight: 750;\n}\n\n.runtime-dev-info-config-head button {\n padding: 0 10px;\n}\n\n.runtime-dev-info-config-list {\n display: grid;\n gap: 5px;\n max-height: 220px;\n overflow: auto;\n}\n\n.runtime-dev-info-config-row {\n min-width: 0;\n border-bottom: 1px solid rgb(255 255 255 / 8%);\n padding: 7px 0;\n}\n\n.runtime-dev-info-config-value {\n display: block;\n min-width: 0;\n overflow-wrap: anywhere;\n white-space: normal;\n}\n\n.runtime-dev-info-config-name {\n font-size: 11px;\n}\n\n@media (max-width: 430px) {\n .runtime-dev-info {\n right: 10px;\n bottom: 10px;\n width: min(360px, calc(100vw - 20px));\n }\n\n .runtime-dev-info-actions {\n grid-template-columns: 1fr;\n }\n\n .runtime-dev-info-identity-main {\n align-items: flex-start;\n }\n}\n";
|
|
23
22
|
export {};
|
|
@@ -73,7 +73,7 @@ export const copyRuntimeDevInfoValue = async (value) => {
|
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
|
-
export function RuntimeDevInfoBox({ runtimeMode, user,
|
|
76
|
+
export function RuntimeDevInfoBox({ runtimeMode, user, configValues = [], initialConfigOpen = false, initialExpanded = false, paywallStateOptions, reloadOnReset = true, title = 'Funnel sandbox', }) {
|
|
77
77
|
const [expanded, setExpanded] = useState(initialExpanded);
|
|
78
78
|
const [configOpen, setConfigOpen] = useState(initialConfigOpen);
|
|
79
79
|
const [copiedKey, setCopiedKey] = useState(null);
|
|
@@ -114,12 +114,12 @@ export function RuntimeDevInfoBox({ runtimeMode, user, onSwitchToLiveMode, confi
|
|
|
114
114
|
value: normalizeDisplayValue(user.email),
|
|
115
115
|
},
|
|
116
116
|
];
|
|
117
|
-
return (_jsxs("aside", { className: `runtime-dev-info ${expanded ? 'is-expanded' : 'is-collapsed'}`, children: [_jsxs("button", { type: 'button', className: 'runtime-dev-info-pill', onClick: () => setExpanded((current) => !current), "aria-expanded": expanded, children: [_jsx("span", { className: 'runtime-dev-info-wordmark', children: "funnelgrove" }), _jsx("span", { className: 'runtime-dev-info-chevron', "aria-hidden": 'true', children: expanded ? 'v' : '>' })] }), expanded ? (_jsxs("div", { className: 'runtime-dev-info-panel', children: [_jsxs("div", { className: 'runtime-dev-info-status-row', children: [_jsx("p", { className: 'runtime-dev-info-title', children: title }), _jsx("span", { className: 'runtime-dev-info-badge', children: "Test" })] }), _jsx("div", { className: 'runtime-dev-info-identities', children: userRows.map((row) => (_jsxs("div", { className: 'runtime-dev-info-identity-card', children: [_jsxs("div", { className: 'runtime-dev-info-identity-main', children: [_jsx("code", { className: 'runtime-dev-info-identity-value', children: row.value }), _jsx("button", { type: 'button', onClick: () => copyValue(row.value, row.key), children: copiedKey === row.key ? 'Copied' : 'Copy' })] }), _jsx("span", { className: 'runtime-dev-info-identity-label', children: row.label })] }, row.key))) }), _jsxs("div", { className: 'runtime-dev-info-actions', children: [_jsx("button", { type: 'button', onClick: () => setConfigOpen((current) => !current), children: configOpen ? 'Hide config' : 'View config' }), _jsx("button", { type: 'button', onClick:
|
|
117
|
+
return (_jsxs("aside", { className: `runtime-dev-info ${expanded ? 'is-expanded' : 'is-collapsed'}`, children: [_jsxs("button", { type: 'button', className: 'runtime-dev-info-pill', onClick: () => setExpanded((current) => !current), "aria-expanded": expanded, children: [_jsx("span", { className: 'runtime-dev-info-wordmark', children: "funnelgrove" }), _jsx("span", { className: 'runtime-dev-info-chevron', "aria-hidden": 'true', children: expanded ? 'v' : '>' })] }), expanded ? (_jsxs("div", { className: 'runtime-dev-info-panel', children: [_jsxs("div", { className: 'runtime-dev-info-status-row', children: [_jsx("p", { className: 'runtime-dev-info-title', children: title }), _jsx("span", { className: 'runtime-dev-info-badge', children: "Test" })] }), _jsx("div", { className: 'runtime-dev-info-identities', children: userRows.map((row) => (_jsxs("div", { className: 'runtime-dev-info-identity-card', children: [_jsxs("div", { className: 'runtime-dev-info-identity-main', children: [_jsx("code", { className: 'runtime-dev-info-identity-value', children: row.value }), _jsx("button", { type: 'button', onClick: () => copyValue(row.value, row.key), children: copiedKey === row.key ? 'Copied' : 'Copy' })] }), _jsx("span", { className: 'runtime-dev-info-identity-label', children: row.label })] }, row.key))) }), _jsxs("div", { className: 'runtime-dev-info-actions', children: [_jsx("button", { type: 'button', onClick: () => setConfigOpen((current) => !current), children: configOpen ? 'Hide config' : 'View config' }), _jsx("button", { type: 'button', onClick: handleResetState, children: resetDone ? 'Timers reset' : 'Reset timers' })] }), configOpen ? (_jsxs("div", { className: 'runtime-dev-info-config', children: [_jsxs("div", { className: 'runtime-dev-info-config-head', children: [_jsx("p", { children: "Runtime config" }), _jsx("button", { type: 'button', onClick: () => {
|
|
118
118
|
const value = configItems
|
|
119
119
|
.map((item) => `${item.source || item.label}=${normalizeDisplayValue(item.value)}`)
|
|
120
120
|
.join('\n');
|
|
121
121
|
copyValue(value, 'config');
|
|
122
|
-
}, children: copiedKey === 'config' ? 'Copied' : 'Copy all' })] }), _jsx("div", { className: 'runtime-dev-info-config-list', children: configItems.map((item) => (_jsxs("div", { className: 'runtime-dev-info-config-row', children: [_jsx("code", { className: 'runtime-dev-info-config-value', children: normalizeDisplayValue(item.value) }), _jsx("span", { className: 'runtime-dev-info-config-name', children: item.source || item.label })] }, `${item.source || item.label}:${item.label}`))) })] })) : null
|
|
122
|
+
}, children: copiedKey === 'config' ? 'Copied' : 'Copy all' })] }), _jsx("div", { className: 'runtime-dev-info-config-list', children: configItems.map((item) => (_jsxs("div", { className: 'runtime-dev-info-config-row', children: [_jsx("code", { className: 'runtime-dev-info-config-value', children: normalizeDisplayValue(item.value) }), _jsx("span", { className: 'runtime-dev-info-config-name', children: item.source || item.label })] }, `${item.source || item.label}:${item.label}`))) })] })) : null] })) : null] }));
|
|
123
123
|
}
|
|
124
124
|
export const runtimeDevInfoBoxStyles = `
|
|
125
125
|
.runtime-dev-info {
|
|
@@ -195,8 +195,7 @@ export const runtimeDevInfoBoxStyles = `
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
.runtime-dev-info-title,
|
|
198
|
-
.runtime-dev-info-config-head p
|
|
199
|
-
.runtime-dev-info-help p {
|
|
198
|
+
.runtime-dev-info-config-head p {
|
|
200
199
|
margin: 0;
|
|
201
200
|
}
|
|
202
201
|
|
|
@@ -255,15 +254,13 @@ export const runtimeDevInfoBoxStyles = `
|
|
|
255
254
|
}
|
|
256
255
|
|
|
257
256
|
.runtime-dev-info-identity-value,
|
|
258
|
-
.runtime-dev-info-config-value
|
|
259
|
-
.runtime-dev-info-help code {
|
|
257
|
+
.runtime-dev-info-config-value {
|
|
260
258
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace;
|
|
261
259
|
font-size: 12px;
|
|
262
260
|
letter-spacing: 0;
|
|
263
261
|
}
|
|
264
262
|
|
|
265
|
-
.runtime-dev-info-config-value
|
|
266
|
-
.runtime-dev-info-help code {
|
|
263
|
+
.runtime-dev-info-config-value {
|
|
267
264
|
color: #dbeafe;
|
|
268
265
|
}
|
|
269
266
|
|
|
@@ -288,7 +285,7 @@ export const runtimeDevInfoBoxStyles = `
|
|
|
288
285
|
|
|
289
286
|
.runtime-dev-info-actions {
|
|
290
287
|
display: grid;
|
|
291
|
-
grid-template-columns: repeat(
|
|
288
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
292
289
|
gap: 8px;
|
|
293
290
|
border-top: 1px solid rgb(255 255 255 / 10%);
|
|
294
291
|
padding: 14px 20px;
|
|
@@ -345,16 +342,6 @@ export const runtimeDevInfoBoxStyles = `
|
|
|
345
342
|
font-size: 11px;
|
|
346
343
|
}
|
|
347
344
|
|
|
348
|
-
.runtime-dev-info-help {
|
|
349
|
-
display: grid;
|
|
350
|
-
gap: 6px;
|
|
351
|
-
border-top: 1px solid rgb(255 255 255 / 10%);
|
|
352
|
-
padding: 14px 20px 18px;
|
|
353
|
-
color: #94a3b8;
|
|
354
|
-
font-size: 12px;
|
|
355
|
-
line-height: 1.45;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
345
|
@media (max-width: 430px) {
|
|
359
346
|
.runtime-dev-info {
|
|
360
347
|
right: 10px;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export declare const BUILDER_PREVIEW_READY = "builder.preview.ready";
|
|
2
2
|
export declare const BUILDER_PREVIEW_ACTIVE_STEP_CHANGED = "builder.preview.activeStepChanged";
|
|
3
3
|
export declare const BUILDER_PREVIEW_GO_TO_STEP = "builder.preview.goToStep";
|
|
4
|
-
export declare const BUILDER_PREVIEW_RUNTIME_MODE_CHANGED = "builder.preview.runtimeModeChanged";
|
|
5
4
|
export declare const BUILDER_PREVIEW_DEFINITION_PATCH = "builder.preview.definitionPatch";
|
|
6
5
|
export declare const BUILDER_PREVIEW_VARIABLE_VALUES_CHANGED = "builder.preview.variableValuesChanged";
|
|
7
6
|
export declare const BUILDER_PREVIEW_PAYWALL_PLANS_CHANGED = "builder.preview.paywallPlansChanged";
|
|
@@ -16,10 +15,6 @@ export type BuilderPreviewActiveStepChangedMessage = {
|
|
|
16
15
|
type: typeof BUILDER_PREVIEW_ACTIVE_STEP_CHANGED;
|
|
17
16
|
stepId: string;
|
|
18
17
|
};
|
|
19
|
-
export type BuilderPreviewRuntimeModeChangedMessage = {
|
|
20
|
-
type: typeof BUILDER_PREVIEW_RUNTIME_MODE_CHANGED;
|
|
21
|
-
mode: 'test' | 'live';
|
|
22
|
-
};
|
|
23
18
|
export type BuilderPreviewGoToStepMessage = {
|
|
24
19
|
type: typeof BUILDER_PREVIEW_GO_TO_STEP;
|
|
25
20
|
stepId: string;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export const BUILDER_PREVIEW_READY = 'builder.preview.ready';
|
|
2
2
|
export const BUILDER_PREVIEW_ACTIVE_STEP_CHANGED = 'builder.preview.activeStepChanged';
|
|
3
3
|
export const BUILDER_PREVIEW_GO_TO_STEP = 'builder.preview.goToStep';
|
|
4
|
-
export const BUILDER_PREVIEW_RUNTIME_MODE_CHANGED = 'builder.preview.runtimeModeChanged';
|
|
5
4
|
export const BUILDER_PREVIEW_DEFINITION_PATCH = 'builder.preview.definitionPatch';
|
|
6
5
|
export const BUILDER_PREVIEW_VARIABLE_VALUES_CHANGED = 'builder.preview.variableValuesChanged';
|
|
7
6
|
export const BUILDER_PREVIEW_PAYWALL_PLANS_CHANGED = 'builder.preview.paywallPlansChanged';
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export * from './sdk/userAnswers.js';
|
|
|
41
41
|
export * from './components/FunnelContext.js';
|
|
42
42
|
export * from './components/FunnelEditorPanel.js';
|
|
43
43
|
export * from './components/ManageSubscriptionScreen.js';
|
|
44
|
+
export * from './components/PaymentRuntimeBoundary.js';
|
|
44
45
|
export * from './components/RuntimeDevInfoBox.js';
|
|
45
46
|
export * from './components/SubscriptionHandoffScreen.js';
|
|
46
47
|
export * from './components/shared/PrimaryButton.js';
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,7 @@ export * from './sdk/userAnswers.js';
|
|
|
44
44
|
export * from './components/FunnelContext.js';
|
|
45
45
|
export * from './components/FunnelEditorPanel.js';
|
|
46
46
|
export * from './components/ManageSubscriptionScreen.js';
|
|
47
|
+
export * from './components/PaymentRuntimeBoundary.js';
|
|
47
48
|
export * from './components/RuntimeDevInfoBox.js';
|
|
48
49
|
export * from './components/SubscriptionHandoffScreen.js';
|
|
49
50
|
export * from './components/shared/PrimaryButton.js';
|
|
@@ -179,7 +179,7 @@ export const applyGeneratedOfferSetToPlanCatalog = (baseCatalog, mode, offerSet)
|
|
|
179
179
|
if (!key) {
|
|
180
180
|
continue;
|
|
181
181
|
}
|
|
182
|
-
nextCatalog[key] = Object.assign(Object.assign({}, ((_c = nextCatalog[key]) !== null && _c !== void 0 ? _c : {})), buildGeneratedPlanOverrides(item, mode, getOfferSetItemKind(item) === 'plan' && key === defaultPlanKey));
|
|
182
|
+
nextCatalog[key] = Object.assign(Object.assign(Object.assign({}, ((_c = nextCatalog[key]) !== null && _c !== void 0 ? _c : {})), { offerSetKey: offerSet.key }), buildGeneratedPlanOverrides(item, mode, getOfferSetItemKind(item) === 'plan' && key === defaultPlanKey));
|
|
183
183
|
}
|
|
184
184
|
return nextCatalog;
|
|
185
185
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type BuilderPreviewDefinitionPatch, type BuilderPreviewPaywallPlan } from '../config/builder-preview.protocol.js';
|
|
2
|
-
import type { RuntimeMode } from '../services/runtime-mode.service.js';
|
|
3
2
|
import type { FunnelStepId } from './funnel-runtime.js';
|
|
4
3
|
export type PreviewQuickEditPatch = {
|
|
5
4
|
kind: 'tagText';
|
|
@@ -34,9 +33,6 @@ type PreviewBridgeMessage = {
|
|
|
34
33
|
} | {
|
|
35
34
|
kind: 'quickEditPatch';
|
|
36
35
|
patch: PreviewQuickEditPatch;
|
|
37
|
-
} | {
|
|
38
|
-
kind: 'runtimeModeChanged';
|
|
39
|
-
mode: RuntimeMode;
|
|
40
36
|
} | {
|
|
41
37
|
kind: 'paywallPlansChanged';
|
|
42
38
|
stepId: string;
|
|
@@ -62,8 +58,7 @@ export type PreviewBridgeOptions = {
|
|
|
62
58
|
activeStepId: FunnelStepId;
|
|
63
59
|
onGoToStep: (stepId: FunnelStepId) => void;
|
|
64
60
|
resolveRenderableStepId: (stepId: string) => FunnelStepId | null;
|
|
65
|
-
setRuntimeMode: (mode: RuntimeMode) => void;
|
|
66
61
|
shouldLockToInitialStep: boolean;
|
|
67
62
|
};
|
|
68
|
-
export declare function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableStepId,
|
|
63
|
+
export declare function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableStepId, shouldLockToInitialStep, }: PreviewBridgeOptions): void;
|
|
69
64
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
-
import { BUILDER_PREVIEW_ACTIVE_STEP_CHANGED, BUILDER_PREVIEW_DEFINITION_PATCH, BUILDER_PREVIEW_EDIT_MODE_CHANGED, BUILDER_PREVIEW_GO_TO_STEP, BUILDER_PREVIEW_PAYWALL_PLANS_CHANGED, BUILDER_PREVIEW_READY,
|
|
3
|
+
import { BUILDER_PREVIEW_ACTIVE_STEP_CHANGED, BUILDER_PREVIEW_DEFINITION_PATCH, BUILDER_PREVIEW_EDIT_MODE_CHANGED, BUILDER_PREVIEW_GO_TO_STEP, BUILDER_PREVIEW_PAYWALL_PLANS_CHANGED, BUILDER_PREVIEW_READY, BUILDER_PREVIEW_TEXT_EDITED, BUILDER_PREVIEW_THEME_CHANGED, BUILDER_PREVIEW_VARIABLE_VALUES_CHANGED, } from '../config/builder-preview.protocol.js';
|
|
4
4
|
import { isPreviewFrameRuntime } from '../services/preview-frame.service.js';
|
|
5
5
|
import { logger } from '../services/logger.js';
|
|
6
6
|
import { applyPreviewDefinitionPatch, applyPreviewPaywallPlansPatch } from './preview-definition-overrides.js';
|
|
@@ -130,12 +130,6 @@ export const parsePreviewBridgeMessage = (value) => {
|
|
|
130
130
|
const patch = parsePreviewDefinitionPatch(value.patch);
|
|
131
131
|
return patch ? { kind: 'definitionPatch', patch } : null;
|
|
132
132
|
}
|
|
133
|
-
if (messageType === BUILDER_PREVIEW_RUNTIME_MODE_CHANGED) {
|
|
134
|
-
return {
|
|
135
|
-
kind: 'runtimeModeChanged',
|
|
136
|
-
mode: value.mode === 'live' ? 'live' : 'test',
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
133
|
if (messageType === BUILDER_PREVIEW_PAYWALL_PLANS_CHANGED) {
|
|
140
134
|
const stepId = typeof value.stepId === 'string' ? value.stepId.trim() : '';
|
|
141
135
|
const plans = Array.isArray(value.plans)
|
|
@@ -422,7 +416,7 @@ export function usePreviewVariableValues(stepId, values) {
|
|
|
422
416
|
emitPreviewVariableValues(stepId, values);
|
|
423
417
|
}, [serializedValues, stepId, values]);
|
|
424
418
|
}
|
|
425
|
-
export function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableStepId,
|
|
419
|
+
export function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableStepId, shouldLockToInitialStep, }) {
|
|
426
420
|
const previewReadySentRef = useRef(false);
|
|
427
421
|
const activeStepIdRef = useRef(activeStepId);
|
|
428
422
|
useEffect(() => {
|
|
@@ -470,10 +464,6 @@ export function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableSt
|
|
|
470
464
|
setPreviewInlineEditMode(action.enabled, () => activeStepIdRef.current);
|
|
471
465
|
return;
|
|
472
466
|
}
|
|
473
|
-
if (action.kind === 'runtimeModeChanged') {
|
|
474
|
-
setRuntimeMode(action.mode);
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
467
|
if (shouldLockToInitialStep) {
|
|
478
468
|
return;
|
|
479
469
|
}
|
|
@@ -494,7 +484,6 @@ export function usePreviewBridge({ activeStepId, onGoToStep, resolveRenderableSt
|
|
|
494
484
|
activeStepId,
|
|
495
485
|
onGoToStep,
|
|
496
486
|
resolveRenderableStepId,
|
|
497
|
-
setRuntimeMode,
|
|
498
487
|
shouldLockToInitialStep,
|
|
499
488
|
]);
|
|
500
489
|
}
|
|
@@ -154,7 +154,6 @@ type UseFunnelFlowControllerResult<StepId extends string> = {
|
|
|
154
154
|
runtimeMode: 'test' | 'live';
|
|
155
155
|
setEditorPanelExpanded: Dispatch<SetStateAction<boolean>>;
|
|
156
156
|
setEditorVariantSelection: (experiment: FunnelFlowControllerExperiment<StepId>, variantKey: string) => void;
|
|
157
|
-
setRuntimeMode: (mode: 'test' | 'live') => void;
|
|
158
157
|
showShellContinue: boolean;
|
|
159
158
|
};
|
|
160
159
|
export declare function resolveFunnelEventAttribution(document: unknown, provisionalAttribution: FunnelUserAttribution | null): FunnelUserAttribution | null;
|
|
@@ -271,7 +271,7 @@ export function resolveRenderedExperimentStepId(input) {
|
|
|
271
271
|
}
|
|
272
272
|
export function useFunnelFlowController({ api = apiService, analytics, stepContractVersion, initialStepId, initialAttributes, lockToInitialStep = false, defaultStepId, stepSequence, stepById, stepComponentById, funnelExperiments, getPathForStep, getStepIdFromPath, getSequentialNextStepId, getChoiceTargetsForStep, isFunnelStepId, resolveConfiguredNextStep, resolveRuntimeInitialStepId, }) {
|
|
273
273
|
var _a, _b;
|
|
274
|
-
const [runtimeMode
|
|
274
|
+
const [runtimeMode] = useRuntimeMode();
|
|
275
275
|
const [editorModeEnabled, setEditorModeEnabled] = useState(() => isEditorEnabled());
|
|
276
276
|
const [editorPanelExpanded, setEditorPanelExpanded] = useState(false);
|
|
277
277
|
const [postHogReady, setPostHogReady] = useState(() => isPostHogReady());
|
|
@@ -1267,7 +1267,6 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
1267
1267
|
}
|
|
1268
1268
|
},
|
|
1269
1269
|
resolveRenderableStepId,
|
|
1270
|
-
setRuntimeMode,
|
|
1271
1270
|
shouldLockToInitialStep,
|
|
1272
1271
|
});
|
|
1273
1272
|
const setEditorVariantSelection = useCallback((experiment, variantKey) => {
|
|
@@ -1374,7 +1373,6 @@ export function useFunnelFlowController({ api = apiService, analytics, stepContr
|
|
|
1374
1373
|
runtimeMode,
|
|
1375
1374
|
setEditorPanelExpanded,
|
|
1376
1375
|
setEditorVariantSelection,
|
|
1377
|
-
setRuntimeMode,
|
|
1378
1376
|
showShellContinue: !hideActionBar && !isAutoAdvanceStep && !choiceTargets,
|
|
1379
1377
|
};
|
|
1380
1378
|
}
|
|
@@ -2,60 +2,12 @@ import { buildSdkHeaders, FUNNEL_ID, FUNNEL_VERSION_ID, getFunnelSdkPublishableK
|
|
|
2
2
|
import { funnelSdkService } from './funnel-sdk.service.js';
|
|
3
3
|
import { mergeFunnelUserAttribution, } from '../runtime/funnel-attribution.js';
|
|
4
4
|
import { applyUrlUserAttributesToUser, hasUrlUserProfileAttributes, resolveUrlUserAttributes, } from '../runtime/url-user-attributes.js';
|
|
5
|
+
import { getRuntimeMode } from './runtime-mode.service.js';
|
|
5
6
|
const DEFAULT_USER_ID_STORAGE_KEY = 'funnel:user-id';
|
|
6
7
|
const DEFAULT_BOOTSTRAP_IDEMPOTENCY_KEY_STORAGE_KEY = 'funnel:bootstrap-idempotency-key';
|
|
7
|
-
const DEFAULT_FUNNEL_DOMAIN_SIGNAL = 'funnelsgrove';
|
|
8
8
|
const canUseDom = () => {
|
|
9
9
|
return typeof window !== 'undefined';
|
|
10
10
|
};
|
|
11
|
-
const normalizeRuntimeEnvironment = (value) => {
|
|
12
|
-
if (typeof value !== 'string') {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
const normalized = value.trim().toLowerCase();
|
|
16
|
-
if (normalized === 'test') {
|
|
17
|
-
return 'test';
|
|
18
|
-
}
|
|
19
|
-
if (normalized === 'live' || normalized === 'prod' || normalized === 'production') {
|
|
20
|
-
return 'live';
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
};
|
|
24
|
-
const readCookieValue = (name) => {
|
|
25
|
-
var _a, _b;
|
|
26
|
-
if (!canUseDom() || typeof document === 'undefined') {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
return (((_b = (_a = document.cookie) === null || _a === void 0 ? void 0 : _a.split(';').map((part) => part.trim()).find((part) => part.startsWith(`${name}=`))) === null || _b === void 0 ? void 0 : _b.slice(name.length + 1)) || null);
|
|
30
|
-
};
|
|
31
|
-
const resolveRuntimeAnalyticsEnvironment = () => {
|
|
32
|
-
var _a, _b, _c, _d;
|
|
33
|
-
if (!canUseDom()) {
|
|
34
|
-
return 'live';
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const queryMode = normalizeRuntimeEnvironment(new URLSearchParams(((_a = window.location) === null || _a === void 0 ? void 0 : _a.search) || '').get('mode'));
|
|
38
|
-
if (queryMode) {
|
|
39
|
-
return queryMode;
|
|
40
|
-
}
|
|
41
|
-
const cookieMode = normalizeRuntimeEnvironment(readCookieValue('funnel-mode'));
|
|
42
|
-
if (cookieMode) {
|
|
43
|
-
return cookieMode;
|
|
44
|
-
}
|
|
45
|
-
const hostname = ((_c = (_b = window.location) === null || _b === void 0 ? void 0 : _b.hostname) === null || _c === void 0 ? void 0 : _c.trim().toLowerCase()) || '';
|
|
46
|
-
if (hostname.includes(DEFAULT_FUNNEL_DOMAIN_SIGNAL)) {
|
|
47
|
-
return 'test';
|
|
48
|
-
}
|
|
49
|
-
const storageMode = normalizeRuntimeEnvironment((_d = window.localStorage) === null || _d === void 0 ? void 0 : _d.getItem('funnel-mode'));
|
|
50
|
-
if (storageMode) {
|
|
51
|
-
return storageMode;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
catch (_e) {
|
|
55
|
-
return 'live';
|
|
56
|
-
}
|
|
57
|
-
return 'live';
|
|
58
|
-
};
|
|
59
11
|
const isRecord = (value) => {
|
|
60
12
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
61
13
|
};
|
|
@@ -329,7 +281,7 @@ class ApiService {
|
|
|
329
281
|
}
|
|
330
282
|
const metadata = {
|
|
331
283
|
source: 'funnel-session',
|
|
332
|
-
environment:
|
|
284
|
+
environment: getRuntimeMode(),
|
|
333
285
|
};
|
|
334
286
|
const fingerprint = await digestBootstrapRequest({
|
|
335
287
|
funnelVersionId: asString(FUNNEL_VERSION_ID),
|
|
@@ -381,7 +333,7 @@ class ApiService {
|
|
|
381
333
|
},
|
|
382
334
|
metadata: {
|
|
383
335
|
source: 'funnel',
|
|
384
|
-
environment:
|
|
336
|
+
environment: getRuntimeMode(),
|
|
385
337
|
},
|
|
386
338
|
attribution: options === null || options === void 0 ? void 0 : options.attribution,
|
|
387
339
|
});
|
|
@@ -72,6 +72,7 @@ export type FunnelSdkUploadPhotoInput = {
|
|
|
72
72
|
};
|
|
73
73
|
export type FunnelSdkCreateHostedCheckoutSessionInput = {
|
|
74
74
|
planId: string;
|
|
75
|
+
offerSetKey?: string | null;
|
|
75
76
|
amountCents: number;
|
|
76
77
|
successUrl: string;
|
|
77
78
|
cancelUrl: string;
|
|
@@ -94,6 +95,7 @@ export type FunnelSdkCreateHostedCheckoutSessionResponse = {
|
|
|
94
95
|
};
|
|
95
96
|
export type FunnelSdkCreateSubscriptionCheckoutInput = {
|
|
96
97
|
planId: string;
|
|
98
|
+
offerSetKey?: string | null;
|
|
97
99
|
amountCents: number;
|
|
98
100
|
returnUrl: string;
|
|
99
101
|
analyticsMetadata?: Record<string, unknown> | null;
|
|
@@ -122,6 +124,7 @@ export type FunnelSdkCreateSubscriptionCheckoutResponse = {
|
|
|
122
124
|
};
|
|
123
125
|
export type FunnelSdkCreateOneTimeCheckoutInput = {
|
|
124
126
|
planId: string;
|
|
127
|
+
offerSetKey?: string | null;
|
|
125
128
|
amountCents: number;
|
|
126
129
|
returnUrl: string;
|
|
127
130
|
analyticsMetadata?: Record<string, unknown> | null;
|
|
@@ -143,6 +146,7 @@ export type FunnelSdkCreateOneTimeCheckoutResponse = {
|
|
|
143
146
|
export type FunnelSdkUpdateSubscriptionCheckoutPlanInput = {
|
|
144
147
|
checkoutSessionId: string;
|
|
145
148
|
planId: string;
|
|
149
|
+
offerSetKey?: string | null;
|
|
146
150
|
amountCents: number;
|
|
147
151
|
analyticsMetadata?: Record<string, unknown> | null;
|
|
148
152
|
billingInterval?: string | null;
|
|
@@ -201,6 +201,7 @@ class FunnelSdkService {
|
|
|
201
201
|
return this.postJson('/sdk/public/payments/checkout-session', {
|
|
202
202
|
runtimeConfig: input.runtimeConfig,
|
|
203
203
|
body: {
|
|
204
|
+
offerSetKey: input.offerSetKey,
|
|
204
205
|
planId: input.planId,
|
|
205
206
|
providerPlanId: input.providerPlanId,
|
|
206
207
|
title: input.title,
|
|
@@ -223,6 +224,7 @@ class FunnelSdkService {
|
|
|
223
224
|
return this.postJson('/sdk/public/payments/subscriptions', {
|
|
224
225
|
runtimeConfig: input.runtimeConfig,
|
|
225
226
|
body: {
|
|
227
|
+
offerSetKey: input.offerSetKey,
|
|
226
228
|
planId: input.planId,
|
|
227
229
|
providerPlanId: input.providerPlanId,
|
|
228
230
|
title: input.title,
|
|
@@ -249,6 +251,7 @@ class FunnelSdkService {
|
|
|
249
251
|
return this.postJson('/sdk/public/payments/one-time-checkout', {
|
|
250
252
|
runtimeConfig: input.runtimeConfig,
|
|
251
253
|
body: {
|
|
254
|
+
offerSetKey: input.offerSetKey,
|
|
252
255
|
planId: input.planId,
|
|
253
256
|
providerPlanId: input.providerPlanId,
|
|
254
257
|
title: input.title,
|
|
@@ -269,6 +272,7 @@ class FunnelSdkService {
|
|
|
269
272
|
runtimeConfig: input.runtimeConfig,
|
|
270
273
|
body: {
|
|
271
274
|
checkoutSessionId: input.checkoutSessionId,
|
|
275
|
+
offerSetKey: input.offerSetKey,
|
|
272
276
|
planId: input.planId,
|
|
273
277
|
providerPlanId: input.providerPlanId,
|
|
274
278
|
title: input.title,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type RuntimeMode = 'test' | 'live';
|
|
2
2
|
export type FunnelMode = 'test' | 'production';
|
|
3
|
+
export declare const isSystemPreviewHostname: (value: string) => boolean;
|
|
3
4
|
export declare const isEditorEnabled: () => boolean;
|
|
4
5
|
export declare const getFunnelMode: () => FunnelMode;
|
|
5
6
|
export declare const getRuntimeMode: () => RuntimeMode;
|
|
6
|
-
export declare const
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const useRuntimeMode: () => [RuntimeMode, (mode: RuntimeMode) => void];
|
|
7
|
+
export declare const useRuntimeMode: () => readonly [RuntimeMode];
|
|
8
|
+
export declare const useRuntimeEnvironmentReady: () => boolean;
|
|
@@ -1,48 +1,40 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
const
|
|
4
|
-
const FUNNEL_MODE_STORAGE_KEY = 'funnel-mode';
|
|
5
|
-
const FUNNEL_MODE_COOKIE_NAME = 'funnel-mode';
|
|
6
|
-
const MODE_CHANGE_EVENT = 'funnel:editor-mode-changed';
|
|
7
|
-
const DEFAULT_FUNNEL_DOMAIN_SIGNAL = 'funnelsgrove';
|
|
8
|
-
const FUNNEL_MODE_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
|
+
const SYSTEM_PREVIEW_DOMAINS = ['funnelsgrove.org', 'funnelsgrove.com'];
|
|
9
4
|
const canUseDom = () => {
|
|
10
5
|
return typeof window !== 'undefined';
|
|
11
6
|
};
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
const normalized = value.trim().toLowerCase();
|
|
17
|
-
if (normalized === 'test') {
|
|
18
|
-
return 'test';
|
|
19
|
-
}
|
|
20
|
-
if (normalized === 'prod' || normalized === 'production' || normalized === 'live') {
|
|
21
|
-
return 'production';
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
};
|
|
25
|
-
const toRuntimeMode = (value) => {
|
|
26
|
-
return toFunnelMode(value) === 'production' ? 'live' : 'test';
|
|
7
|
+
const normalizeHostname = (value) => {
|
|
8
|
+
return value.trim().toLowerCase().replace(/^\[|\]$/g, '').replace(/\.+$/, '');
|
|
27
9
|
};
|
|
28
|
-
const
|
|
29
|
-
return
|
|
10
|
+
const isExactOrSubdomain = (hostname, domain) => {
|
|
11
|
+
return hostname === domain || hostname.endsWith(`.${domain}`);
|
|
30
12
|
};
|
|
31
|
-
const
|
|
32
|
-
|
|
13
|
+
const isIpv4Loopback = (hostname) => {
|
|
14
|
+
const match = /^127((?:\.\d{1,3}){3})$/.exec(hostname);
|
|
15
|
+
return match
|
|
16
|
+
? match[1].slice(1).split('.').every((octet) => Number(octet) <= 255)
|
|
17
|
+
: false;
|
|
33
18
|
};
|
|
34
|
-
const
|
|
35
|
-
|
|
19
|
+
const isMappedIpv4Loopback = (hostname) => {
|
|
20
|
+
const mappedIpv4 = /^::ffff:([0-9a-f]{1,4}):[0-9a-f]{1,4}$/.exec(hostname);
|
|
21
|
+
return Boolean(mappedIpv4
|
|
22
|
+
&& Number.parseInt(mappedIpv4[1], 16) >= 0x7f00
|
|
23
|
+
&& Number.parseInt(mappedIpv4[1], 16) <= 0x7fff);
|
|
36
24
|
};
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
const hostname = normalizeHostname(window.location.hostname || '');
|
|
25
|
+
export const isSystemPreviewHostname = (value) => {
|
|
26
|
+
const hostname = normalizeHostname(value);
|
|
42
27
|
if (!hostname) {
|
|
43
28
|
return false;
|
|
44
29
|
}
|
|
45
|
-
|
|
30
|
+
if (hostname === 'localhost' ||
|
|
31
|
+
hostname.endsWith('.localhost') ||
|
|
32
|
+
isIpv4Loopback(hostname) ||
|
|
33
|
+
hostname === '::1' ||
|
|
34
|
+
isMappedIpv4Loopback(hostname)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return SYSTEM_PREVIEW_DOMAINS.some((domain) => isExactOrSubdomain(hostname, domain));
|
|
46
38
|
};
|
|
47
39
|
export const isEditorEnabled = () => {
|
|
48
40
|
if (!canUseDom()) {
|
|
@@ -55,197 +47,31 @@ export const isEditorEnabled = () => {
|
|
|
55
47
|
return false;
|
|
56
48
|
}
|
|
57
49
|
};
|
|
58
|
-
const readStoredEditorMode = () => {
|
|
59
|
-
if (!canUseDom()) {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const storedValue = window.localStorage.getItem(EDITOR_MODE_STORAGE_KEY);
|
|
64
|
-
if (!storedValue) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
return toRuntimeMode(storedValue);
|
|
68
|
-
}
|
|
69
|
-
catch (_a) {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
const readCookieValue = (name) => {
|
|
74
|
-
if (typeof document === 'undefined') {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
const target = `${encodeURIComponent(name)}=`;
|
|
79
|
-
const entry = document.cookie
|
|
80
|
-
.split(';')
|
|
81
|
-
.map((item) => item.trim())
|
|
82
|
-
.find((item) => item.startsWith(target));
|
|
83
|
-
return entry ? decodeURIComponent(entry.slice(target.length)) : null;
|
|
84
|
-
}
|
|
85
|
-
catch (_a) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
const writeFunnelModeCookie = (mode) => {
|
|
90
|
-
if (typeof document === 'undefined') {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
document.cookie = [
|
|
95
|
-
`${encodeURIComponent(FUNNEL_MODE_COOKIE_NAME)}=${encodeURIComponent(mode)}`,
|
|
96
|
-
'Path=/',
|
|
97
|
-
`Max-Age=${FUNNEL_MODE_COOKIE_MAX_AGE_SECONDS}`,
|
|
98
|
-
'SameSite=Lax',
|
|
99
|
-
].join('; ');
|
|
100
|
-
}
|
|
101
|
-
catch (_a) {
|
|
102
|
-
// ignore cookie failures
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
const readFunnelModeCookie = () => {
|
|
106
|
-
return toFunnelMode(readCookieValue(FUNNEL_MODE_COOKIE_NAME));
|
|
107
|
-
};
|
|
108
|
-
const readFunnelModeStorage = () => {
|
|
109
|
-
if (!canUseDom()) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
return toFunnelMode(window.localStorage.getItem(FUNNEL_MODE_STORAGE_KEY));
|
|
114
|
-
}
|
|
115
|
-
catch (_a) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
const persistFunnelMode = (mode, options = {}) => {
|
|
120
|
-
if (!canUseDom()) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
window.localStorage.setItem(FUNNEL_MODE_STORAGE_KEY, mode);
|
|
125
|
-
}
|
|
126
|
-
catch (_a) {
|
|
127
|
-
// ignore storage failures
|
|
128
|
-
}
|
|
129
|
-
writeFunnelModeCookie(mode);
|
|
130
|
-
if (options.emit === false) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
emitModeChange(toRuntimeModeFromFunnelMode(mode));
|
|
134
|
-
};
|
|
135
|
-
const readQueryFunnelMode = () => {
|
|
136
|
-
if (!canUseDom()) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
try {
|
|
140
|
-
return toFunnelMode(new URLSearchParams(window.location.search).get('mode'));
|
|
141
|
-
}
|
|
142
|
-
catch (_a) {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
const emitModeChange = (mode) => {
|
|
147
|
-
if (!canUseDom()) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
window.dispatchEvent(new CustomEvent(MODE_CHANGE_EVENT, {
|
|
151
|
-
detail: {
|
|
152
|
-
mode,
|
|
153
|
-
},
|
|
154
|
-
}));
|
|
155
|
-
};
|
|
156
50
|
export const getFunnelMode = () => {
|
|
157
51
|
if (!canUseDom()) {
|
|
158
52
|
return 'production';
|
|
159
53
|
}
|
|
160
|
-
|
|
161
|
-
if (queryMode) {
|
|
162
|
-
persistFunnelMode(queryMode, { emit: false });
|
|
163
|
-
return queryMode;
|
|
164
|
-
}
|
|
165
|
-
const cookieMode = readFunnelModeCookie();
|
|
166
|
-
if (cookieMode) {
|
|
167
|
-
return cookieMode;
|
|
168
|
-
}
|
|
169
|
-
if (isDefaultPublishedDomain()) {
|
|
170
|
-
return 'test';
|
|
171
|
-
}
|
|
172
|
-
return readFunnelModeStorage() || 'production';
|
|
54
|
+
return isSystemPreviewHostname(window.location.hostname || '') ? 'test' : 'production';
|
|
173
55
|
};
|
|
174
56
|
export const getRuntimeMode = () => {
|
|
175
|
-
|
|
176
|
-
return 'live';
|
|
177
|
-
}
|
|
178
|
-
const queryMode = readQueryFunnelMode();
|
|
179
|
-
if (queryMode) {
|
|
180
|
-
persistFunnelMode(queryMode, { emit: false });
|
|
181
|
-
return toRuntimeModeFromFunnelMode(queryMode);
|
|
182
|
-
}
|
|
183
|
-
if (!isEditorEnabled()) {
|
|
184
|
-
return toRuntimeModeFromFunnelMode(getFunnelMode());
|
|
185
|
-
}
|
|
186
|
-
return readStoredEditorMode() || 'test';
|
|
57
|
+
return getFunnelMode() === 'test' ? 'test' : 'live';
|
|
187
58
|
};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
persistFunnelMode(mode);
|
|
59
|
+
const subscribeRuntimeEnvironment = () => {
|
|
60
|
+
return () => undefined;
|
|
193
61
|
};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const normalizedMode = toRuntimeMode(mode);
|
|
199
|
-
try {
|
|
200
|
-
window.localStorage.setItem(EDITOR_MODE_STORAGE_KEY, normalizedMode);
|
|
201
|
-
}
|
|
202
|
-
catch (_a) {
|
|
203
|
-
// ignore storage failures
|
|
204
|
-
}
|
|
205
|
-
persistFunnelMode(toFunnelModeFromRuntimeMode(normalizedMode));
|
|
62
|
+
const getServerRuntimeMode = () => {
|
|
63
|
+
return 'live';
|
|
206
64
|
};
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
var _a;
|
|
213
|
-
const customEvent = event;
|
|
214
|
-
listener(toRuntimeMode((_a = customEvent.detail) === null || _a === void 0 ? void 0 : _a.mode));
|
|
215
|
-
};
|
|
216
|
-
const onStorageChanged = (event) => {
|
|
217
|
-
if (event.key !== EDITOR_MODE_STORAGE_KEY && event.key !== FUNNEL_MODE_STORAGE_KEY) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
listener(getRuntimeMode());
|
|
221
|
-
};
|
|
222
|
-
window.addEventListener(MODE_CHANGE_EVENT, onModeChanged);
|
|
223
|
-
window.addEventListener('storage', onStorageChanged);
|
|
224
|
-
return () => {
|
|
225
|
-
window.removeEventListener(MODE_CHANGE_EVENT, onModeChanged);
|
|
226
|
-
window.removeEventListener('storage', onStorageChanged);
|
|
227
|
-
};
|
|
65
|
+
const getRuntimeEnvironmentReady = () => {
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
68
|
+
const getServerRuntimeEnvironmentReady = () => {
|
|
69
|
+
return false;
|
|
228
70
|
};
|
|
229
71
|
export const useRuntimeMode = () => {
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
syncMode();
|
|
236
|
-
const unsubscribe = subscribeRuntimeMode((nextMode) => {
|
|
237
|
-
setMode(nextMode);
|
|
238
|
-
});
|
|
239
|
-
window.addEventListener('popstate', syncMode);
|
|
240
|
-
return () => {
|
|
241
|
-
unsubscribe();
|
|
242
|
-
window.removeEventListener('popstate', syncMode);
|
|
243
|
-
};
|
|
244
|
-
}, []);
|
|
245
|
-
const updateMode = useCallback((nextMode) => {
|
|
246
|
-
const normalizedMode = toRuntimeMode(nextMode);
|
|
247
|
-
setRuntimeMode(normalizedMode);
|
|
248
|
-
setMode(normalizedMode);
|
|
249
|
-
}, []);
|
|
250
|
-
return [mode, updateMode];
|
|
72
|
+
const mode = useSyncExternalStore(subscribeRuntimeEnvironment, getRuntimeMode, getServerRuntimeMode);
|
|
73
|
+
return [mode];
|
|
74
|
+
};
|
|
75
|
+
export const useRuntimeEnvironmentReady = () => {
|
|
76
|
+
return useSyncExternalStore(subscribeRuntimeEnvironment, getRuntimeEnvironmentReady, getServerRuntimeEnvironmentReady);
|
|
251
77
|
};
|