@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.
Files changed (35) hide show
  1. package/dist/{chunk-H5IHOYUF.cjs → chunk-ER6YITYC.cjs} +202 -9
  2. package/dist/chunk-ER6YITYC.cjs.map +1 -0
  3. package/dist/{chunk-W47U2HRC.cjs → chunk-FK4AJOX6.cjs} +7 -6
  4. package/dist/chunk-FK4AJOX6.cjs.map +1 -0
  5. package/dist/{chunk-EYIGZLVT.js → chunk-GH5EKETT.js} +6 -3
  6. package/dist/chunk-GH5EKETT.js.map +1 -0
  7. package/dist/{chunk-XHXG43PW.js → chunk-GNRS7V7H.js} +4 -3
  8. package/dist/chunk-GNRS7V7H.js.map +1 -0
  9. package/dist/{chunk-WVSNF3TN.cjs → chunk-M6Z3BHEH.cjs} +6 -2
  10. package/dist/chunk-M6Z3BHEH.cjs.map +1 -0
  11. package/dist/{chunk-F6IP3LGK.js → chunk-RFKW3LWM.js} +199 -10
  12. package/dist/chunk-RFKW3LWM.js.map +1 -0
  13. package/dist/core.cjs +30 -14
  14. package/dist/core.d.cts +22 -6
  15. package/dist/core.d.ts +22 -6
  16. package/dist/core.js +2 -2
  17. package/dist/headless.cjs +3 -3
  18. package/dist/headless.d.cts +4 -1
  19. package/dist/headless.d.ts +4 -1
  20. package/dist/headless.js +2 -2
  21. package/dist/index.cjs +238 -163
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +15 -15
  24. package/dist/index.d.ts +15 -15
  25. package/dist/index.js +135 -76
  26. package/dist/index.js.map +1 -1
  27. package/dist/{step-graph-Dee2cFLK.d.cts → step-graph-C7JC8Umv.d.cts} +199 -1
  28. package/dist/{step-graph-Dee2cFLK.d.ts → step-graph-C7JC8Umv.d.ts} +199 -1
  29. package/package.json +1 -1
  30. package/dist/chunk-EYIGZLVT.js.map +0 -1
  31. package/dist/chunk-F6IP3LGK.js.map +0 -1
  32. package/dist/chunk-H5IHOYUF.cjs.map +0 -1
  33. package/dist/chunk-W47U2HRC.cjs.map +0 -1
  34. package/dist/chunk-WVSNF3TN.cjs.map +0 -1
  35. package/dist/chunk-XHXG43PW.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { useCancelFlowMachine } from './chunk-XHXG43PW.js';
2
- export { useCancelFlow } from './chunk-XHXG43PW.js';
3
- import { discountPhrase, formatMonthDayLong, formatPriceFromMinor, formatMonthDay } from './chunk-EYIGZLVT.js';
4
- export { calculateDiscountedPrice, formatPeriodEnd, formatPrice } from './chunk-EYIGZLVT.js';
5
- import { cn, appearanceToStyle, defaultTitles, BUILT_IN_OFFER_TYPES } from './chunk-F6IP3LGK.js';
6
- export { AnalyticsClient, BUILT_IN_OFFER_TYPES, BUILT_IN_STEP_TYPES, CancelFlowMachine, ChurnkeyApi, appearanceToStyle, cn, decodeSessionToken, defaultTitles } from './chunk-F6IP3LGK.js';
1
+ import { useCancelFlowMachine } from './chunk-GNRS7V7H.js';
2
+ export { useCancelFlow } from './chunk-GNRS7V7H.js';
3
+ import { discountPhrase, formatMonthDayLong, formatPriceFromMinor, formatMonthDay, formatShortDate, formatPeriodEnd } from './chunk-GH5EKETT.js';
4
+ export { calculateDiscountedPrice, formatPeriodEnd, formatPrice } from './chunk-GH5EKETT.js';
5
+ import { cn, defaultMessages, formatMessage, appearanceToStyle, selectTiming, BUILT_IN_OFFER_TYPES } from './chunk-RFKW3LWM.js';
6
+ export { AnalyticsClient, BUILT_IN_OFFER_TYPES, BUILT_IN_STEP_TYPES, CancelFlowMachine, ChurnkeyApi, appearanceToStyle, buildMessages, cn, decodeSessionToken, defaultMessages, defaultTitles, formatMessage, selectTiming } from './chunk-RFKW3LWM.js';
7
7
  import { useState, useRef, useEffect, useCallback } from 'react';
8
8
  import { jsx, jsxs } from 'react/jsx-runtime';
9
9
 
@@ -25,22 +25,26 @@ function DefaultConfirm({
25
25
  lossesLabel,
26
26
  confirmLabel,
27
27
  goBackLabel,
28
+ periodEndNotice,
28
29
  onConfirm,
29
30
  onGoBack,
30
31
  isProcessing,
31
- classNames
32
+ classNames,
33
+ messages
32
34
  }) {
35
+ const m = messages ?? defaultMessages;
33
36
  const hasLosses = Array.isArray(losses) && losses.length > 0;
34
37
  return /* @__PURE__ */ jsxs("div", { className: cn("ck-step ck-step-confirm", classNames?.root), children: [
35
38
  /* @__PURE__ */ jsx("h2", { className: cn("ck-step-title", classNames?.title), children: title }),
36
39
  description && /* @__PURE__ */ jsx(RichText, { as: "div", html: description, className: cn("ck-step-description", classNames?.description) }),
37
40
  hasLosses && /* @__PURE__ */ jsxs("div", { className: cn("ck-loss-block", classNames?.lossList), children: [
38
- /* @__PURE__ */ jsx("div", { className: cn("ck-loss-label", classNames?.lossLabel), children: lossesLabel ?? "You'll lose access to:" }),
41
+ /* @__PURE__ */ jsx("div", { className: cn("ck-loss-label", classNames?.lossLabel), children: lossesLabel ?? m.confirm.lossesLabel }),
39
42
  /* @__PURE__ */ jsx("ul", { className: "ck-loss-list", children: losses.map((item) => /* @__PURE__ */ jsxs("li", { className: cn("ck-loss-item", classNames?.lossItem), children: [
40
43
  /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn("ck-loss-bullet", classNames?.lossBullet), children: "\u25CF" }),
41
44
  /* @__PURE__ */ jsx("span", { children: item })
42
45
  ] }, item)) })
43
46
  ] }),
47
+ periodEndNotice && /* @__PURE__ */ jsx("p", { className: cn("ck-period-end", classNames?.periodEndNotice), children: periodEndNotice }),
44
48
  /* @__PURE__ */ jsx(
45
49
  "button",
46
50
  {
@@ -48,7 +52,7 @@ function DefaultConfirm({
48
52
  className: cn("ck-button ck-button-danger", classNames?.confirmButton),
49
53
  onClick: onConfirm,
50
54
  disabled: isProcessing,
51
- children: isProcessing ? "Processing..." : confirmLabel
55
+ children: isProcessing ? m.common.processing : confirmLabel
52
56
  }
53
57
  ),
54
58
  /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.goBackButton), onClick: onGoBack, children: goBackLabel })
@@ -63,13 +67,15 @@ function DefaultFeedback({
63
67
  value,
64
68
  onChange,
65
69
  onSubmit,
66
- classNames
70
+ classNames,
71
+ messages
67
72
  }) {
73
+ const m = messages ?? defaultMessages;
68
74
  const [focused, setFocused] = useState(false);
69
75
  const hasMin = minLength > 0;
70
76
  const isUnderMin = hasMin && value.length > 0 && value.length < minLength;
71
77
  const isValid = !required || value.length >= minLength;
72
- const placeholderText = placeholder ?? (hasMin ? `At least ${minLength} characters\u2026` : "Type your thoughts\u2026");
78
+ const placeholderText = placeholder ?? (hasMin ? formatMessage(m.feedback.placeholderWithMin, { minLength }) : m.feedback.placeholder);
73
79
  return /* @__PURE__ */ jsxs("div", { className: cn("ck-step ck-step-feedback", classNames?.root), children: [
74
80
  /* @__PURE__ */ jsx("h2", { className: cn("ck-step-title", classNames?.title), children: title }),
75
81
  description && /* @__PURE__ */ jsx(RichText, { as: "div", html: description, className: cn("ck-step-description", classNames?.description) }),
@@ -119,7 +125,7 @@ function DefaultFeedback({
119
125
  className: cn("ck-button ck-button-primary", classNames?.submitButton),
120
126
  onClick: onSubmit,
121
127
  disabled: !isValid,
122
- children: "Continue"
128
+ children: m.common.continue
123
129
  }
124
130
  )
125
131
  ] });
@@ -131,8 +137,10 @@ function DefaultContactOffer({
131
137
  onAccept,
132
138
  onDecline,
133
139
  isProcessing,
134
- classNames
140
+ classNames,
141
+ messages
135
142
  }) {
143
+ const msg = messages ?? defaultMessages;
136
144
  const headline = title ?? offer.copy.headline;
137
145
  const body = description ?? offer.copy.body;
138
146
  return /* @__PURE__ */ jsxs("div", { className: cn("ck-step ck-step-offer", classNames?.root), children: [
@@ -146,10 +154,10 @@ function DefaultContactOffer({
146
154
  className: cn("ck-button ck-button-primary", classNames?.acceptButton),
147
155
  onClick: () => onAccept(),
148
156
  disabled: isProcessing,
149
- children: isProcessing ? "Processing..." : offer.copy.cta
157
+ children: isProcessing ? msg.common.processing : msg.offer.acceptCta.contact || offer.copy.cta
150
158
  }
151
159
  ),
152
- /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: offer.copy.declineCta })
160
+ /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: msg.offer.declineCta || offer.copy.declineCta })
153
161
  ] })
154
162
  ] });
155
163
  }
@@ -160,8 +168,10 @@ function DefaultDiscountOffer({
160
168
  onAccept,
161
169
  onDecline,
162
170
  isProcessing,
163
- classNames
171
+ classNames,
172
+ messages
164
173
  }) {
174
+ const msg = messages ?? defaultMessages;
165
175
  const o = offer;
166
176
  const headline = title ?? offer.copy.headline;
167
177
  const body = description ?? offer.copy.body;
@@ -171,7 +181,7 @@ function DefaultDiscountOffer({
171
181
  body && /* @__PURE__ */ jsx(RichText, { as: "div", html: body, className: cn("ck-step-description", classNames?.description) }),
172
182
  /* @__PURE__ */ jsxs("div", { className: cn("ck-offer-card", classNames?.card), children: [
173
183
  /* @__PURE__ */ jsxs("div", { className: "ck-offer-details ck-offer-discount", children: [
174
- /* @__PURE__ */ jsx("div", { className: "ck-offer-discount-eyebrow", children: "Limited-time offer" }),
184
+ /* @__PURE__ */ jsx("div", { className: "ck-offer-discount-eyebrow", children: msg.offer.limitedTimeEyebrow }),
175
185
  /* @__PURE__ */ jsx("div", { className: "ck-offer-discount-phrase", children: phrase })
176
186
  ] }),
177
187
  /* @__PURE__ */ jsx(
@@ -181,10 +191,10 @@ function DefaultDiscountOffer({
181
191
  className: cn("ck-button ck-button-primary", classNames?.acceptButton),
182
192
  onClick: () => onAccept(),
183
193
  disabled: isProcessing,
184
- children: isProcessing ? "Processing..." : offer.copy.cta
194
+ children: isProcessing ? msg.common.processing : msg.offer.acceptCta.discount || offer.copy.cta
185
195
  }
186
196
  ),
187
- /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: offer.copy.declineCta })
197
+ /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: msg.offer.declineCta || offer.copy.declineCta })
188
198
  ] })
189
199
  ] });
190
200
  }
@@ -195,8 +205,10 @@ function DefaultPauseOffer({
195
205
  onAccept,
196
206
  onDecline,
197
207
  isProcessing,
198
- classNames
208
+ classNames,
209
+ messages
199
210
  }) {
211
+ const msg = messages ?? defaultMessages;
200
212
  const o = offer;
201
213
  const max = Math.max(1, o.months);
202
214
  const [months, setMonths] = useState(1);
@@ -209,7 +221,7 @@ function DefaultPauseOffer({
209
221
  headline && /* @__PURE__ */ jsx("h2", { className: cn("ck-step-title", classNames?.title), children: headline }),
210
222
  body && /* @__PURE__ */ jsx(RichText, { as: "div", html: body, className: cn("ck-step-description", classNames?.description) }),
211
223
  /* @__PURE__ */ jsxs("div", { className: cn("ck-offer-card ck-pause-card", classNames?.card), children: [
212
- /* @__PURE__ */ jsx("div", { className: "ck-pause-eyebrow", children: "We'll see you back on" }),
224
+ /* @__PURE__ */ jsx("div", { className: "ck-pause-eyebrow", children: msg.offer.pauseEyebrow }),
213
225
  /* @__PURE__ */ jsx("div", { className: "ck-pause-date", children: resumeDate }),
214
226
  max > 1 && /* @__PURE__ */ jsx("div", { className: cn("ck-pause-chips", classNames?.pauseSlider), children: Array.from({ length: max }, (_, i) => i + 1).map((m) => /* @__PURE__ */ jsxs(
215
227
  "button",
@@ -221,7 +233,7 @@ function DefaultPauseOffer({
221
233
  children: [
222
234
  m,
223
235
  " ",
224
- m === 1 ? "month" : "months"
236
+ m === 1 ? msg.common.month : msg.common.months
225
237
  ]
226
238
  },
227
239
  m
@@ -234,10 +246,10 @@ function DefaultPauseOffer({
234
246
  className: cn("ck-button ck-button-primary", classNames?.acceptButton),
235
247
  onClick: () => onAccept({ months }),
236
248
  disabled: isProcessing,
237
- children: isProcessing ? "Processing..." : offer.copy.cta
249
+ children: isProcessing ? msg.common.processing : msg.offer.acceptCta.pause || offer.copy.cta
238
250
  }
239
251
  ),
240
- /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: offer.copy.declineCta })
252
+ /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: msg.offer.declineCta || offer.copy.declineCta })
241
253
  ] });
242
254
  }
243
255
  function Checkmark({ color = "currentColor", size = 14 }) {
@@ -251,8 +263,10 @@ function DefaultPlanChangeOffer({
251
263
  onAccept,
252
264
  onDecline,
253
265
  isProcessing,
254
- classNames
266
+ classNames,
267
+ messages
255
268
  }) {
269
+ const msg = messages ?? defaultMessages;
256
270
  const o = offer;
257
271
  const plans = o.plans ?? [];
258
272
  const currentPlanId = subscriptions[0]?.items[0]?.price.id;
@@ -261,7 +275,7 @@ function DefaultPlanChangeOffer({
261
275
  const selectedPlan = plans.find((p) => p.id === selectedPlanId) ?? null;
262
276
  const headline = title ?? offer.copy.headline;
263
277
  const body = description ?? offer.copy.body;
264
- const ctaLabel = isProcessing ? "Processing..." : selectedPlan?.name ? `Switch to ${selectedPlan.name}` : offer.copy.cta;
278
+ const ctaLabel = isProcessing ? msg.common.processing : selectedPlan?.name ? formatMessage(msg.offer.switchToCta, { planName: selectedPlan.name }) : msg.offer.acceptCta.plan_change || offer.copy.cta;
265
279
  return /* @__PURE__ */ jsxs("div", { className: cn("ck-step ck-step-offer", classNames?.root), children: [
266
280
  headline && /* @__PURE__ */ jsx("h2", { className: cn("ck-step-title", classNames?.title), children: headline }),
267
281
  body && /* @__PURE__ */ jsx(RichText, { as: "div", html: body, className: cn("ck-step-description", classNames?.description) }),
@@ -286,7 +300,7 @@ function DefaultPlanChangeOffer({
286
300
  children: [
287
301
  /* @__PURE__ */ jsxs("div", { className: "ck-plan-name", children: [
288
302
  plan.name ?? plan.id,
289
- isCurrent && /* @__PURE__ */ jsx("span", { className: "ck-plan-current-badge", children: "Current" })
303
+ isCurrent && /* @__PURE__ */ jsx("span", { className: "ck-plan-current-badge", children: msg.offer.currentPlanBadge })
290
304
  ] }),
291
305
  plan.tagline && /* @__PURE__ */ jsx("div", { className: "ck-plan-tagline", children: plan.tagline }),
292
306
  /* @__PURE__ */ jsxs("div", { className: "ck-plan-price-row", children: [
@@ -316,7 +330,7 @@ function DefaultPlanChangeOffer({
316
330
  children: ctaLabel
317
331
  }
318
332
  ),
319
- /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: offer.copy.declineCta })
333
+ /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: msg.offer.declineCta || offer.copy.declineCta })
320
334
  ] })
321
335
  ] });
322
336
  }
@@ -327,8 +341,10 @@ function DefaultRebateOffer({
327
341
  onAccept,
328
342
  onDecline,
329
343
  isProcessing,
330
- classNames
344
+ classNames,
345
+ messages
331
346
  }) {
347
+ const msg = messages ?? defaultMessages;
332
348
  const o = offer;
333
349
  const headline = title ?? offer.copy.headline;
334
350
  const body = description ?? offer.copy.body;
@@ -342,16 +358,15 @@ function DefaultRebateOffer({
342
358
  /* @__PURE__ */ jsxs("div", { className: cn("ck-offer-card", classNames?.card), children: [
343
359
  /* @__PURE__ */ jsxs("div", { className: "ck-offer-rebate", children: [
344
360
  o.amountPaidMinor != null && /* @__PURE__ */ jsxs("div", { className: "ck-offer-rebate-row", children: [
345
- /* @__PURE__ */ jsx("span", { children: "You paid this period" }),
361
+ /* @__PURE__ */ jsx("span", { children: msg.offer.rebate.paidLabel }),
346
362
  /* @__PURE__ */ jsx("span", { children: formatPriceFromMinor(o.amountPaidMinor, currency) })
347
363
  ] }),
348
364
  /* @__PURE__ */ jsxs("div", { className: "ck-offer-rebate-row ck-offer-rebate-credit", children: [
349
365
  /* @__PURE__ */ jsxs("span", { children: [
350
- "Money back",
366
+ msg.offer.rebate.moneyBackLabel,
351
367
  taxRefunded > 0 && /* @__PURE__ */ jsxs("span", { className: "ck-offer-rebate-tax", children: [
352
- " (incl. ",
353
- formatPriceFromMinor(taxRefunded, currency),
354
- " tax)"
368
+ " ",
369
+ formatMessage(msg.offer.rebate.inclTax, { amount: formatPriceFromMinor(taxRefunded, currency) })
355
370
  ] })
356
371
  ] }),
357
372
  /* @__PURE__ */ jsxs("span", { children: [
@@ -360,7 +375,7 @@ function DefaultRebateOffer({
360
375
  ] })
361
376
  ] }),
362
377
  o.netAfterRebateMinor != null && /* @__PURE__ */ jsxs("div", { className: "ck-offer-rebate-row ck-offer-rebate-total", children: [
363
- /* @__PURE__ */ jsx("span", { children: "Your net for this period" }),
378
+ /* @__PURE__ */ jsx("span", { children: msg.offer.rebate.netLabel }),
364
379
  /* @__PURE__ */ jsx("span", { children: formatPriceFromMinor(o.netAfterRebateMinor, currency) })
365
380
  ] })
366
381
  ] }),
@@ -371,14 +386,15 @@ function DefaultRebateOffer({
371
386
  className: cn("ck-button ck-button-primary", classNames?.acceptButton),
372
387
  onClick: () => onAccept(),
373
388
  disabled: isProcessing,
374
- children: isProcessing ? "Processing..." : offer.copy.cta
389
+ children: isProcessing ? msg.common.processing : msg.offer.acceptCta.rebate || offer.copy.cta
375
390
  }
376
391
  ),
377
- /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: offer.copy.declineCta })
392
+ /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: msg.offer.declineCta || offer.copy.declineCta })
378
393
  ] })
379
394
  ] });
380
395
  }
381
- function DefaultRedirectOffer({ title, description, offer, onDecline, classNames }) {
396
+ function DefaultRedirectOffer({ title, description, offer, onDecline, classNames, messages }) {
397
+ const msg = messages ?? defaultMessages;
382
398
  const url = offer.url;
383
399
  const headline = title ?? offer.copy.headline;
384
400
  const body = description ?? offer.copy.body;
@@ -387,10 +403,10 @@ function DefaultRedirectOffer({ title, description, offer, onDecline, classNames
387
403
  body && /* @__PURE__ */ jsx(RichText, { as: "div", html: body, className: cn("ck-step-description", classNames?.description) }),
388
404
  /* @__PURE__ */ jsxs("div", { className: cn("ck-offer-card", classNames?.card), children: [
389
405
  /* @__PURE__ */ jsx("div", { className: "ck-offer-details", children: /* @__PURE__ */ jsxs("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "ck-redirect-link", children: [
390
- /* @__PURE__ */ jsx("span", { children: offer.copy.cta }),
406
+ /* @__PURE__ */ jsx("span", { children: msg.offer.acceptCta.redirect || offer.copy.cta }),
391
407
  /* @__PURE__ */ jsx(ExternalIcon, {})
392
408
  ] }) }),
393
- /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: offer.copy.declineCta })
409
+ /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: msg.offer.declineCta || offer.copy.declineCta })
394
410
  ] })
395
411
  ] });
396
412
  }
@@ -423,8 +439,10 @@ function DefaultTrialExtensionOffer({
423
439
  onAccept,
424
440
  onDecline,
425
441
  isProcessing,
426
- classNames
442
+ classNames,
443
+ messages
427
444
  }) {
445
+ const msg = messages ?? defaultMessages;
428
446
  const o = offer;
429
447
  const headline = title ?? offer.copy.headline;
430
448
  const body = description ?? offer.copy.body;
@@ -441,10 +459,10 @@ function DefaultTrialExtensionOffer({
441
459
  "+",
442
460
  o.days
443
461
  ] }),
444
- /* @__PURE__ */ jsx("div", { className: "ck-trial-unit", children: o.days === 1 ? "day" : "days" })
462
+ /* @__PURE__ */ jsx("div", { className: "ck-trial-unit", children: o.days === 1 ? msg.common.day : msg.common.days })
445
463
  ] }),
446
464
  /* @__PURE__ */ jsxs("div", { children: [
447
- /* @__PURE__ */ jsx("div", { className: "ck-trial-end-label", children: "New end date" }),
465
+ /* @__PURE__ */ jsx("div", { className: "ck-trial-end-label", children: msg.offer.newEndDateLabel }),
448
466
  /* @__PURE__ */ jsx("div", { className: "ck-trial-end-date", children: newEnd })
449
467
  ] })
450
468
  ] }),
@@ -455,10 +473,10 @@ function DefaultTrialExtensionOffer({
455
473
  className: cn("ck-button ck-button-primary", classNames?.acceptButton),
456
474
  onClick: () => onAccept(),
457
475
  disabled: isProcessing,
458
- children: isProcessing ? "Processing..." : offer.copy.cta
476
+ children: isProcessing ? msg.common.processing : msg.offer.acceptCta.trial_extension || offer.copy.cta
459
477
  }
460
478
  ),
461
- /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: offer.copy.declineCta })
479
+ /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button-link", classNames?.declineButton), onClick: onDecline, children: msg.offer.declineCta || offer.copy.declineCta })
462
480
  ] })
463
481
  ] });
464
482
  }
@@ -494,12 +512,13 @@ function pickOfferComponent(type, components) {
494
512
  return null;
495
513
  }
496
514
  }
497
- function DefaultSuccess({ title, description, onClose, classNames }) {
515
+ function DefaultSuccess({ title, description, onClose, classNames, messages }) {
516
+ const m = messages ?? defaultMessages;
498
517
  return /* @__PURE__ */ jsxs("div", { className: cn("ck-step ck-step-success", classNames?.root), children: [
499
518
  /* @__PURE__ */ jsx("div", { className: cn("ck-success-icon", classNames?.icon), children: /* @__PURE__ */ jsx(Checkmark, { color: "currentColor", size: 26 }) }),
500
519
  /* @__PURE__ */ jsx("h2", { className: cn("ck-step-title", classNames?.title), children: title }),
501
520
  description && /* @__PURE__ */ jsx(RichText, { as: "div", html: description, className: cn("ck-step-description", classNames?.description) }),
502
- /* @__PURE__ */ jsx("div", { className: "ck-success-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button ck-button-primary", classNames?.closeButton), onClick: onClose, children: "Done" }) })
521
+ /* @__PURE__ */ jsx("div", { className: "ck-success-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-button ck-button-primary", classNames?.closeButton), onClick: onClose, children: m.common.done }) })
503
522
  ] });
504
523
  }
505
524
  function DefaultReasonButton({ reason, index, isSelected, onSelect }) {
@@ -529,8 +548,10 @@ function DefaultSurvey({
529
548
  onFollowupResponseChange,
530
549
  onNext,
531
550
  classNames,
532
- components
551
+ components,
552
+ messages
533
553
  }) {
554
+ const m = messages ?? defaultMessages;
534
555
  const ReasonButton = components?.ReasonButton ?? DefaultReasonButton;
535
556
  const selected = reasons.find((r) => r.id === selectedReason);
536
557
  const showFollowup = selected?.freeform === true;
@@ -551,11 +572,11 @@ function DefaultSurvey({
551
572
  "textarea",
552
573
  {
553
574
  className: cn("ck-reason-followup", classNames?.followupInput),
554
- placeholder: "Tell us more (optional)",
575
+ placeholder: m.survey.followupPlaceholder,
555
576
  rows: 3,
556
577
  value: followupResponse,
557
578
  onChange: (e) => onFollowupResponseChange(e.target.value),
558
- "aria-label": "Additional detail"
579
+ "aria-label": m.survey.followupAriaLabel
559
580
  }
560
581
  ),
561
582
  /* @__PURE__ */ jsx(
@@ -565,12 +586,12 @@ function DefaultSurvey({
565
586
  className: cn("ck-button ck-button-primary", classNames?.continueButton),
566
587
  onClick: onNext,
567
588
  disabled: !selectedReason,
568
- children: "Continue"
589
+ children: m.common.continue
569
590
  }
570
591
  )
571
592
  ] });
572
593
  }
573
- function DefaultBackButton({ onBack, className }) {
594
+ function DefaultBackButton({ onBack, className, label }) {
574
595
  return /* @__PURE__ */ jsxs("button", { type: "button", className: cn("ck-back-button", className), onClick: onBack, children: [
575
596
  /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
576
597
  "path",
@@ -582,11 +603,11 @@ function DefaultBackButton({ onBack, className }) {
582
603
  strokeLinejoin: "round"
583
604
  }
584
605
  ) }),
585
- /* @__PURE__ */ jsx("span", { children: "Back" })
606
+ /* @__PURE__ */ jsx("span", { children: label ?? "Back" })
586
607
  ] });
587
608
  }
588
- function DefaultCloseButton({ onClose, className }) {
589
- return /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-close-button", className), onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) });
609
+ function DefaultCloseButton({ onClose, className, label }) {
610
+ return /* @__PURE__ */ jsx("button", { type: "button", className: cn("ck-close-button", className), onClick: onClose, "aria-label": label ?? "Close", children: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) });
590
611
  }
591
612
  function trapFocus(container) {
592
613
  const focusableSelector = [
@@ -695,7 +716,8 @@ function CancelFlow(props) {
695
716
  onClose: props.onClose,
696
717
  isLoading,
697
718
  loadError,
698
- onRetry: retry
719
+ onRetry: retry,
720
+ messages: machine.messages
699
721
  }
700
722
  );
701
723
  }
@@ -718,7 +740,8 @@ function LoadStatus({
718
740
  onClose,
719
741
  isLoading,
720
742
  loadError,
721
- onRetry
743
+ onRetry,
744
+ messages
722
745
  }) {
723
746
  const scheme = useColorScheme(appearance?.colorScheme);
724
747
  const appearanceStyle = appearanceToStyle(appearance);
@@ -727,7 +750,7 @@ function LoadStatus({
727
750
  const handleClose = onClose ?? (() => {
728
751
  });
729
752
  return /* @__PURE__ */ jsx("div", { className: "ck-cancel-flow", "data-color-scheme": scheme, style: appearanceStyle, children: /* @__PURE__ */ jsxs(Modal, { open: true, onClose: handleClose, className: classNames?.modal, overlayClassName: classNames?.overlay, children: [
730
- /* @__PURE__ */ jsx(CloseButton, { onClose: handleClose, className: classNames?.closeButton }),
753
+ /* @__PURE__ */ jsx(CloseButton, { onClose: handleClose, className: classNames?.closeButton, label: messages.common.close }),
731
754
  /* @__PURE__ */ jsxs("div", { className: "ck-content", children: [
732
755
  isLoading && /* @__PURE__ */ jsxs("div", { className: "ck-loading", style: { padding: "32px", textAlign: "center" }, children: [
733
756
  /* @__PURE__ */ jsx(
@@ -745,10 +768,10 @@ function LoadStatus({
745
768
  }
746
769
  }
747
770
  ),
748
- /* @__PURE__ */ jsx("p", { style: { color: "var(--ck-color-text-secondary, #6b7280)" }, children: "Loading your options..." })
771
+ /* @__PURE__ */ jsx("p", { style: { color: "var(--ck-color-text-secondary, #6b7280)" }, children: messages.common.loading })
749
772
  ] }),
750
773
  loadError && /* @__PURE__ */ jsxs("div", { className: "ck-error", role: "alert", style: { padding: "32px", textAlign: "center" }, children: [
751
- /* @__PURE__ */ jsx("p", { className: "ck-error-message", style: { marginBottom: 16 }, children: "We couldn't load your cancellation options. Please try again." }),
774
+ /* @__PURE__ */ jsx("p", { className: "ck-error-message", style: { marginBottom: 16 }, children: messages.common.loadError }),
752
775
  /* @__PURE__ */ jsx(
753
776
  "button",
754
777
  {
@@ -765,7 +788,7 @@ function LoadStatus({
765
788
  borderRadius: "var(--ck-border-radius, 8px)",
766
789
  cursor: "pointer"
767
790
  },
768
- children: "Try again"
791
+ children: messages.common.tryAgain
769
792
  }
770
793
  )
771
794
  ] })
@@ -775,14 +798,15 @@ function LoadStatus({
775
798
  function FlowShell({ machine, state, appearance, classNames, components, customComponents }) {
776
799
  const scheme = useColorScheme(appearance?.colorScheme);
777
800
  const appearanceStyle = appearanceToStyle(appearance);
801
+ const messages = machine.messages;
778
802
  const Modal = components?.Modal ?? DefaultModal;
779
803
  const CloseButton = components?.CloseButton ?? DefaultCloseButton;
780
804
  const BackButton = components?.BackButton ?? DefaultBackButton;
781
805
  return /* @__PURE__ */ jsx("div", { className: "ck-cancel-flow", "data-color-scheme": scheme, style: appearanceStyle, children: /* @__PURE__ */ jsxs(Modal, { open: true, onClose: machine.close, className: classNames?.modal, overlayClassName: classNames?.overlay, children: [
782
- /* @__PURE__ */ jsx(CloseButton, { onClose: machine.close, className: classNames?.closeButton }),
806
+ /* @__PURE__ */ jsx(CloseButton, { onClose: machine.close, className: classNames?.closeButton, label: messages.common.close }),
783
807
  /* @__PURE__ */ jsxs("div", { className: "ck-content", children: [
784
- machine.canGoBack && /* @__PURE__ */ jsx(BackButton, { onBack: machine.back, className: classNames?.backButton }),
785
- state.error && /* @__PURE__ */ jsx("div", { className: "ck-error", role: "alert", children: /* @__PURE__ */ jsx("p", { className: "ck-error-message", children: "Something went wrong. Please try again." }) }),
808
+ machine.canGoBack && /* @__PURE__ */ jsx(BackButton, { onBack: machine.back, className: classNames?.backButton, label: messages.common.back }),
809
+ state.error && /* @__PURE__ */ jsx("div", { className: "ck-error", role: "alert", children: /* @__PURE__ */ jsx("p", { className: "ck-error-message", children: messages.common.error }) }),
786
810
  /* @__PURE__ */ jsx(StepRenderer, { state, machine, components, customComponents })
787
811
  ] })
788
812
  ] }) });
@@ -794,6 +818,7 @@ function StepRenderer({
794
818
  customComponents
795
819
  }) {
796
820
  const stepConfig = machine.currentStep;
821
+ const messages = machine.messages;
797
822
  switch (state.step) {
798
823
  case "survey": {
799
824
  const Survey = components?.Survey ?? DefaultSurvey;
@@ -801,7 +826,7 @@ function StepRenderer({
801
826
  return /* @__PURE__ */ jsx(
802
827
  Survey,
803
828
  {
804
- title: config?.title ?? defaultTitles.survey,
829
+ title: config?.title ?? messages.survey.title,
805
830
  description: config?.description,
806
831
  customer: state.customer,
807
832
  subscriptions: state.subscriptions,
@@ -812,7 +837,8 @@ function StepRenderer({
812
837
  onFollowupResponseChange: machine.setFollowupResponse,
813
838
  onNext: machine.next,
814
839
  classNames: config?.classNames,
815
- components
840
+ components,
841
+ messages
816
842
  }
817
843
  );
818
844
  }
@@ -850,7 +876,8 @@ function StepRenderer({
850
876
  onDecline: machine.decline,
851
877
  isProcessing: state.isProcessing,
852
878
  classNames: config?.classNames,
853
- components
879
+ components,
880
+ messages
854
881
  }
855
882
  );
856
883
  }
@@ -860,7 +887,7 @@ function StepRenderer({
860
887
  return /* @__PURE__ */ jsx(
861
888
  Feedback,
862
889
  {
863
- title: config?.title ?? defaultTitles.feedback,
890
+ title: config?.title ?? messages.feedback.title,
864
891
  description: config?.description,
865
892
  customer: state.customer,
866
893
  subscriptions: state.subscriptions,
@@ -870,7 +897,8 @@ function StepRenderer({
870
897
  value: state.feedback,
871
898
  onChange: machine.setFeedback,
872
899
  onSubmit: machine.next,
873
- classNames: config?.classNames
900
+ classNames: config?.classNames,
901
+ messages
874
902
  }
875
903
  );
876
904
  }
@@ -880,18 +908,20 @@ function StepRenderer({
880
908
  return /* @__PURE__ */ jsx(
881
909
  Confirm,
882
910
  {
883
- title: config?.title ?? defaultTitles.confirm,
911
+ title: config?.title ?? messages.confirm.title,
884
912
  description: config?.description,
885
913
  customer: state.customer,
886
914
  subscriptions: state.subscriptions,
887
915
  losses: config?.losses,
888
916
  lossesLabel: config?.lossesLabel,
889
- confirmLabel: config?.confirmLabel ?? "Cancel subscription",
890
- goBackLabel: config?.goBackLabel ?? "Go back",
917
+ confirmLabel: config?.confirmLabel ?? selectTiming(messages.confirm.cta, state.cancelAtPeriodEnd),
918
+ goBackLabel: config?.goBackLabel ?? messages.confirm.goBack,
919
+ periodEndNotice: resolvePeriodEndNotice(state, messages),
891
920
  onConfirm: machine.cancel,
892
921
  onGoBack: machine.back,
893
922
  isProcessing: state.isProcessing,
894
- classNames: config?.classNames
923
+ classNames: config?.classNames,
924
+ messages
895
925
  }
896
926
  );
897
927
  }
@@ -899,17 +929,20 @@ function StepRenderer({
899
929
  const Success = components?.Success ?? DefaultSuccess;
900
930
  const config = stepConfig;
901
931
  const isSaved = state.outcome === "saved";
932
+ const perOffer = isSaved ? savedOfferCopy(messages, state.acceptedOffer) : void 0;
902
933
  return /* @__PURE__ */ jsx(
903
934
  Success,
904
935
  {
905
936
  outcome: state.outcome ?? "cancelled",
906
937
  offer: machine.currentOffer ?? void 0,
907
- title: isSaved ? config?.savedTitle ?? "Welcome back!" : config?.cancelledTitle ?? "Subscription cancelled",
908
- description: isSaved ? config?.savedDescription ?? "Your offer has been applied." : config?.cancelledDescription ?? "We're sorry to see you go.",
938
+ acceptedOffer: state.acceptedOffer ?? void 0,
939
+ title: isSaved ? config?.savedTitle ?? perOffer?.title ?? messages.success.saved.title : config?.cancelledTitle ?? selectTiming(messages.success.cancelled.title, state.cancelAtPeriodEnd),
940
+ description: isSaved ? config?.savedDescription ?? pauseResumeDescription(messages, state.acceptedOffer) ?? perOffer?.description ?? messages.success.saved.description : config?.cancelledDescription ?? selectTiming(messages.success.cancelled.description, state.cancelAtPeriodEnd),
909
941
  customer: state.customer,
910
942
  subscriptions: state.subscriptions,
911
943
  onClose: machine.close,
912
- classNames: config?.classNames
944
+ classNames: config?.classNames,
945
+ messages
913
946
  }
914
947
  );
915
948
  }
@@ -937,6 +970,32 @@ function StepRenderer({
937
970
  }
938
971
  }
939
972
  }
973
+ var SAVED_OFFER_COPY_TYPES = ["discount", "pause", "trial_extension", "plan_change", "rebate"];
974
+ function savedOfferCopy(messages, offer) {
975
+ if (!offer) return void 0;
976
+ if (!SAVED_OFFER_COPY_TYPES.includes(offer.type)) return void 0;
977
+ return messages.success.saved[offer.type];
978
+ }
979
+ function pauseResumeDescription(messages, offer) {
980
+ if (offer?.type !== "pause") return void 0;
981
+ const months = offer.result?.months;
982
+ if (typeof months !== "number" || months < 1) return void 0;
983
+ const resume = /* @__PURE__ */ new Date();
984
+ if (offer.interval === "week") {
985
+ resume.setDate(resume.getDate() + months * 7);
986
+ } else {
987
+ resume.setMonth(resume.getMonth() + months);
988
+ }
989
+ return formatMessage(messages.success.saved.pause.resumeDescription, { resumeDate: formatShortDate(resume) });
990
+ }
991
+ function resolvePeriodEndNotice(state, messages) {
992
+ if (state.cancelAtPeriodEnd !== true) return void 0;
993
+ const template = selectTiming(messages.confirm.periodEndNotice, state.cancelAtPeriodEnd);
994
+ if (!template) return void 0;
995
+ const periodEnd = formatPeriodEnd(state.subscriptions);
996
+ if (!periodEnd) return void 0;
997
+ return formatMessage(template, { periodEnd });
998
+ }
940
999
  function UnregisteredStepFallback({ step, onSkip }) {
941
1000
  useEffect(() => {
942
1001
  console.warn(`[churnkey] No component registered for step type "${step}". Skipping.`);