@churnkey/react 0.6.4 → 0.7.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/{chunk-WTFB6T53.cjs → chunk-4ZAJYXG2.cjs} +7 -6
- package/dist/chunk-4ZAJYXG2.cjs.map +1 -0
- package/dist/{chunk-4WYZSTRI.js → chunk-BODEVZ3K.js} +189 -10
- package/dist/chunk-BODEVZ3K.js.map +1 -0
- package/dist/{chunk-EYIGZLVT.js → chunk-GH5EKETT.js} +6 -3
- package/dist/chunk-GH5EKETT.js.map +1 -0
- package/dist/{chunk-OC5H64F4.js → chunk-KS3RWFRH.js} +4 -3
- package/dist/chunk-KS3RWFRH.js.map +1 -0
- package/dist/{chunk-WVSNF3TN.cjs → chunk-M6Z3BHEH.cjs} +6 -2
- package/dist/chunk-M6Z3BHEH.cjs.map +1 -0
- package/dist/{chunk-WGS432VT.cjs → chunk-SWEPUHIN.cjs} +192 -9
- package/dist/chunk-SWEPUHIN.cjs.map +1 -0
- package/dist/core.cjs +30 -14
- package/dist/core.d.cts +22 -6
- package/dist/core.d.ts +22 -6
- package/dist/core.js +2 -2
- package/dist/headless.cjs +3 -3
- package/dist/headless.d.cts +4 -1
- package/dist/headless.d.ts +4 -1
- package/dist/headless.js +2 -2
- package/dist/index.cjs +235 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +136 -78
- package/dist/index.js.map +1 -1
- package/dist/{step-graph-BpoAhd0d.d.cts → step-graph-s1L1wV-P.d.cts} +184 -2
- package/dist/{step-graph-BpoAhd0d.d.ts → step-graph-s1L1wV-P.d.ts} +184 -2
- package/dist/styles.css +13 -0
- package/package.json +1 -1
- package/dist/chunk-4WYZSTRI.js.map +0 -1
- package/dist/chunk-EYIGZLVT.js.map +0 -1
- package/dist/chunk-OC5H64F4.js.map +0 -1
- package/dist/chunk-WGS432VT.cjs.map +0 -1
- package/dist/chunk-WTFB6T53.cjs.map +0 -1
- package/dist/chunk-WVSNF3TN.cjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkSWEPUHIN_cjs = require('./chunk-SWEPUHIN.cjs');
|
|
4
4
|
var react = require('react');
|
|
5
5
|
|
|
6
6
|
function useCancelFlowMachine(config) {
|
|
@@ -25,7 +25,7 @@ function useCancelFlowMachine(config) {
|
|
|
25
25
|
onClose: () => cb.current.onClose?.(),
|
|
26
26
|
onStepChange: (step, prevStep) => cb.current.onStepChange?.(step, prevStep)
|
|
27
27
|
};
|
|
28
|
-
return new
|
|
28
|
+
return new chunkSWEPUHIN_cjs.CancelFlowMachine({ ...config, ...dispatch });
|
|
29
29
|
});
|
|
30
30
|
const [state, setState] = react.useState(() => machine.getSnapshot());
|
|
31
31
|
const [isLoading, setIsLoading] = react.useState(!!config.session);
|
|
@@ -39,8 +39,8 @@ function useCancelFlowMachine(config) {
|
|
|
39
39
|
setLoadError(null);
|
|
40
40
|
setIsLoading(true);
|
|
41
41
|
let cancelled = false;
|
|
42
|
-
const creds =
|
|
43
|
-
const api = new
|
|
42
|
+
const creds = chunkSWEPUHIN_cjs.decodeSessionToken(config.session);
|
|
43
|
+
const api = new chunkSWEPUHIN_cjs.ChurnkeyApi(creds, config.apiBaseUrl);
|
|
44
44
|
api.fetchConfig(callbacksRef.current.customerAttributes).then((sdkConfig) => {
|
|
45
45
|
if (cancelled) return;
|
|
46
46
|
machine.initializeFromConfig(sdkConfig, api, creds);
|
|
@@ -69,6 +69,7 @@ function useCancelFlow(config) {
|
|
|
69
69
|
isLoading,
|
|
70
70
|
loadError,
|
|
71
71
|
retry,
|
|
72
|
+
messages: machine.messages,
|
|
72
73
|
reasons: machine.reasons,
|
|
73
74
|
currentStep: machine.currentStep,
|
|
74
75
|
currentOffer: machine.currentOffer,
|
|
@@ -88,5 +89,5 @@ function useCancelFlow(config) {
|
|
|
88
89
|
|
|
89
90
|
exports.useCancelFlow = useCancelFlow;
|
|
90
91
|
exports.useCancelFlowMachine = useCancelFlowMachine;
|
|
91
|
-
//# sourceMappingURL=chunk-
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
92
|
+
//# sourceMappingURL=chunk-4ZAJYXG2.cjs.map
|
|
93
|
+
//# sourceMappingURL=chunk-4ZAJYXG2.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/headless/use-cancel-flow-machine.ts","../src/headless/use-cancel-flow.ts"],"names":["useRef","useState","CancelFlowMachine","useEffect","useCallback","decodeSessionToken","ChurnkeyApi"],"mappings":"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","file":"chunk-4ZAJYXG2.cjs","sourcesContent":["// Internal — shared by `useCancelFlow` (headless) and the `CancelFlow`\n// component. Lives under headless/ since it's the headless layer of the\n// SDK; the component imports it because there's no separate public/private\n// split worth maintaining for one shared file.\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { ChurnkeyApi } from '../core/api'\nimport { CancelFlowMachine } from '../core/machine'\nimport { decodeSessionToken } from '../core/token'\nimport type { FlowCallbacks, FlowConfig, FlowState } from '../core/types'\n\nexport interface CancelFlowMachineHandle {\n machine: CancelFlowMachine\n state: FlowState\n isLoading: boolean\n loadError: Error | null\n /** Re-fetch the flow config (token mode only). No-op without a session. */\n retry: () => void\n}\n\n/**\n * Wires a CancelFlowMachine to React lifecycle. Used internally by both the\n * `CancelFlow` component and the `useCancelFlow` hook — not part of the\n * public API.\n *\n * Callbacks reach the machine via thunks that dereference a ref updated each\n * render. This buys two things: the consumer's latest closure always runs,\n * and the fetch effect's dep list stays stable so inline-arrow handlers\n * don't trigger a re-fetch and reset the flow to step 1.\n */\nexport function useCancelFlowMachine(config: FlowConfig): CancelFlowMachineHandle {\n // FlowConfig extends FlowCallbacks, so storing the whole config gives us\n // access to every callback by name without a separate copy.\n const callbacksRef = useRef(config)\n callbacksRef.current = config\n\n const [machine] = useState(() => {\n const cb = callbacksRef\n const dispatch: FlowCallbacks = {\n handleDiscount: (o, c) => cb.current.handleDiscount?.(o, c),\n handlePause: (o, c) => cb.current.handlePause?.(o, c),\n handlePlanChange: (o, c) => cb.current.handlePlanChange?.(o, c),\n handleTrialExtension: (o, c) => cb.current.handleTrialExtension?.(o, c),\n handleRebate: (o, c) => cb.current.handleRebate?.(o, c),\n handleCancel: (c) => cb.current.handleCancel?.(c),\n onAccept: (o, c) => cb.current.onAccept?.(o, c),\n onDiscount: (o, c) => cb.current.onDiscount?.(o, c),\n onPause: (o, c) => cb.current.onPause?.(o, c),\n onPlanChange: (o, c) => cb.current.onPlanChange?.(o, c),\n onTrialExtension: (o, c) => cb.current.onTrialExtension?.(o, c),\n onRebate: (o, c) => cb.current.onRebate?.(o, c),\n onCancel: (c) => cb.current.onCancel?.(c),\n onClose: () => cb.current.onClose?.(),\n onStepChange: (step, prevStep) => cb.current.onStepChange?.(step, prevStep),\n }\n return new CancelFlowMachine({ ...config, ...dispatch })\n })\n\n const [state, setState] = useState<FlowState>(() => machine.getSnapshot())\n const [isLoading, setIsLoading] = useState(!!config.session)\n const [loadError, setLoadError] = useState<Error | null>(null)\n\n useEffect(() => {\n setState(machine.getSnapshot())\n return machine.subscribe(() => setState(machine.getSnapshot()))\n }, [machine])\n\n const loadConfig = useCallback(() => {\n if (!config.session) return\n setLoadError(null)\n setIsLoading(true)\n let cancelled = false\n const creds = decodeSessionToken(config.session)\n const api = new ChurnkeyApi(creds, config.apiBaseUrl)\n api\n // Read through the ref, not the closure: an inline `customerAttributes`\n // object literal would otherwise join the dep list and re-fetch (and\n // reset the flow) on every render.\n .fetchConfig(callbacksRef.current.customerAttributes)\n .then((sdkConfig) => {\n if (cancelled) return\n machine.initializeFromConfig(sdkConfig, api, creds)\n setIsLoading(false)\n })\n .catch((err) => {\n if (cancelled) return\n setLoadError(err instanceof Error ? err : new Error(String(err)))\n setIsLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [config.session, config.apiBaseUrl, machine])\n\n useEffect(() => {\n if (!config.session) return\n return loadConfig()\n }, [config.session, loadConfig])\n\n return { machine, state, isLoading, loadError, retry: loadConfig }\n}\n","import type { FlowConfig } from '../core/types'\nimport { useCancelFlowMachine } from './use-cancel-flow-machine'\n\nexport function useCancelFlow(config: FlowConfig) {\n const { machine, state, isLoading, loadError, retry } = useCancelFlowMachine(config)\n\n return {\n ...state,\n isLoading,\n loadError,\n retry,\n messages: machine.messages,\n reasons: machine.reasons,\n currentStep: machine.currentStep,\n currentOffer: machine.currentOffer,\n stepIndex: machine.stepIndex,\n totalSteps: machine.totalSteps,\n selectReason: machine.selectReason,\n setFollowupResponse: machine.setFollowupResponse,\n setFeedback: machine.setFeedback,\n accept: machine.accept,\n decline: machine.decline,\n cancel: machine.cancel,\n next: machine.next,\n back: machine.back,\n close: machine.close,\n }\n}\n"]}
|
|
@@ -1,3 +1,153 @@
|
|
|
1
|
+
// src/core/messages.ts
|
|
2
|
+
var defaultMessages = {
|
|
3
|
+
common: {
|
|
4
|
+
continue: "Continue",
|
|
5
|
+
back: "Back",
|
|
6
|
+
close: "Close",
|
|
7
|
+
done: "Done",
|
|
8
|
+
processing: "Processing...",
|
|
9
|
+
tryAgain: "Try again",
|
|
10
|
+
loading: "Loading your options...",
|
|
11
|
+
loadError: "We couldn't load your cancellation options. Please try again.",
|
|
12
|
+
error: "Something went wrong. Please try again.",
|
|
13
|
+
day: "day",
|
|
14
|
+
days: "days",
|
|
15
|
+
month: "month",
|
|
16
|
+
months: "months"
|
|
17
|
+
},
|
|
18
|
+
survey: {
|
|
19
|
+
title: "Why are you cancelling?",
|
|
20
|
+
followupPlaceholder: "Tell us more (optional)",
|
|
21
|
+
followupAriaLabel: "Additional detail"
|
|
22
|
+
},
|
|
23
|
+
feedback: {
|
|
24
|
+
title: "Any other feedback?",
|
|
25
|
+
placeholder: "Type your thoughts\u2026",
|
|
26
|
+
placeholderWithMin: "At least {minLength} characters\u2026"
|
|
27
|
+
},
|
|
28
|
+
confirm: {
|
|
29
|
+
title: "Confirm cancellation",
|
|
30
|
+
lossesLabel: "You'll lose access to:",
|
|
31
|
+
cta: "Cancel subscription",
|
|
32
|
+
goBack: "Go back",
|
|
33
|
+
periodEndNotice: {
|
|
34
|
+
immediate: "",
|
|
35
|
+
atPeriodEnd: "Your access continues until {periodEnd}."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
success: {
|
|
39
|
+
// Per-offer defaults mirror the embed's *Applied keys so the two
|
|
40
|
+
// surfaces confirm the same outcome with the same words.
|
|
41
|
+
saved: {
|
|
42
|
+
title: "Welcome back!",
|
|
43
|
+
description: "Your offer has been applied.",
|
|
44
|
+
discount: { title: "Discount applied.", description: "We're so happy you're still here." },
|
|
45
|
+
pause: {
|
|
46
|
+
title: "Subscription paused.",
|
|
47
|
+
description: "You won't be billed until your subscription resumes.",
|
|
48
|
+
resumeDescription: "You won't be billed again until {resumeDate}."
|
|
49
|
+
},
|
|
50
|
+
trial_extension: { title: "Trial extended.", description: "Your trial has been extended successfully." },
|
|
51
|
+
plan_change: { title: "Plan changed.", description: "Your new plan is now in effect." },
|
|
52
|
+
rebate: { title: "Refund issued.", description: "Your money's on its way back." }
|
|
53
|
+
},
|
|
54
|
+
cancelled: {
|
|
55
|
+
title: "Subscription cancelled",
|
|
56
|
+
description: "We're sorry to see you go."
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
offer: {
|
|
60
|
+
limitedTimeEyebrow: "Limited-time offer",
|
|
61
|
+
pauseEyebrow: "We'll see you back on",
|
|
62
|
+
newEndDateLabel: "New end date",
|
|
63
|
+
currentPlanBadge: "Current",
|
|
64
|
+
switchToCta: "Switch to {planName}",
|
|
65
|
+
rebate: {
|
|
66
|
+
paidLabel: "You paid this period",
|
|
67
|
+
moneyBackLabel: "Money back",
|
|
68
|
+
inclTax: "(incl. {amount} tax)",
|
|
69
|
+
netLabel: "Your net for this period"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function isTimingVariants(value) {
|
|
74
|
+
return typeof value === "object" && value !== null && ("immediate" in value || "atPeriodEnd" in value) && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
function selectTiming(value, cancelAtPeriodEnd) {
|
|
77
|
+
if (typeof value === "string") return value;
|
|
78
|
+
return cancelAtPeriodEnd ?? true ? value.atPeriodEnd : value.immediate;
|
|
79
|
+
}
|
|
80
|
+
function formatMessage(template, vars) {
|
|
81
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
82
|
+
const value = vars[name];
|
|
83
|
+
return value == null ? match : String(value);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
var TIMING_AWARE_PATHS = /* @__PURE__ */ new Set([
|
|
87
|
+
"confirm.cta",
|
|
88
|
+
"confirm.periodEndNotice",
|
|
89
|
+
"success.cancelled.title",
|
|
90
|
+
"success.cancelled.description"
|
|
91
|
+
]);
|
|
92
|
+
function mergeValue(base, patch, path) {
|
|
93
|
+
if (typeof base === "string" || isTimingVariants(base)) {
|
|
94
|
+
if (typeof patch === "string") return patch === "" ? base : patch;
|
|
95
|
+
if (isTimingVariants(patch) && TIMING_AWARE_PATHS.has(path)) {
|
|
96
|
+
const result = typeof base === "string" ? { immediate: base, atPeriodEnd: base } : { immediate: "", atPeriodEnd: "", ...base };
|
|
97
|
+
let applied = false;
|
|
98
|
+
if (typeof patch.immediate === "string" && patch.immediate) {
|
|
99
|
+
result.immediate = patch.immediate;
|
|
100
|
+
applied = true;
|
|
101
|
+
}
|
|
102
|
+
if (typeof patch.atPeriodEnd === "string" && patch.atPeriodEnd) {
|
|
103
|
+
result.atPeriodEnd = patch.atPeriodEnd;
|
|
104
|
+
applied = true;
|
|
105
|
+
}
|
|
106
|
+
return applied ? result : base;
|
|
107
|
+
}
|
|
108
|
+
return base;
|
|
109
|
+
}
|
|
110
|
+
if (typeof base === "object" && base !== null && typeof patch === "object" && patch !== null && !Array.isArray(patch) && !isTimingVariants(patch)) {
|
|
111
|
+
const merged = { ...base };
|
|
112
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
113
|
+
if (value == null || !(key in merged)) continue;
|
|
114
|
+
merged[key] = mergeValue(merged[key], value, path ? `${path}.${key}` : key);
|
|
115
|
+
}
|
|
116
|
+
return merged;
|
|
117
|
+
}
|
|
118
|
+
return base;
|
|
119
|
+
}
|
|
120
|
+
function mergeMessages(base, patch) {
|
|
121
|
+
if (!patch) return base;
|
|
122
|
+
return mergeValue(base, patch, "");
|
|
123
|
+
}
|
|
124
|
+
function resolveLocale(explicit) {
|
|
125
|
+
if (explicit) return explicit;
|
|
126
|
+
if (typeof navigator !== "undefined" && navigator.language) return navigator.language;
|
|
127
|
+
return "en";
|
|
128
|
+
}
|
|
129
|
+
function localeChain(locale) {
|
|
130
|
+
const exact = locale.toLowerCase();
|
|
131
|
+
const base = exact.split("-")[0];
|
|
132
|
+
const chain = ["en"];
|
|
133
|
+
if (base !== "en") chain.push(base);
|
|
134
|
+
if (exact !== base) chain.push(exact);
|
|
135
|
+
return chain;
|
|
136
|
+
}
|
|
137
|
+
function applyLocaleLayer(base, byLang, chain) {
|
|
138
|
+
if (!byLang) return base;
|
|
139
|
+
const byLowerKey = new Map(Object.entries(byLang).map(([k, v]) => [k.toLowerCase(), v]));
|
|
140
|
+
let resolved = base;
|
|
141
|
+
for (const lang of chain) {
|
|
142
|
+
resolved = mergeMessages(resolved, byLowerKey.get(lang));
|
|
143
|
+
}
|
|
144
|
+
return resolved;
|
|
145
|
+
}
|
|
146
|
+
function buildMessages(i18n, orgMessages) {
|
|
147
|
+
const chain = localeChain(resolveLocale(i18n?.locale));
|
|
148
|
+
return applyLocaleLayer(applyLocaleLayer(defaultMessages, orgMessages, chain), i18n?.messages, chain);
|
|
149
|
+
}
|
|
150
|
+
|
|
1
151
|
// src/core/utils.ts
|
|
2
152
|
var BUILT_IN_STEP_TYPES = ["survey", "offer", "feedback", "confirm", "success"];
|
|
3
153
|
var BUILT_IN_OFFER_TYPES = [
|
|
@@ -64,9 +214,9 @@ function appearanceToStyle(appearance) {
|
|
|
64
214
|
return Object.keys(style).length > 0 ? style : void 0;
|
|
65
215
|
}
|
|
66
216
|
var defaultTitles = {
|
|
67
|
-
survey:
|
|
68
|
-
feedback:
|
|
69
|
-
confirm:
|
|
217
|
+
survey: defaultMessages.survey.title,
|
|
218
|
+
feedback: defaultMessages.feedback.title,
|
|
219
|
+
confirm: defaultMessages.confirm.title
|
|
70
220
|
};
|
|
71
221
|
|
|
72
222
|
// src/core/api.ts
|
|
@@ -752,6 +902,9 @@ var CancelFlowMachine = class {
|
|
|
752
902
|
presentedOffers = [];
|
|
753
903
|
customStepResults = {};
|
|
754
904
|
configMode = "live";
|
|
905
|
+
i18nConfig;
|
|
906
|
+
localCancelAtPeriodEnd = null;
|
|
907
|
+
resolvedMessages;
|
|
755
908
|
stepEnteredAt = Date.now();
|
|
756
909
|
aborted = false;
|
|
757
910
|
// Visited-step stack. `back` pops this so it lands on the actually-seen
|
|
@@ -760,6 +913,11 @@ var CancelFlowMachine = class {
|
|
|
760
913
|
history = [];
|
|
761
914
|
constructor(config) {
|
|
762
915
|
this.callbacks = config;
|
|
916
|
+
this.i18nConfig = config.i18n;
|
|
917
|
+
this.resolvedMessages = buildMessages(config.i18n);
|
|
918
|
+
if (!config.session && typeof config.cancelAtPeriodEnd === "boolean") {
|
|
919
|
+
this.localCancelAtPeriodEnd = config.cancelAtPeriodEnd;
|
|
920
|
+
}
|
|
763
921
|
if (config.mode) this.configMode = config.mode;
|
|
764
922
|
if (config.customerAttributes) this.customerAttributes = config.customerAttributes;
|
|
765
923
|
if (config.appId && config.customer) {
|
|
@@ -800,6 +958,14 @@ var CancelFlowMachine = class {
|
|
|
800
958
|
get currentOffer() {
|
|
801
959
|
return this.currentStep?.offer ?? null;
|
|
802
960
|
}
|
|
961
|
+
/**
|
|
962
|
+
* The fully resolved message catalog (defaults layered with the config's
|
|
963
|
+
* `i18n` overrides). Timing-aware values are still unresolved — pick a
|
|
964
|
+
* variant with `selectTiming(value, state.cancelAtPeriodEnd)`.
|
|
965
|
+
*/
|
|
966
|
+
get messages() {
|
|
967
|
+
return this.resolvedMessages;
|
|
968
|
+
}
|
|
803
969
|
/** Whether `back()` would move anywhere — false on the first step and on success. */
|
|
804
970
|
get canGoBack() {
|
|
805
971
|
if (this.state.step === "success") return false;
|
|
@@ -867,7 +1033,7 @@ var CancelFlowMachine = class {
|
|
|
867
1033
|
if (listener) await runListener(listener, acceptedOffer, customer);
|
|
868
1034
|
await this.callbacks.onAccept?.(acceptedOffer, customer);
|
|
869
1035
|
this.markCurrentOfferAccepted();
|
|
870
|
-
this.enterSuccessStep("saved");
|
|
1036
|
+
this.enterSuccessStep("saved", acceptedOffer);
|
|
871
1037
|
this.recordOutcome("saved", offer, safeResult);
|
|
872
1038
|
} catch (error) {
|
|
873
1039
|
this.setState({ isProcessing: false, error });
|
|
@@ -916,6 +1082,9 @@ var CancelFlowMachine = class {
|
|
|
916
1082
|
this.apiClient = apiClient;
|
|
917
1083
|
this.creds = creds;
|
|
918
1084
|
this.config = config;
|
|
1085
|
+
if (config.translations) {
|
|
1086
|
+
this.resolvedMessages = buildMessages(this.i18nConfig, config.translations);
|
|
1087
|
+
}
|
|
919
1088
|
const result = transformSdkConfig(config);
|
|
920
1089
|
this.blueprintId = result.blueprintId;
|
|
921
1090
|
const merged = this.localSteps ? mergeLocalSteps(result.steps, this.localSteps) : result.steps;
|
|
@@ -949,7 +1118,9 @@ var CancelFlowMachine = class {
|
|
|
949
1118
|
isProcessing: false,
|
|
950
1119
|
error: null,
|
|
951
1120
|
customer,
|
|
952
|
-
subscriptions
|
|
1121
|
+
subscriptions,
|
|
1122
|
+
cancelAtPeriodEnd: this.config?.settings.cancelAtPeriodEnd ?? this.localCancelAtPeriodEnd,
|
|
1123
|
+
acceptedOffer: null
|
|
953
1124
|
};
|
|
954
1125
|
}
|
|
955
1126
|
// On a survey step with a selected reason that has an attached offer,
|
|
@@ -1075,9 +1246,17 @@ var CancelFlowMachine = class {
|
|
|
1075
1246
|
// Terminal transition. Prefer moving currentStepId to a declared success
|
|
1076
1247
|
// step so the developer's savedTitle / classNames / custom component
|
|
1077
1248
|
// applies; otherwise stay put and the renderer's defaults cover it.
|
|
1078
|
-
|
|
1249
|
+
// The accepted offer is snapshotted into state here because currentOffer
|
|
1250
|
+
// derives from currentStepId, which a declared success step moves off the
|
|
1251
|
+
// offer — per-offer success copy needs a stable source.
|
|
1252
|
+
enterSuccessStep(outcome, acceptedOffer) {
|
|
1079
1253
|
const success = this.getStepConfig("success");
|
|
1080
|
-
const partial = {
|
|
1254
|
+
const partial = {
|
|
1255
|
+
step: "success",
|
|
1256
|
+
outcome,
|
|
1257
|
+
isProcessing: false,
|
|
1258
|
+
acceptedOffer: acceptedOffer ?? null
|
|
1259
|
+
};
|
|
1081
1260
|
if (success) partial.currentStepId = success.guid;
|
|
1082
1261
|
this.setState(partial);
|
|
1083
1262
|
}
|
|
@@ -1206,6 +1385,6 @@ function decodeSessionToken(token) {
|
|
|
1206
1385
|
};
|
|
1207
1386
|
}
|
|
1208
1387
|
|
|
1209
|
-
export { AnalyticsClient, BUILT_IN_OFFER_TYPES, BUILT_IN_STEP_TYPES, CancelFlowMachine, ChurnkeyApi, appearanceToStyle, cn, decodeSessionToken, defaultTitles };
|
|
1210
|
-
//# sourceMappingURL=chunk-
|
|
1211
|
-
//# sourceMappingURL=chunk-
|
|
1388
|
+
export { AnalyticsClient, BUILT_IN_OFFER_TYPES, BUILT_IN_STEP_TYPES, CancelFlowMachine, ChurnkeyApi, appearanceToStyle, buildMessages, cn, decodeSessionToken, defaultMessages, defaultTitles, formatMessage, selectTiming };
|
|
1389
|
+
//# sourceMappingURL=chunk-BODEVZ3K.js.map
|
|
1390
|
+
//# sourceMappingURL=chunk-BODEVZ3K.js.map
|