@churnkey/react 0.6.5 → 0.7.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/dist/{chunk-H5IHOYUF.cjs → chunk-ER6YITYC.cjs} +202 -9
- package/dist/chunk-ER6YITYC.cjs.map +1 -0
- package/dist/{chunk-W47U2HRC.cjs → chunk-FK4AJOX6.cjs} +7 -6
- package/dist/chunk-FK4AJOX6.cjs.map +1 -0
- package/dist/{chunk-EYIGZLVT.js → chunk-GH5EKETT.js} +6 -3
- package/dist/chunk-GH5EKETT.js.map +1 -0
- package/dist/{chunk-XHXG43PW.js → chunk-GNRS7V7H.js} +4 -3
- package/dist/chunk-GNRS7V7H.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-F6IP3LGK.js → chunk-RFKW3LWM.js} +199 -10
- package/dist/chunk-RFKW3LWM.js.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 +238 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -15
- package/dist/index.d.ts +15 -15
- package/dist/index.js +135 -76
- package/dist/index.js.map +1 -1
- package/dist/{step-graph-Dee2cFLK.d.cts → step-graph-C7JC8Umv.d.cts} +199 -1
- package/dist/{step-graph-Dee2cFLK.d.ts → step-graph-C7JC8Umv.d.ts} +199 -1
- package/package.json +1 -1
- package/dist/chunk-EYIGZLVT.js.map +0 -1
- package/dist/chunk-F6IP3LGK.js.map +0 -1
- package/dist/chunk-H5IHOYUF.cjs.map +0 -1
- package/dist/chunk-W47U2HRC.cjs.map +0 -1
- package/dist/chunk-WVSNF3TN.cjs.map +0 -1
- package/dist/chunk-XHXG43PW.js.map +0 -1
|
@@ -1,5 +1,165 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/core/messages.ts
|
|
4
|
+
var defaultMessages = {
|
|
5
|
+
common: {
|
|
6
|
+
continue: "Continue",
|
|
7
|
+
back: "Back",
|
|
8
|
+
close: "Close",
|
|
9
|
+
done: "Done",
|
|
10
|
+
processing: "Processing...",
|
|
11
|
+
tryAgain: "Try again",
|
|
12
|
+
loading: "Loading your options...",
|
|
13
|
+
loadError: "We couldn't load your cancellation options. Please try again.",
|
|
14
|
+
error: "Something went wrong. Please try again.",
|
|
15
|
+
day: "day",
|
|
16
|
+
days: "days",
|
|
17
|
+
month: "month",
|
|
18
|
+
months: "months"
|
|
19
|
+
},
|
|
20
|
+
survey: {
|
|
21
|
+
title: "Why are you cancelling?",
|
|
22
|
+
followupPlaceholder: "Tell us more (optional)",
|
|
23
|
+
followupAriaLabel: "Additional detail"
|
|
24
|
+
},
|
|
25
|
+
feedback: {
|
|
26
|
+
title: "Any other feedback?",
|
|
27
|
+
placeholder: "Type your thoughts\u2026",
|
|
28
|
+
placeholderWithMin: "At least {minLength} characters\u2026"
|
|
29
|
+
},
|
|
30
|
+
confirm: {
|
|
31
|
+
title: "Confirm cancellation",
|
|
32
|
+
lossesLabel: "You'll lose access to:",
|
|
33
|
+
cta: "Cancel subscription",
|
|
34
|
+
goBack: "Go back",
|
|
35
|
+
periodEndNotice: {
|
|
36
|
+
immediate: "",
|
|
37
|
+
atPeriodEnd: "Your access continues until {periodEnd}."
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
success: {
|
|
41
|
+
// Per-offer defaults mirror the embed's *Applied keys so the two
|
|
42
|
+
// surfaces confirm the same outcome with the same words.
|
|
43
|
+
saved: {
|
|
44
|
+
title: "Welcome back!",
|
|
45
|
+
description: "Your offer has been applied.",
|
|
46
|
+
discount: { title: "Discount applied.", description: "We're so happy you're still here." },
|
|
47
|
+
pause: {
|
|
48
|
+
title: "Subscription paused.",
|
|
49
|
+
description: "You won't be billed until your subscription resumes.",
|
|
50
|
+
resumeDescription: "You won't be billed again until {resumeDate}."
|
|
51
|
+
},
|
|
52
|
+
trial_extension: { title: "Trial extended.", description: "Your trial has been extended successfully." },
|
|
53
|
+
plan_change: { title: "Plan changed.", description: "Your new plan is now in effect." },
|
|
54
|
+
rebate: { title: "Refund issued.", description: "Your money's on its way back." }
|
|
55
|
+
},
|
|
56
|
+
cancelled: {
|
|
57
|
+
title: "Subscription cancelled",
|
|
58
|
+
description: "We're sorry to see you go."
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
offer: {
|
|
62
|
+
limitedTimeEyebrow: "Limited-time offer",
|
|
63
|
+
pauseEyebrow: "We'll see you back on",
|
|
64
|
+
newEndDateLabel: "New end date",
|
|
65
|
+
currentPlanBadge: "Current",
|
|
66
|
+
switchToCta: "Switch to {planName}",
|
|
67
|
+
acceptCta: {
|
|
68
|
+
discount: "",
|
|
69
|
+
pause: "",
|
|
70
|
+
trial_extension: "",
|
|
71
|
+
plan_change: "",
|
|
72
|
+
contact: "",
|
|
73
|
+
redirect: "",
|
|
74
|
+
rebate: ""
|
|
75
|
+
},
|
|
76
|
+
declineCta: "",
|
|
77
|
+
rebate: {
|
|
78
|
+
paidLabel: "You paid this period",
|
|
79
|
+
moneyBackLabel: "Money back",
|
|
80
|
+
inclTax: "(incl. {amount} tax)",
|
|
81
|
+
netLabel: "Your net for this period"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
function isTimingVariants(value) {
|
|
86
|
+
return typeof value === "object" && value !== null && ("immediate" in value || "atPeriodEnd" in value) && !Array.isArray(value);
|
|
87
|
+
}
|
|
88
|
+
function selectTiming(value, cancelAtPeriodEnd) {
|
|
89
|
+
if (typeof value === "string") return value;
|
|
90
|
+
return cancelAtPeriodEnd ?? true ? value.atPeriodEnd : value.immediate;
|
|
91
|
+
}
|
|
92
|
+
function formatMessage(template, vars) {
|
|
93
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
94
|
+
const value = vars[name];
|
|
95
|
+
return value == null ? match : String(value);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
var TIMING_AWARE_PATHS = /* @__PURE__ */ new Set([
|
|
99
|
+
"confirm.cta",
|
|
100
|
+
"confirm.periodEndNotice",
|
|
101
|
+
"success.cancelled.title",
|
|
102
|
+
"success.cancelled.description"
|
|
103
|
+
]);
|
|
104
|
+
function mergeValue(base, patch, path) {
|
|
105
|
+
if (typeof base === "string" || isTimingVariants(base)) {
|
|
106
|
+
if (typeof patch === "string") return patch === "" ? base : patch;
|
|
107
|
+
if (isTimingVariants(patch) && TIMING_AWARE_PATHS.has(path)) {
|
|
108
|
+
const result = typeof base === "string" ? { immediate: base, atPeriodEnd: base } : { immediate: "", atPeriodEnd: "", ...base };
|
|
109
|
+
let applied = false;
|
|
110
|
+
if (typeof patch.immediate === "string" && patch.immediate) {
|
|
111
|
+
result.immediate = patch.immediate;
|
|
112
|
+
applied = true;
|
|
113
|
+
}
|
|
114
|
+
if (typeof patch.atPeriodEnd === "string" && patch.atPeriodEnd) {
|
|
115
|
+
result.atPeriodEnd = patch.atPeriodEnd;
|
|
116
|
+
applied = true;
|
|
117
|
+
}
|
|
118
|
+
return applied ? result : base;
|
|
119
|
+
}
|
|
120
|
+
return base;
|
|
121
|
+
}
|
|
122
|
+
if (typeof base === "object" && base !== null && typeof patch === "object" && patch !== null && !Array.isArray(patch) && !isTimingVariants(patch)) {
|
|
123
|
+
const merged = { ...base };
|
|
124
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
125
|
+
if (value == null || !(key in merged)) continue;
|
|
126
|
+
merged[key] = mergeValue(merged[key], value, path ? `${path}.${key}` : key);
|
|
127
|
+
}
|
|
128
|
+
return merged;
|
|
129
|
+
}
|
|
130
|
+
return base;
|
|
131
|
+
}
|
|
132
|
+
function mergeMessages(base, patch) {
|
|
133
|
+
if (!patch) return base;
|
|
134
|
+
return mergeValue(base, patch, "");
|
|
135
|
+
}
|
|
136
|
+
function resolveLocale(explicit) {
|
|
137
|
+
if (explicit) return explicit;
|
|
138
|
+
if (typeof navigator !== "undefined" && navigator.language) return navigator.language;
|
|
139
|
+
return "en";
|
|
140
|
+
}
|
|
141
|
+
function localeChain(locale) {
|
|
142
|
+
const exact = locale.toLowerCase();
|
|
143
|
+
const base = exact.split("-")[0];
|
|
144
|
+
const chain = ["en"];
|
|
145
|
+
if (base !== "en") chain.push(base);
|
|
146
|
+
if (exact !== base) chain.push(exact);
|
|
147
|
+
return chain;
|
|
148
|
+
}
|
|
149
|
+
function applyLocaleLayer(base, byLang, chain) {
|
|
150
|
+
if (!byLang) return base;
|
|
151
|
+
const byLowerKey = new Map(Object.entries(byLang).map(([k, v]) => [k.toLowerCase(), v]));
|
|
152
|
+
let resolved = base;
|
|
153
|
+
for (const lang of chain) {
|
|
154
|
+
resolved = mergeMessages(resolved, byLowerKey.get(lang));
|
|
155
|
+
}
|
|
156
|
+
return resolved;
|
|
157
|
+
}
|
|
158
|
+
function buildMessages(i18n, orgMessages) {
|
|
159
|
+
const chain = localeChain(resolveLocale(i18n?.locale));
|
|
160
|
+
return applyLocaleLayer(applyLocaleLayer(defaultMessages, orgMessages, chain), i18n?.messages, chain);
|
|
161
|
+
}
|
|
162
|
+
|
|
3
163
|
// src/core/utils.ts
|
|
4
164
|
var BUILT_IN_STEP_TYPES = ["survey", "offer", "feedback", "confirm", "success"];
|
|
5
165
|
var BUILT_IN_OFFER_TYPES = [
|
|
@@ -66,9 +226,9 @@ function appearanceToStyle(appearance) {
|
|
|
66
226
|
return Object.keys(style).length > 0 ? style : void 0;
|
|
67
227
|
}
|
|
68
228
|
var defaultTitles = {
|
|
69
|
-
survey:
|
|
70
|
-
feedback:
|
|
71
|
-
confirm:
|
|
229
|
+
survey: defaultMessages.survey.title,
|
|
230
|
+
feedback: defaultMessages.feedback.title,
|
|
231
|
+
confirm: defaultMessages.confirm.title
|
|
72
232
|
};
|
|
73
233
|
|
|
74
234
|
// src/core/api.ts
|
|
@@ -754,6 +914,9 @@ var CancelFlowMachine = class {
|
|
|
754
914
|
presentedOffers = [];
|
|
755
915
|
customStepResults = {};
|
|
756
916
|
configMode = "live";
|
|
917
|
+
i18nConfig;
|
|
918
|
+
localCancelAtPeriodEnd = null;
|
|
919
|
+
resolvedMessages;
|
|
757
920
|
stepEnteredAt = Date.now();
|
|
758
921
|
aborted = false;
|
|
759
922
|
// Visited-step stack. `back` pops this so it lands on the actually-seen
|
|
@@ -762,6 +925,11 @@ var CancelFlowMachine = class {
|
|
|
762
925
|
history = [];
|
|
763
926
|
constructor(config) {
|
|
764
927
|
this.callbacks = config;
|
|
928
|
+
this.i18nConfig = config.i18n;
|
|
929
|
+
this.resolvedMessages = buildMessages(config.i18n);
|
|
930
|
+
if (!config.session && typeof config.cancelAtPeriodEnd === "boolean") {
|
|
931
|
+
this.localCancelAtPeriodEnd = config.cancelAtPeriodEnd;
|
|
932
|
+
}
|
|
765
933
|
if (config.mode) this.configMode = config.mode;
|
|
766
934
|
if (config.customerAttributes) this.customerAttributes = config.customerAttributes;
|
|
767
935
|
if (config.appId && config.customer) {
|
|
@@ -802,6 +970,14 @@ var CancelFlowMachine = class {
|
|
|
802
970
|
get currentOffer() {
|
|
803
971
|
return this.currentStep?.offer ?? null;
|
|
804
972
|
}
|
|
973
|
+
/**
|
|
974
|
+
* The fully resolved message catalog (defaults layered with the config's
|
|
975
|
+
* `i18n` overrides). Timing-aware values are still unresolved — pick a
|
|
976
|
+
* variant with `selectTiming(value, state.cancelAtPeriodEnd)`.
|
|
977
|
+
*/
|
|
978
|
+
get messages() {
|
|
979
|
+
return this.resolvedMessages;
|
|
980
|
+
}
|
|
805
981
|
/** Whether `back()` would move anywhere — false on the first step and on success. */
|
|
806
982
|
get canGoBack() {
|
|
807
983
|
if (this.state.step === "success") return false;
|
|
@@ -869,7 +1045,7 @@ var CancelFlowMachine = class {
|
|
|
869
1045
|
if (listener) await runListener(listener, acceptedOffer, customer);
|
|
870
1046
|
await this.callbacks.onAccept?.(acceptedOffer, customer);
|
|
871
1047
|
this.markCurrentOfferAccepted();
|
|
872
|
-
this.enterSuccessStep("saved");
|
|
1048
|
+
this.enterSuccessStep("saved", acceptedOffer);
|
|
873
1049
|
this.recordOutcome("saved", offer, safeResult);
|
|
874
1050
|
} catch (error) {
|
|
875
1051
|
this.setState({ isProcessing: false, error });
|
|
@@ -918,6 +1094,9 @@ var CancelFlowMachine = class {
|
|
|
918
1094
|
this.apiClient = apiClient;
|
|
919
1095
|
this.creds = creds;
|
|
920
1096
|
this.config = config;
|
|
1097
|
+
if (config.translations) {
|
|
1098
|
+
this.resolvedMessages = buildMessages(this.i18nConfig, config.translations);
|
|
1099
|
+
}
|
|
921
1100
|
const result = transformSdkConfig(config);
|
|
922
1101
|
this.blueprintId = result.blueprintId;
|
|
923
1102
|
const merged = this.localSteps ? mergeLocalSteps(result.steps, this.localSteps) : result.steps;
|
|
@@ -951,7 +1130,9 @@ var CancelFlowMachine = class {
|
|
|
951
1130
|
isProcessing: false,
|
|
952
1131
|
error: null,
|
|
953
1132
|
customer,
|
|
954
|
-
subscriptions
|
|
1133
|
+
subscriptions,
|
|
1134
|
+
cancelAtPeriodEnd: this.config?.settings.cancelAtPeriodEnd ?? this.localCancelAtPeriodEnd,
|
|
1135
|
+
acceptedOffer: null
|
|
955
1136
|
};
|
|
956
1137
|
}
|
|
957
1138
|
// On a survey step with a selected reason that has an attached offer,
|
|
@@ -1077,9 +1258,17 @@ var CancelFlowMachine = class {
|
|
|
1077
1258
|
// Terminal transition. Prefer moving currentStepId to a declared success
|
|
1078
1259
|
// step so the developer's savedTitle / classNames / custom component
|
|
1079
1260
|
// applies; otherwise stay put and the renderer's defaults cover it.
|
|
1080
|
-
|
|
1261
|
+
// The accepted offer is snapshotted into state here because currentOffer
|
|
1262
|
+
// derives from currentStepId, which a declared success step moves off the
|
|
1263
|
+
// offer — per-offer success copy needs a stable source.
|
|
1264
|
+
enterSuccessStep(outcome, acceptedOffer) {
|
|
1081
1265
|
const success = this.getStepConfig("success");
|
|
1082
|
-
const partial = {
|
|
1266
|
+
const partial = {
|
|
1267
|
+
step: "success",
|
|
1268
|
+
outcome,
|
|
1269
|
+
isProcessing: false,
|
|
1270
|
+
acceptedOffer: acceptedOffer ?? null
|
|
1271
|
+
};
|
|
1083
1272
|
if (success) partial.currentStepId = success.guid;
|
|
1084
1273
|
this.setState(partial);
|
|
1085
1274
|
}
|
|
@@ -1214,8 +1403,12 @@ exports.BUILT_IN_STEP_TYPES = BUILT_IN_STEP_TYPES;
|
|
|
1214
1403
|
exports.CancelFlowMachine = CancelFlowMachine;
|
|
1215
1404
|
exports.ChurnkeyApi = ChurnkeyApi;
|
|
1216
1405
|
exports.appearanceToStyle = appearanceToStyle;
|
|
1406
|
+
exports.buildMessages = buildMessages;
|
|
1217
1407
|
exports.cn = cn;
|
|
1218
1408
|
exports.decodeSessionToken = decodeSessionToken;
|
|
1409
|
+
exports.defaultMessages = defaultMessages;
|
|
1219
1410
|
exports.defaultTitles = defaultTitles;
|
|
1220
|
-
|
|
1221
|
-
|
|
1411
|
+
exports.formatMessage = formatMessage;
|
|
1412
|
+
exports.selectTiming = selectTiming;
|
|
1413
|
+
//# sourceMappingURL=chunk-ER6YITYC.cjs.map
|
|
1414
|
+
//# sourceMappingURL=chunk-ER6YITYC.cjs.map
|