@funnelsgrove/runtime 0.1.63 → 0.3.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 +2 -0
- package/dist/runtime/offer-set-runtime.js +2 -2
- 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 +10 -0
- package/dist/services/funnel-sdk.service.js +8 -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';
|
|
@@ -55,6 +55,7 @@ export type GeneratedOfferSetDiscount = {
|
|
|
55
55
|
readonly metadata?: Record<string, unknown> | null;
|
|
56
56
|
};
|
|
57
57
|
export type GeneratedOfferSet = {
|
|
58
|
+
readonly id?: string | null;
|
|
58
59
|
readonly key: string;
|
|
59
60
|
readonly isActive?: boolean | null;
|
|
60
61
|
readonly metadata?: Record<string, unknown> | null;
|
|
@@ -74,6 +75,7 @@ export type OfferSetRuntimeDiscount = {
|
|
|
74
75
|
readonly previousDiscountPercent: number;
|
|
75
76
|
};
|
|
76
77
|
export type OfferSetRuntimePlanOverrides = {
|
|
78
|
+
offerSetId?: string;
|
|
77
79
|
offerSetKey?: string;
|
|
78
80
|
projectPlanId?: string;
|
|
79
81
|
providerPlanId?: string;
|
|
@@ -159,7 +159,7 @@ const buildGeneratedPlanOverrides = (item, mode, isDefault) => {
|
|
|
159
159
|
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (projectPlanId ? { projectPlanId } : {})), (providerPlanId ? { providerPlanId } : {})), (paymentMode ? { paymentMode } : {})), (analyticsPurchaseValue !== null ? { analyticsPurchaseValue } : {})), (title ? { title } : {})), (description ? { description } : {})), (priceLabel ? { priceLabel } : {})), (perDayAmount ? { perDayAmount } : {})), (perDayLabel ? { perDayLabel } : {})), (amountCents !== null ? { amountCents: Math.max(0, Math.round(amountCents)) } : {})), (checkoutSummaryLabel ? { checkoutSummaryLabel } : {})), (billingInterval ? { billingInterval } : {})), (billingIntervalCount !== null ? { billingIntervalCount: Math.max(1, Math.round(billingIntervalCount)) } : {})), (followUpProviderPlanId ? { followUpProviderPlanId } : {})), (followUpPlanTitle ? { followUpPlanTitle } : {})), (followUpBillingInterval ? { followUpBillingInterval } : {})), (followUpBillingIntervalCount !== null ? { followUpBillingIntervalCount: Math.max(1, Math.round(followUpBillingIntervalCount)) } : {})), (introIterations !== null ? { introIterations: Math.max(1, Math.round(introIterations)) } : {})), (featuredTag ? { featuredTag } : {})), (oldPriceLabel ? { oldPriceLabel } : {})), (isDefault ? { isDefault: true } : {}));
|
|
160
160
|
};
|
|
161
161
|
export const applyGeneratedOfferSetToPlanCatalog = (baseCatalog, mode, offerSet) => {
|
|
162
|
-
var _a, _b, _c;
|
|
162
|
+
var _a, _b, _c, _d;
|
|
163
163
|
if (!offerSet) {
|
|
164
164
|
return Object.assign({}, baseCatalog);
|
|
165
165
|
}
|
|
@@ -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(Object.assign({}, ((_c = nextCatalog[key]) !== null && _c !== void 0 ? _c : {})), { offerSetKey: offerSet.key }), buildGeneratedPlanOverrides(item, mode, getOfferSetItemKind(item) === 'plan' && key === defaultPlanKey));
|
|
182
|
+
nextCatalog[key] = Object.assign(Object.assign(Object.assign(Object.assign({}, ((_c = nextCatalog[key]) !== null && _c !== void 0 ? _c : {})), (((_d = offerSet.id) === null || _d === void 0 ? void 0 : _d.trim()) ? { offerSetId: offerSet.id.trim() } : {})), { 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
|
});
|
|
@@ -10,6 +10,8 @@ export type FunnelSdkJsonObject = Record<string, unknown>;
|
|
|
10
10
|
export type FunnelSdkListPaymentPlansResponse = {
|
|
11
11
|
funnelId?: string;
|
|
12
12
|
environment?: FunnelSdkRuntimeMode;
|
|
13
|
+
offerSetId?: string;
|
|
14
|
+
offerSetKey?: string;
|
|
13
15
|
plans: FunnelSdkPaymentPlan[];
|
|
14
16
|
};
|
|
15
17
|
export type FunnelSdkBootstrapUserInput = {
|
|
@@ -72,6 +74,7 @@ export type FunnelSdkUploadPhotoInput = {
|
|
|
72
74
|
};
|
|
73
75
|
export type FunnelSdkCreateHostedCheckoutSessionInput = {
|
|
74
76
|
planId: string;
|
|
77
|
+
offerSetId?: string | null;
|
|
75
78
|
offerSetKey?: string | null;
|
|
76
79
|
amountCents: number;
|
|
77
80
|
successUrl: string;
|
|
@@ -95,6 +98,7 @@ export type FunnelSdkCreateHostedCheckoutSessionResponse = {
|
|
|
95
98
|
};
|
|
96
99
|
export type FunnelSdkCreateSubscriptionCheckoutInput = {
|
|
97
100
|
planId: string;
|
|
101
|
+
offerSetId?: string | null;
|
|
98
102
|
offerSetKey?: string | null;
|
|
99
103
|
amountCents: number;
|
|
100
104
|
returnUrl: string;
|
|
@@ -124,6 +128,7 @@ export type FunnelSdkCreateSubscriptionCheckoutResponse = {
|
|
|
124
128
|
};
|
|
125
129
|
export type FunnelSdkCreateOneTimeCheckoutInput = {
|
|
126
130
|
planId: string;
|
|
131
|
+
offerSetId?: string | null;
|
|
127
132
|
offerSetKey?: string | null;
|
|
128
133
|
amountCents: number;
|
|
129
134
|
returnUrl: string;
|
|
@@ -146,6 +151,7 @@ export type FunnelSdkCreateOneTimeCheckoutResponse = {
|
|
|
146
151
|
export type FunnelSdkUpdateSubscriptionCheckoutPlanInput = {
|
|
147
152
|
checkoutSessionId: string;
|
|
148
153
|
planId: string;
|
|
154
|
+
offerSetId?: string | null;
|
|
149
155
|
offerSetKey?: string | null;
|
|
150
156
|
amountCents: number;
|
|
151
157
|
analyticsMetadata?: Record<string, unknown> | null;
|
|
@@ -187,6 +193,8 @@ export type FunnelSdkVerifySubscriptionCheckoutResponse = {
|
|
|
187
193
|
*/
|
|
188
194
|
export type FunnelSdkCreateOneTimePaymentIntentInput = {
|
|
189
195
|
planId: string;
|
|
196
|
+
offerSetId?: string | null;
|
|
197
|
+
offerSetKey?: string | null;
|
|
190
198
|
amountCents: number;
|
|
191
199
|
analyticsMetadata?: Record<string, unknown> | null;
|
|
192
200
|
couponId?: string | null;
|
|
@@ -250,6 +258,8 @@ declare class FunnelSdkService {
|
|
|
250
258
|
uploadPhoto<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkUploadPhotoInput): Promise<ResponsePayload>;
|
|
251
259
|
listPaymentPlans(input?: {
|
|
252
260
|
environment?: FunnelSdkRuntimeMode;
|
|
261
|
+
offerSetId?: string | null;
|
|
262
|
+
offerSetKey?: string | null;
|
|
253
263
|
runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
|
|
254
264
|
}): Promise<FunnelSdkListPaymentPlansResponse>;
|
|
255
265
|
createHostedCheckoutSession(input: FunnelSdkCreateHostedCheckoutSessionInput): Promise<FunnelSdkCreateHostedCheckoutSessionResponse>;
|
|
@@ -193,6 +193,8 @@ class FunnelSdkService {
|
|
|
193
193
|
searchParams: {
|
|
194
194
|
environment: input.environment,
|
|
195
195
|
funnelId,
|
|
196
|
+
offerSetId: input.offerSetId,
|
|
197
|
+
offerSetKey: input.offerSetKey,
|
|
196
198
|
},
|
|
197
199
|
errorMessage: 'Unable to load payment plans',
|
|
198
200
|
});
|
|
@@ -201,6 +203,7 @@ class FunnelSdkService {
|
|
|
201
203
|
return this.postJson('/sdk/public/payments/checkout-session', {
|
|
202
204
|
runtimeConfig: input.runtimeConfig,
|
|
203
205
|
body: {
|
|
206
|
+
offerSetId: input.offerSetId,
|
|
204
207
|
offerSetKey: input.offerSetKey,
|
|
205
208
|
planId: input.planId,
|
|
206
209
|
providerPlanId: input.providerPlanId,
|
|
@@ -224,6 +227,7 @@ class FunnelSdkService {
|
|
|
224
227
|
return this.postJson('/sdk/public/payments/subscriptions', {
|
|
225
228
|
runtimeConfig: input.runtimeConfig,
|
|
226
229
|
body: {
|
|
230
|
+
offerSetId: input.offerSetId,
|
|
227
231
|
offerSetKey: input.offerSetKey,
|
|
228
232
|
planId: input.planId,
|
|
229
233
|
providerPlanId: input.providerPlanId,
|
|
@@ -251,6 +255,7 @@ class FunnelSdkService {
|
|
|
251
255
|
return this.postJson('/sdk/public/payments/one-time-checkout', {
|
|
252
256
|
runtimeConfig: input.runtimeConfig,
|
|
253
257
|
body: {
|
|
258
|
+
offerSetId: input.offerSetId,
|
|
254
259
|
offerSetKey: input.offerSetKey,
|
|
255
260
|
planId: input.planId,
|
|
256
261
|
providerPlanId: input.providerPlanId,
|
|
@@ -272,6 +277,7 @@ class FunnelSdkService {
|
|
|
272
277
|
runtimeConfig: input.runtimeConfig,
|
|
273
278
|
body: {
|
|
274
279
|
checkoutSessionId: input.checkoutSessionId,
|
|
280
|
+
offerSetId: input.offerSetId,
|
|
275
281
|
offerSetKey: input.offerSetKey,
|
|
276
282
|
planId: input.planId,
|
|
277
283
|
providerPlanId: input.providerPlanId,
|
|
@@ -306,6 +312,8 @@ class FunnelSdkService {
|
|
|
306
312
|
return this.postJson('/sdk/public/payments/intents', {
|
|
307
313
|
runtimeConfig: input.runtimeConfig,
|
|
308
314
|
body: {
|
|
315
|
+
offerSetId: input.offerSetId,
|
|
316
|
+
offerSetKey: input.offerSetKey,
|
|
309
317
|
planId: input.planId,
|
|
310
318
|
amountCents: input.amountCents,
|
|
311
319
|
couponId: input.couponId,
|
|
@@ -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
|
};
|