@funnelsgrove/runtime 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/components/FunnelContext.d.ts +5 -2
- package/dist/components/FunnelContext.js +3 -0
- package/dist/components/FunnelEditorPanel.d.ts +3 -5
- package/dist/components/FunnelEditorPanel.js +3 -3
- package/dist/components/ManageSubscriptionScreen.d.ts +51 -0
- package/dist/components/ManageSubscriptionScreen.js +349 -0
- package/dist/components/RuntimeDevInfoBox.d.ts +23 -0
- package/dist/components/RuntimeDevInfoBox.js +363 -0
- package/dist/components/SubscriptionHandoffScreen.d.ts +31 -0
- package/dist/components/SubscriptionHandoffScreen.js +338 -0
- package/dist/config/builder-preview.protocol.d.ts +73 -0
- package/dist/config/builder-preview.protocol.js +3 -0
- package/dist/config/env.config.d.ts +44 -0
- package/dist/config/env.config.js +161 -0
- package/dist/config/font-config.d.ts +14 -0
- package/dist/config/font-config.js +101 -0
- package/dist/config/funnel-theme.d.ts +61 -10
- package/dist/config/funnel-theme.js +355 -35
- package/dist/config/funnel.manifest.types.d.ts +13 -7
- package/dist/content/step-content.d.ts +130 -0
- package/dist/content/step-content.js +381 -0
- package/dist/index.d.ts +33 -21
- package/dist/index.js +33 -21
- package/dist/runtime/browser-helpers.d.ts +1 -0
- package/dist/runtime/browser-helpers.js +14 -0
- package/dist/runtime/experiment-assignment.d.ts +13 -4
- package/dist/runtime/experiment-assignment.js +9 -27
- package/dist/runtime/funnel-attribution.d.ts +18 -0
- package/dist/runtime/funnel-attribution.js +226 -0
- package/dist/runtime/funnel-flow.d.ts +9 -10
- package/dist/runtime/funnel-flow.js +4 -18
- package/dist/runtime/funnel-manifest.validation.d.ts +1 -1
- package/dist/runtime/funnel-manifest.validation.js +2 -6
- package/dist/runtime/funnel-runtime.d.ts +2 -3
- package/dist/runtime/funnel-runtime.js +6 -13
- package/dist/runtime/posthog-flags.d.ts +30 -0
- package/dist/runtime/posthog-flags.js +71 -0
- package/dist/runtime/preview-bridge.d.ts +13 -3
- package/dist/runtime/preview-bridge.js +96 -4
- package/dist/runtime/preview-definition-overrides.d.ts +20 -0
- package/dist/runtime/preview-definition-overrides.js +148 -0
- package/dist/runtime/route-resolver.d.ts +2 -3
- package/dist/runtime/route-resolver.js +15 -26
- package/dist/runtime/subscription-handoff.d.ts +32 -0
- package/dist/runtime/subscription-handoff.js +113 -0
- package/dist/runtime/use-funnel-flow-controller.d.ts +19 -10
- package/dist/runtime/use-funnel-flow-controller.js +190 -159
- package/dist/sdk/userAnswers.d.ts +2 -2
- package/dist/services/api.service.d.ts +21 -4
- package/dist/services/api.service.js +165 -35
- package/dist/services/funnel-state.service.d.ts +8 -0
- package/dist/services/funnel-state.service.js +44 -0
- package/dist/services/preview-frame.service.d.ts +2 -2
- package/dist/services/preview-frame.service.js +2 -2
- package/dist/services/public-env.d.ts +69 -0
- package/dist/services/public-env.js +105 -0
- package/dist/services/runtime-api.config.d.ts +5 -0
- package/dist/services/runtime-api.config.js +12 -7
- package/dist/services/runtime-mode.service.d.ts +3 -0
- package/dist/services/runtime-mode.service.js +142 -4
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ Shared funnel runtime contracts and helpers.
|
|
|
6
6
|
|
|
7
7
|
- Build distributable output with `npm run build --workspace @funnelsgrove/runtime`.
|
|
8
8
|
- Publish from the repo root with `npm publish --workspace @funnelsgrove/runtime --access public`.
|
|
9
|
-
- Local repo installs still resolve this package through npm workspaces when another workspace depends on version `0.1.
|
|
9
|
+
- Local repo installs still resolve this package through npm workspaces when another workspace depends on version `0.1.1`.
|
|
10
|
+
- The build normalizes generated relative ESM imports to explicit `.js` files for published package consumers.
|
|
10
11
|
|
|
11
12
|
## Use This Package For
|
|
12
13
|
|
|
@@ -16,7 +17,9 @@ Shared funnel runtime contracts and helpers.
|
|
|
16
17
|
- preview-bridge parsing and preview/runtime detection
|
|
17
18
|
- published theme contract and CSS variable helpers
|
|
18
19
|
- funnel context, base controls, and reusable runtime UI primitives
|
|
20
|
+
- subscription handoff and subscription management screens whose copy stays funnel-local
|
|
19
21
|
- browser-safe API client helpers used by funnels
|
|
22
|
+
- a shared test-mode developer info box for funnel-local preview tooling
|
|
20
23
|
|
|
21
24
|
## Responsibilities
|
|
22
25
|
|
|
@@ -28,10 +31,25 @@ Shared funnel runtime contracts and helpers.
|
|
|
28
31
|
|
|
29
32
|
- reusable runtime helpers that any funnel can use
|
|
30
33
|
- shared browser/event/storage helpers tied to funnel behavior
|
|
34
|
+
- funnel-scoped paywall state helpers that keep runtime-owned storage keys under `fg_...`
|
|
31
35
|
- hosted-path navigation helpers so static previews keep their `/published/f/...` or `/catalog/...` prefix
|
|
32
36
|
- generic context and base component primitives
|
|
33
37
|
- safe local-only fallbacks when no real SDK publishable key is configured
|
|
38
|
+
- shared preview-checkout publishable key fallback used by paywalls when only the seed placeholder is configured
|
|
34
39
|
- default runtime config values for API/bootstrap wiring
|
|
40
|
+
- env-backed support email rendering for shared subscription management screens
|
|
41
|
+
- full-step subscription management surfaces that avoid exposing an outer page background rim
|
|
42
|
+
- subscription handoff link rendering for iOS, Android, and desktop/web fallbacks from runtime env
|
|
43
|
+
- subscription handoff browser-only values are resolved after mount so published success routes hydrate without text mismatches
|
|
44
|
+
- public subscription summaries with provider plan and period metadata for funnel-local management screens
|
|
45
|
+
- a bottom-right `RuntimeDevInfoBox` that funnels can mount in test mode to inspect public runtime config with one canonical env/config name per row, copy the current funnel user id/email, switch to live mode, and clear funnel-scoped paywall state
|
|
46
|
+
|
|
47
|
+
## Storage Conventions
|
|
48
|
+
|
|
49
|
+
- runtime user ids stay funnel-scoped under `funnel:<funnelId>:user-id`
|
|
50
|
+
- runtime-generated user ids use the `u_` prefix
|
|
51
|
+
- paywall state is stored through runtime helpers under `fg_state:<funnelId>`
|
|
52
|
+
- developer reset controls should clear paywall state through `clearPaywallStateValue`, including any funnel-provided legacy keys
|
|
35
53
|
|
|
36
54
|
## What Does Not Belong Here
|
|
37
55
|
|
|
@@ -40,3 +58,4 @@ Shared funnel runtime contracts and helpers.
|
|
|
40
58
|
- billing catalog data
|
|
41
59
|
- analytics SDK transport
|
|
42
60
|
- Stripe checkout UI
|
|
61
|
+
- funnel-specific developer widget copy, config extras, or legacy storage keys
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FunnelStepId } from '../runtime/funnel-runtime';
|
|
2
|
-
import type { FunnelUserAnswers } from '../sdk/userAnswers';
|
|
1
|
+
import type { FunnelStepId } from '../runtime/funnel-runtime.js';
|
|
2
|
+
import type { FunnelUserAnswers } from '../sdk/userAnswers.js';
|
|
3
3
|
/** A single record of a completed step, stored in the user's history. */
|
|
4
4
|
export type StepCompletionRecord = {
|
|
5
5
|
stepId: string;
|
|
@@ -12,11 +12,13 @@ export type FunnelUser = {
|
|
|
12
12
|
name: string;
|
|
13
13
|
email: string;
|
|
14
14
|
attributes: FunnelUserAnswers;
|
|
15
|
+
document?: Record<string, unknown>;
|
|
15
16
|
/** Ordered history of every step the user has completed. */
|
|
16
17
|
completedSteps?: StepCompletionRecord[];
|
|
17
18
|
};
|
|
18
19
|
export type FunnelContextValue = {
|
|
19
20
|
activeStepId: FunnelStepId;
|
|
21
|
+
isBuilder: boolean;
|
|
20
22
|
goToStep: (stepId: string) => void;
|
|
21
23
|
goNext: () => void;
|
|
22
24
|
goChoice: (choice: 'yes' | 'no', stepId?: string) => void;
|
|
@@ -40,4 +42,5 @@ type FunnelProviderProps = {
|
|
|
40
42
|
};
|
|
41
43
|
export declare function FunnelProvider({ value, children }: FunnelProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
42
44
|
export declare function useFunnel(): FunnelContextValue;
|
|
45
|
+
export declare function useOptionalFunnel(): FunnelContextValue | null;
|
|
43
46
|
export {};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { RuntimeMode } from '../services/runtime-mode.service';
|
|
1
|
+
import type { RuntimeMode } from '../services/runtime-mode.service.js';
|
|
2
2
|
export type FunnelEditorPanelExperiment = {
|
|
3
3
|
experimentId: string;
|
|
4
|
-
status: 'draft' | 'active';
|
|
5
4
|
variants: readonly {
|
|
6
|
-
|
|
7
|
-
label: string;
|
|
5
|
+
variantKey: string;
|
|
8
6
|
}[];
|
|
9
7
|
};
|
|
10
8
|
type FunnelEditorPanelProps<Experiment extends FunnelEditorPanelExperiment> = {
|
|
@@ -13,7 +11,7 @@ type FunnelEditorPanelProps<Experiment extends FunnelEditorPanelExperiment> = {
|
|
|
13
11
|
experimentAssignmentForEditor: (experiment: Experiment) => string;
|
|
14
12
|
funnelExperiments: readonly Experiment[];
|
|
15
13
|
onRuntimeModeChange: (mode: RuntimeMode) => void;
|
|
16
|
-
onSelectVariant: (experiment: Experiment,
|
|
14
|
+
onSelectVariant: (experiment: Experiment, variantKey: string) => void;
|
|
17
15
|
onToggleExpanded: () => void;
|
|
18
16
|
runtimeMode: RuntimeMode;
|
|
19
17
|
};
|
|
@@ -5,10 +5,10 @@ export function FunnelEditorPanel({ editorModeEnabled, editorPanelExpanded, expe
|
|
|
5
5
|
return null;
|
|
6
6
|
}
|
|
7
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 ? (_jsxs("div", { className: 'funnel-editor-panel-content', children: [_jsx("p", { className: 'funnel-editor-panel-label', children: "MODE" }), _jsxs("div", { className: 'funnel-editor-panel-options', children: [_jsx("button", { type: 'button', className: runtimeMode === 'test' ? 'is-active' : '', onClick: () => onRuntimeModeChange('test'), children: "Test" }), _jsx("button", { type: 'button', className: runtimeMode === 'live' ? 'is-active' : '', onClick: () => onRuntimeModeChange('live'), children: "Live" })] }), _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
|
|
9
|
-
return (_jsxs("label", { className: 'funnel-editor-panel-test-item', children: [
|
|
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
10
|
onSelectVariant(experiment, event.target.value);
|
|
11
|
-
}, children: experiment.variants.map((variant) => (_jsx("option", { value: variant.
|
|
11
|
+
}, children: experiment.variants.map((variant) => (_jsx("option", { value: variant.variantKey, children: variant.variantKey }, variant.variantKey))) })] }, experiment.experimentId));
|
|
12
12
|
}) }))] })] })) : null] }));
|
|
13
13
|
}
|
|
14
14
|
export const funnelEditorPanelStyles = `
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { LinkItem, ReasonItem } from '../content/step-content.js';
|
|
2
|
+
export type ManageSubscriptionContent = {
|
|
3
|
+
subscriptionsStage: {
|
|
4
|
+
title: string;
|
|
5
|
+
loadingLabel: string;
|
|
6
|
+
emptyLabel: string;
|
|
7
|
+
listAriaLabel: string;
|
|
8
|
+
supportPrefix: string;
|
|
9
|
+
continueLabel: string;
|
|
10
|
+
rowSelectedLabel: string;
|
|
11
|
+
rowSelectLabel: string;
|
|
12
|
+
subscriptionSuffix: string;
|
|
13
|
+
renewsOnPrefix: string;
|
|
14
|
+
cancelScheduledLabel: string;
|
|
15
|
+
notCancellableLabel: string;
|
|
16
|
+
};
|
|
17
|
+
whyStage: {
|
|
18
|
+
title: string;
|
|
19
|
+
backLabel: string;
|
|
20
|
+
reasonsAriaLabel: string;
|
|
21
|
+
reasons: readonly ReasonItem[];
|
|
22
|
+
};
|
|
23
|
+
confirmStage: {
|
|
24
|
+
title: string;
|
|
25
|
+
reasonPrefix: string;
|
|
26
|
+
selectedPlanPrefix: string;
|
|
27
|
+
missingSelectionLabel: string;
|
|
28
|
+
cancelLabel: string;
|
|
29
|
+
cancellingLabel: string;
|
|
30
|
+
backLabel: string;
|
|
31
|
+
};
|
|
32
|
+
doneStage: {
|
|
33
|
+
title: string;
|
|
34
|
+
cancelledMessage: string;
|
|
35
|
+
emptyMessage: string;
|
|
36
|
+
returnHomeLabel: string;
|
|
37
|
+
};
|
|
38
|
+
supportLink: LinkItem;
|
|
39
|
+
errorMessages: {
|
|
40
|
+
load: string;
|
|
41
|
+
cancel: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
type ManageSubscriptionScreenProps = {
|
|
45
|
+
stepId: string;
|
|
46
|
+
content: ManageSubscriptionContent;
|
|
47
|
+
homeStepId: string;
|
|
48
|
+
nodeId?: string;
|
|
49
|
+
};
|
|
50
|
+
export declare function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId, }: ManageSubscriptionScreenProps): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { apiService } from '../services/api.service.js';
|
|
5
|
+
import { buildPreviewManageSubscriptionsFallback, isPreviewFrameRuntime, } from '../services/preview-frame.service.js';
|
|
6
|
+
import { runtimePublicConfig } from '../services/public-env.js';
|
|
7
|
+
import { useFunnel } from './FunnelContext.js';
|
|
8
|
+
const supportEmail = runtimePublicConfig.supportEmail;
|
|
9
|
+
const isCancellableSubscription = (subscription) => {
|
|
10
|
+
const normalizedStatus = subscription.status.trim().toLowerCase();
|
|
11
|
+
return normalizedStatus !== 'canceled' && !subscription.cancelAtPeriodEnd;
|
|
12
|
+
};
|
|
13
|
+
const formatDate = (value) => {
|
|
14
|
+
if (!value) {
|
|
15
|
+
return 'n/a';
|
|
16
|
+
}
|
|
17
|
+
const timestamp = Date.parse(value);
|
|
18
|
+
if (!Number.isFinite(timestamp)) {
|
|
19
|
+
return 'n/a';
|
|
20
|
+
}
|
|
21
|
+
return new Date(timestamp).toLocaleDateString();
|
|
22
|
+
};
|
|
23
|
+
const getRowActionLabel = (subscription, isSelected, content) => {
|
|
24
|
+
if (isCancellableSubscription(subscription)) {
|
|
25
|
+
return isSelected
|
|
26
|
+
? content.subscriptionsStage.rowSelectedLabel
|
|
27
|
+
: content.subscriptionsStage.rowSelectLabel;
|
|
28
|
+
}
|
|
29
|
+
if (subscription.cancelAtPeriodEnd) {
|
|
30
|
+
return content.subscriptionsStage.cancelScheduledLabel;
|
|
31
|
+
}
|
|
32
|
+
return content.subscriptionsStage.notCancellableLabel;
|
|
33
|
+
};
|
|
34
|
+
export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId, }) {
|
|
35
|
+
var _a, _b, _c, _d;
|
|
36
|
+
const { goToStep, setAnswer } = useFunnel();
|
|
37
|
+
const supportLinkLabel = supportEmail;
|
|
38
|
+
const supportLinkHref = `mailto:${supportEmail}`;
|
|
39
|
+
const [stage, setStage] = useState('subscriptions');
|
|
40
|
+
const [data, setData] = useState(null);
|
|
41
|
+
const [loading, setLoading] = useState(true);
|
|
42
|
+
const [error, setError] = useState(null);
|
|
43
|
+
const [selectedReasonId, setSelectedReasonId] = useState('');
|
|
44
|
+
const [selectedSubscriptionId, setSelectedSubscriptionId] = useState(null);
|
|
45
|
+
const [cancelInFlight, setCancelInFlight] = useState(false);
|
|
46
|
+
const [cancelCompleted, setCancelCompleted] = useState(false);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
let active = true;
|
|
49
|
+
const load = async () => {
|
|
50
|
+
var _a;
|
|
51
|
+
setLoading(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
if (isPreviewFrameRuntime()) {
|
|
54
|
+
const payload = buildPreviewManageSubscriptionsFallback(apiService.getOrCreateClientUserId());
|
|
55
|
+
if (active) {
|
|
56
|
+
setData(payload);
|
|
57
|
+
setSelectedSubscriptionId(null);
|
|
58
|
+
setLoading(false);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const payload = await apiService.getManageSubscriptions();
|
|
64
|
+
if (!active) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setData(payload);
|
|
68
|
+
const firstCancellable = (_a = payload.subscriptions.find(isCancellableSubscription)) !== null && _a !== void 0 ? _a : null;
|
|
69
|
+
setSelectedSubscriptionId((current) => {
|
|
70
|
+
var _a;
|
|
71
|
+
const currentStillCancellable = payload.subscriptions.some((subscription) => {
|
|
72
|
+
return subscription.id === current && isCancellableSubscription(subscription);
|
|
73
|
+
});
|
|
74
|
+
if (current && currentStillCancellable) {
|
|
75
|
+
return current;
|
|
76
|
+
}
|
|
77
|
+
return (_a = firstCancellable === null || firstCancellable === void 0 ? void 0 : firstCancellable.id) !== null && _a !== void 0 ? _a : null;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (nextError) {
|
|
81
|
+
if (!active) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setError(nextError instanceof Error ? nextError.message : content.errorMessages.load);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
if (active) {
|
|
88
|
+
setLoading(false);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
void load();
|
|
93
|
+
return () => {
|
|
94
|
+
active = false;
|
|
95
|
+
};
|
|
96
|
+
}, [content.errorMessages.load]);
|
|
97
|
+
const subscriptions = useMemo(() => { var _a; return (_a = data === null || data === void 0 ? void 0 : data.subscriptions) !== null && _a !== void 0 ? _a : []; }, [data]);
|
|
98
|
+
const cancellableSubscriptions = useMemo(() => {
|
|
99
|
+
return subscriptions.filter(isCancellableSubscription);
|
|
100
|
+
}, [subscriptions]);
|
|
101
|
+
const selectedReason = (_b = (_a = content.whyStage.reasons.find((reason) => reason.id === selectedReasonId)) !== null && _a !== void 0 ? _a : content.whyStage.reasons[0]) !== null && _b !== void 0 ? _b : null;
|
|
102
|
+
const selectedSubscription = (_d = (_c = cancellableSubscriptions.find((subscription) => subscription.id === selectedSubscriptionId)) !== null && _c !== void 0 ? _c : cancellableSubscriptions[0]) !== null && _d !== void 0 ? _d : null;
|
|
103
|
+
const handleContinueFromSubscriptions = () => {
|
|
104
|
+
const hasCancellableSubscription = cancellableSubscriptions.length > 0;
|
|
105
|
+
setAnswer('manageSubscriptionHasActive', hasCancellableSubscription);
|
|
106
|
+
if (!hasCancellableSubscription) {
|
|
107
|
+
setCancelCompleted(false);
|
|
108
|
+
setStage('done');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
setStage('why');
|
|
112
|
+
};
|
|
113
|
+
const handleReasonSelected = (reasonId) => {
|
|
114
|
+
setSelectedReasonId(reasonId);
|
|
115
|
+
setAnswer('manageSubscriptionReason', reasonId);
|
|
116
|
+
setStage('confirm');
|
|
117
|
+
};
|
|
118
|
+
const handleCancelSubscription = async () => {
|
|
119
|
+
if (!selectedSubscription || !selectedReason || cancelInFlight) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
setCancelInFlight(true);
|
|
123
|
+
setError(null);
|
|
124
|
+
try {
|
|
125
|
+
const payload = await apiService.updateSubscription({
|
|
126
|
+
subscriptionId: selectedSubscription.id,
|
|
127
|
+
action: 'cancel',
|
|
128
|
+
});
|
|
129
|
+
setData(payload);
|
|
130
|
+
setAnswer('manageSubscriptionCancelled', true);
|
|
131
|
+
setAnswer('manageSubscriptionCancelledId', selectedSubscription.id);
|
|
132
|
+
setAnswer('manageSubscriptionReason', selectedReason.id);
|
|
133
|
+
setCancelCompleted(true);
|
|
134
|
+
setStage('done');
|
|
135
|
+
}
|
|
136
|
+
catch (nextError) {
|
|
137
|
+
setError(nextError instanceof Error ? nextError.message : content.errorMessages.cancel);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
setCancelInFlight(false);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
return (_jsxs(_Fragment, { children: [_jsx("section", { className: 'manage-subscription-step', "data-node-id": nodeId || stepId, children: _jsxs("article", { className: 'manage-subscription-card', children: [stage === 'subscriptions' ? (_jsxs(_Fragment, { children: [_jsx("h1", { className: 'manage-subscription-title', children: content.subscriptionsStage.title }), _jsxs("section", { className: 'manage-subscription-list-card', "aria-label": content.subscriptionsStage.listAriaLabel, children: [loading ? (_jsx("p", { className: 'manage-subscription-muted', children: content.subscriptionsStage.loadingLabel })) : null, error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, !loading && !error && subscriptions.length === 0 ? (_jsx("p", { className: 'manage-subscription-empty', children: content.subscriptionsStage.emptyLabel })) : null, !loading && subscriptions.length > 0 ? (_jsx("ul", { className: 'manage-subscription-list', role: 'list', children: subscriptions.map((subscription) => {
|
|
144
|
+
const canCancel = isCancellableSubscription(subscription);
|
|
145
|
+
const isSelected = (selectedSubscription === null || selectedSubscription === void 0 ? void 0 : selectedSubscription.id) === subscription.id;
|
|
146
|
+
return (_jsx("li", { children: _jsxs("button", { type: 'button', className: isSelected
|
|
147
|
+
? 'manage-subscription-row is-selected'
|
|
148
|
+
: 'manage-subscription-row', disabled: !canCancel, onClick: () => setSelectedSubscriptionId(subscription.id), children: [_jsxs("span", { children: [_jsxs("strong", { children: [subscription.environment.toUpperCase(), ' ', content.subscriptionsStage.subscriptionSuffix] }), _jsxs("small", { children: [subscription.status, " \u00B7 ", content.subscriptionsStage.renewsOnPrefix, ' ', formatDate(subscription.currentPeriodEnd)] })] }), _jsx("span", { children: getRowActionLabel(subscription, isSelected, content) })] }) }, subscription.id));
|
|
149
|
+
}) })) : null] }), _jsxs("p", { className: 'manage-subscription-support', children: [content.subscriptionsStage.supportPrefix, ' ', _jsx("a", { href: supportLinkHref, children: supportLinkLabel })] }), _jsx("button", { type: 'button', className: 'manage-subscription-primary', disabled: loading, onClick: handleContinueFromSubscriptions, children: content.subscriptionsStage.continueLabel })] })) : null, stage === 'why' ? (_jsxs(_Fragment, { children: [_jsx("h1", { className: 'manage-subscription-title', children: content.whyStage.title }), _jsx("ul", { className: 'manage-subscription-reason-list', role: 'list', "aria-label": content.whyStage.reasonsAriaLabel, children: content.whyStage.reasons.map((reason) => (_jsx("li", { children: _jsxs("button", { type: 'button', className: 'manage-subscription-reason', onClick: () => handleReasonSelected(reason.id), children: [_jsx("span", { className: 'manage-subscription-reason-icon', "aria-hidden": true, children: reason.icon }), _jsx("span", { className: 'manage-subscription-reason-label', children: reason.label }), _jsx("span", { className: 'manage-subscription-reason-arrow', "aria-hidden": true, children: "\u203A" })] }) }, reason.id))) }), _jsx("button", { type: 'button', className: 'manage-subscription-secondary', onClick: () => setStage('subscriptions'), children: content.whyStage.backLabel })] })) : null, stage === 'confirm' ? (_jsxs(_Fragment, { children: [_jsx("h1", { className: 'manage-subscription-title', children: content.confirmStage.title }), _jsxs("p", { className: 'manage-subscription-confirm-copy', children: [content.confirmStage.reasonPrefix, " ", (selectedReason === null || selectedReason === void 0 ? void 0 : selectedReason.label) || ''] }), selectedSubscription ? (_jsxs("p", { className: 'manage-subscription-confirm-meta', children: [content.confirmStage.selectedPlanPrefix, ' ', selectedSubscription.environment.toUpperCase(), ' ', content.subscriptionsStage.subscriptionSuffix] })) : (_jsx("p", { className: 'manage-subscription-error', children: content.confirmStage.missingSelectionLabel })), error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', disabled: !selectedSubscription || !selectedReason || cancelInFlight, onClick: () => {
|
|
150
|
+
void handleCancelSubscription();
|
|
151
|
+
}, children: cancelInFlight
|
|
152
|
+
? content.confirmStage.cancellingLabel
|
|
153
|
+
: content.confirmStage.cancelLabel }), _jsx("button", { type: 'button', className: 'manage-subscription-secondary', onClick: () => setStage('why'), children: content.confirmStage.backLabel })] })) : null, stage === 'done' ? (_jsxs(_Fragment, { children: [_jsx("h1", { className: 'manage-subscription-title', children: content.doneStage.title }), _jsx("p", { className: 'manage-subscription-confirm-copy', children: cancelCompleted
|
|
154
|
+
? content.doneStage.cancelledMessage
|
|
155
|
+
: content.doneStage.emptyMessage }), _jsxs("p", { className: 'manage-subscription-support', children: [content.subscriptionsStage.supportPrefix, ' ', _jsx("a", { href: supportLinkHref, children: supportLinkLabel })] }), _jsx("button", { type: 'button', className: 'manage-subscription-primary', onClick: () => goToStep(homeStepId), children: content.doneStage.returnHomeLabel })] })) : null] }) }), _jsx("style", { children: manageSubscriptionScreenStyles })] }));
|
|
156
|
+
}
|
|
157
|
+
const manageSubscriptionScreenStyles = `
|
|
158
|
+
.manage-subscription-step {
|
|
159
|
+
position: absolute;
|
|
160
|
+
inset: 0;
|
|
161
|
+
overflow-y: auto;
|
|
162
|
+
box-sizing: border-box;
|
|
163
|
+
background: color-mix(in srgb, var(--color-primary, #4db53f) 8%, var(--color-surface, #fff) 92%);
|
|
164
|
+
padding: 0;
|
|
165
|
+
font-family: var(--font-family-base, Inter, sans-serif);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.manage-subscription-card {
|
|
169
|
+
box-sizing: border-box;
|
|
170
|
+
min-height: 100%;
|
|
171
|
+
border: 0;
|
|
172
|
+
border-radius: 0;
|
|
173
|
+
background: transparent;
|
|
174
|
+
padding: 18px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.manage-subscription-title {
|
|
178
|
+
margin: 0;
|
|
179
|
+
color: var(--color-text, #262729);
|
|
180
|
+
font-size: 48px;
|
|
181
|
+
line-height: 1.1;
|
|
182
|
+
font-weight: 700;
|
|
183
|
+
text-align: center;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.manage-subscription-list-card {
|
|
187
|
+
margin-top: 22px;
|
|
188
|
+
border-radius: 14px;
|
|
189
|
+
background: var(--color-surface, #fff);
|
|
190
|
+
padding: 14px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.manage-subscription-list {
|
|
194
|
+
margin: 0;
|
|
195
|
+
padding: 0;
|
|
196
|
+
list-style: none;
|
|
197
|
+
display: grid;
|
|
198
|
+
gap: 10px;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.manage-subscription-row {
|
|
202
|
+
width: 100%;
|
|
203
|
+
border: 1px solid var(--color-border, #dce2ff);
|
|
204
|
+
background: var(--color-surface, #fff);
|
|
205
|
+
border-radius: 12px;
|
|
206
|
+
padding: 12px;
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
justify-content: space-between;
|
|
210
|
+
gap: 12px;
|
|
211
|
+
color: var(--color-text, #262729);
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
text-align: left;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.manage-subscription-row:disabled {
|
|
217
|
+
opacity: 0.62;
|
|
218
|
+
cursor: default;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.manage-subscription-row span {
|
|
222
|
+
display: grid;
|
|
223
|
+
gap: 4px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.manage-subscription-row small {
|
|
227
|
+
color: var(--color-text-muted, #5f6b74);
|
|
228
|
+
font-size: 12px;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.manage-subscription-row.is-selected {
|
|
232
|
+
border-color: var(--color-primary, #4db53f);
|
|
233
|
+
box-shadow: inset 0 0 0 1px var(--color-primary, #4db53f);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.manage-subscription-support {
|
|
237
|
+
margin: 24px 0 0;
|
|
238
|
+
color: var(--color-text-muted, #5f6b74);
|
|
239
|
+
font-size: 17px;
|
|
240
|
+
line-height: 1.45;
|
|
241
|
+
text-align: center;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.manage-subscription-support a {
|
|
245
|
+
color: var(--color-primary, #4db53f);
|
|
246
|
+
font-weight: 600;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.manage-subscription-primary {
|
|
250
|
+
margin-top: 24px;
|
|
251
|
+
width: 100%;
|
|
252
|
+
min-height: 60px;
|
|
253
|
+
border: 0;
|
|
254
|
+
border-radius: 10px;
|
|
255
|
+
background: var(--color-secondary, #f28100);
|
|
256
|
+
color: var(--color-surface, #fff);
|
|
257
|
+
font-size: 34px;
|
|
258
|
+
font-weight: 700;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.manage-subscription-primary:disabled {
|
|
263
|
+
opacity: 0.55;
|
|
264
|
+
cursor: default;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.manage-subscription-secondary {
|
|
268
|
+
margin-top: 12px;
|
|
269
|
+
width: 100%;
|
|
270
|
+
min-height: 52px;
|
|
271
|
+
border: 1px solid var(--color-border, #dce2ff);
|
|
272
|
+
border-radius: 10px;
|
|
273
|
+
background: var(--color-surface, #fff);
|
|
274
|
+
color: var(--color-secondary, #f28100);
|
|
275
|
+
font-size: 16px;
|
|
276
|
+
font-weight: 600;
|
|
277
|
+
cursor: pointer;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.manage-subscription-reason-list {
|
|
281
|
+
margin: 20px 0 0;
|
|
282
|
+
padding: 0;
|
|
283
|
+
list-style: none;
|
|
284
|
+
display: grid;
|
|
285
|
+
gap: 10px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.manage-subscription-reason {
|
|
289
|
+
width: 100%;
|
|
290
|
+
border: 0;
|
|
291
|
+
border-radius: 14px;
|
|
292
|
+
background: var(--color-surface, #fff);
|
|
293
|
+
padding: 14px 16px;
|
|
294
|
+
display: grid;
|
|
295
|
+
grid-template-columns: auto 1fr auto;
|
|
296
|
+
align-items: center;
|
|
297
|
+
gap: 12px;
|
|
298
|
+
color: var(--color-text, #262729);
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.manage-subscription-reason-icon {
|
|
303
|
+
font-size: 28px;
|
|
304
|
+
line-height: 1;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.manage-subscription-reason-label {
|
|
308
|
+
font-size: 17px;
|
|
309
|
+
font-weight: 600;
|
|
310
|
+
line-height: 1.35;
|
|
311
|
+
text-align: left;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.manage-subscription-reason-arrow {
|
|
315
|
+
font-size: 44px;
|
|
316
|
+
color: var(--color-primary, #4db53f);
|
|
317
|
+
line-height: 0.7;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.manage-subscription-confirm-copy {
|
|
321
|
+
margin: 22px 0 0;
|
|
322
|
+
color: var(--color-text-muted, #5f6b74);
|
|
323
|
+
font-size: 18px;
|
|
324
|
+
line-height: 1.45;
|
|
325
|
+
text-align: center;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.manage-subscription-confirm-meta {
|
|
329
|
+
margin: 10px 0 0;
|
|
330
|
+
color: var(--color-text, #262729);
|
|
331
|
+
font-size: 16px;
|
|
332
|
+
text-align: center;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.manage-subscription-muted,
|
|
336
|
+
.manage-subscription-empty {
|
|
337
|
+
margin: 0;
|
|
338
|
+
color: var(--color-text-muted, #5f6b74);
|
|
339
|
+
font-size: 17px;
|
|
340
|
+
text-align: center;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.manage-subscription-error {
|
|
344
|
+
margin: 14px 0 0;
|
|
345
|
+
color: var(--color-danger, #c74b43);
|
|
346
|
+
font-size: 14px;
|
|
347
|
+
text-align: center;
|
|
348
|
+
}
|
|
349
|
+
`;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FunnelUser } from './FunnelContext.js';
|
|
2
|
+
import { type PaywallStateOptions } from '../services/funnel-state.service.js';
|
|
3
|
+
import type { RuntimeMode } from '../services/runtime-mode.service.js';
|
|
4
|
+
export type RuntimeDevInfoValue = {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string | number | boolean | null | undefined;
|
|
7
|
+
source?: string;
|
|
8
|
+
};
|
|
9
|
+
type RuntimeDevInfoBoxProps = {
|
|
10
|
+
runtimeMode: RuntimeMode;
|
|
11
|
+
user: Pick<FunnelUser, 'id' | 'email'>;
|
|
12
|
+
onSwitchToLiveMode: () => void;
|
|
13
|
+
configValues?: readonly RuntimeDevInfoValue[];
|
|
14
|
+
initialConfigOpen?: boolean;
|
|
15
|
+
initialExpanded?: boolean;
|
|
16
|
+
paywallStateOptions?: PaywallStateOptions;
|
|
17
|
+
reloadOnReset?: boolean;
|
|
18
|
+
title?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare const copyRuntimeDevInfoValue: (value: string) => Promise<boolean>;
|
|
21
|
+
export declare function RuntimeDevInfoBox({ runtimeMode, user, onSwitchToLiveMode, configValues, initialConfigOpen, initialExpanded, paywallStateOptions, reloadOnReset, title, }: RuntimeDevInfoBoxProps): import("react/jsx-runtime").JSX.Element | null;
|
|
22
|
+
export declare const runtimeDevInfoBoxStyles = "\n.runtime-dev-info {\n position: fixed;\n right: 16px;\n bottom: 16px;\n z-index: 1400;\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.runtime-dev-info-help 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.runtime-dev-info-help code {\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.runtime-dev-info-help code {\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(3, 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.runtime-dev-info-help {\n display: grid;\n gap: 6px;\n border-top: 1px solid rgb(255 255 255 / 10%);\n padding: 14px 20px 18px;\n color: #94a3b8;\n font-size: 12px;\n line-height: 1.45;\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
|
+
export {};
|