@churnkey/react 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ZEYUKGAR.cjs → chunk-2VQNWZWO.cjs} +33 -23
- package/dist/chunk-2VQNWZWO.cjs.map +1 -0
- package/dist/{chunk-YSNMTOAW.cjs → chunk-42SJA62X.cjs} +9 -7
- package/dist/chunk-42SJA62X.cjs.map +1 -0
- package/dist/{chunk-EHZJAQQ5.cjs → chunk-LVUDBCQ2.cjs} +20 -2
- package/dist/chunk-LVUDBCQ2.cjs.map +1 -0
- package/dist/{chunk-SXGORJOX.js → chunk-SQ35G4LA.js} +19 -3
- package/dist/chunk-SQ35G4LA.js.map +1 -0
- package/dist/{chunk-OL5HQUE4.js → chunk-UNSSI6KX.js} +33 -23
- package/dist/chunk-UNSSI6KX.js.map +1 -0
- package/dist/{chunk-CPCWJTSG.js → chunk-YZERGV7F.js} +6 -4
- package/dist/chunk-YZERGV7F.js.map +1 -0
- package/dist/core.cjs +20 -12
- package/dist/core.d.cts +14 -3
- package/dist/core.d.ts +14 -3
- package/dist/core.js +2 -2
- package/dist/headless.cjs +3 -3
- package/dist/headless.d.cts +5 -1
- package/dist/headless.d.ts +5 -1
- package/dist/headless.js +2 -2
- package/dist/index.cjs +166 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +73 -50
- package/dist/index.js.map +1 -1
- package/dist/{step-graph-Cbi0DRHX.d.cts → step-graph-ChlSCUGk.d.cts} +39 -19
- package/dist/{step-graph-Cbi0DRHX.d.ts → step-graph-ChlSCUGk.d.ts} +39 -19
- package/dist/styles.css +50 -11
- package/package.json +10 -10
- package/dist/chunk-CPCWJTSG.js.map +0 -1
- package/dist/chunk-EHZJAQQ5.cjs.map +0 -1
- package/dist/chunk-OL5HQUE4.js.map +0 -1
- package/dist/chunk-SXGORJOX.js.map +0 -1
- package/dist/chunk-YSNMTOAW.cjs.map +0 -1
- package/dist/chunk-ZEYUKGAR.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useCancelFlowMachine } from './chunk-
|
|
2
|
-
export { useCancelFlow } from './chunk-
|
|
3
|
-
import { cn, discountPhrase, formatMonthDayLong, formatPriceFromMinor, formatMonthDay, appearanceToStyle, defaultTitles } from './chunk-
|
|
4
|
-
export { BUILT_IN_STEP_TYPES, appearanceToStyle, calculateDiscountedPrice, cn, defaultTitles, formatPrice } from './chunk-
|
|
5
|
-
export { AnalyticsClient, CancelFlowMachine, ChurnkeyApi, decodeSessionToken } from './chunk-
|
|
6
|
-
import { useState, useRef,
|
|
1
|
+
import { useCancelFlowMachine } from './chunk-YZERGV7F.js';
|
|
2
|
+
export { useCancelFlow } from './chunk-YZERGV7F.js';
|
|
3
|
+
import { formatPeriodEnd, cn, discountPhrase, formatMonthDayLong, formatPriceFromMinor, formatMonthDay, appearanceToStyle, defaultTitles, BUILT_IN_OFFER_TYPES } from './chunk-SQ35G4LA.js';
|
|
4
|
+
export { BUILT_IN_OFFER_TYPES, BUILT_IN_STEP_TYPES, appearanceToStyle, calculateDiscountedPrice, cn, defaultTitles, formatPeriodEnd, formatPrice } from './chunk-SQ35G4LA.js';
|
|
5
|
+
export { AnalyticsClient, CancelFlowMachine, ChurnkeyApi, decodeSessionToken } from './chunk-UNSSI6KX.js';
|
|
6
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
7
7
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
8
8
|
|
|
9
9
|
function RichText({ html, as = "p", className }) {
|
|
@@ -20,17 +20,18 @@ function RichText({ html, as = "p", className }) {
|
|
|
20
20
|
function DefaultConfirm({
|
|
21
21
|
title,
|
|
22
22
|
description,
|
|
23
|
+
subscriptions,
|
|
23
24
|
losses,
|
|
24
25
|
lossesLabel,
|
|
25
26
|
confirmLabel,
|
|
26
27
|
goBackLabel,
|
|
27
|
-
periodEnd,
|
|
28
28
|
onConfirm,
|
|
29
29
|
onGoBack,
|
|
30
30
|
isProcessing,
|
|
31
31
|
classNames
|
|
32
32
|
}) {
|
|
33
33
|
const hasLosses = Array.isArray(losses) && losses.length > 0;
|
|
34
|
+
const periodEnd = formatPeriodEnd(subscriptions);
|
|
34
35
|
return /* @__PURE__ */ jsxs("div", { className: cn("ck-step ck-step-confirm", classNames?.root), children: [
|
|
35
36
|
/* @__PURE__ */ jsx("h2", { className: cn("ck-step-title", classNames?.title), children: title }),
|
|
36
37
|
description && /* @__PURE__ */ jsx(RichText, { html: description, className: cn("ck-step-description", classNames?.description) }),
|
|
@@ -251,6 +252,7 @@ function Checkmark({ color = "currentColor", size = 14 }) {
|
|
|
251
252
|
function DefaultPlanChangeOffer({
|
|
252
253
|
title,
|
|
253
254
|
description,
|
|
255
|
+
subscriptions,
|
|
254
256
|
offer,
|
|
255
257
|
onAccept,
|
|
256
258
|
onDecline,
|
|
@@ -259,7 +261,9 @@ function DefaultPlanChangeOffer({
|
|
|
259
261
|
}) {
|
|
260
262
|
const o = offer;
|
|
261
263
|
const plans = o.plans ?? [];
|
|
262
|
-
const
|
|
264
|
+
const currentPlanId = subscriptions[0]?.items[0]?.price.id;
|
|
265
|
+
const initialPlanId = plans.find((p) => p.id !== currentPlanId)?.id ?? null;
|
|
266
|
+
const [selectedPlanId, setSelectedPlanId] = useState(initialPlanId);
|
|
263
267
|
const selectedPlan = plans.find((p) => p.id === selectedPlanId) ?? null;
|
|
264
268
|
const headline = title ?? offer.copy.headline;
|
|
265
269
|
const body = description ?? offer.copy.body;
|
|
@@ -272,15 +276,24 @@ function DefaultPlanChangeOffer({
|
|
|
272
276
|
const interval = plan.duration?.interval ?? "month";
|
|
273
277
|
const currency = plan.amount.currency ?? "USD";
|
|
274
278
|
const isSelected = plan.id === selectedPlanId;
|
|
279
|
+
const isCurrent = plan.id === currentPlanId;
|
|
275
280
|
return /* @__PURE__ */ jsxs(
|
|
276
281
|
"button",
|
|
277
282
|
{
|
|
278
283
|
type: "button",
|
|
279
284
|
onClick: () => setSelectedPlanId(plan.id),
|
|
280
|
-
|
|
285
|
+
disabled: isCurrent,
|
|
286
|
+
className: cn(
|
|
287
|
+
"ck-plan-card",
|
|
288
|
+
isSelected && "ck-plan-card--selected",
|
|
289
|
+
isCurrent && "ck-plan-card--current"
|
|
290
|
+
),
|
|
281
291
|
"aria-pressed": isSelected,
|
|
282
292
|
children: [
|
|
283
|
-
/* @__PURE__ */
|
|
293
|
+
/* @__PURE__ */ jsxs("div", { className: "ck-plan-name", children: [
|
|
294
|
+
plan.name ?? plan.id,
|
|
295
|
+
isCurrent && /* @__PURE__ */ jsx("span", { className: "ck-plan-current-badge", children: "Current" })
|
|
296
|
+
] }),
|
|
284
297
|
plan.tagline && /* @__PURE__ */ jsx("div", { className: "ck-plan-tagline", children: plan.tagline }),
|
|
285
298
|
/* @__PURE__ */ jsxs("div", { className: "ck-plan-price-row", children: [
|
|
286
299
|
/* @__PURE__ */ jsx("span", { className: "ck-plan-amount", children: formatPriceFromMinor(plan.amount.value, currency) }),
|
|
@@ -443,7 +456,6 @@ function DefaultReasonButton({ reason, index, isSelected, onSelect }) {
|
|
|
443
456
|
type: "button",
|
|
444
457
|
role: "radio",
|
|
445
458
|
"aria-checked": isSelected,
|
|
446
|
-
tabIndex: isSelected ? 0 : -1,
|
|
447
459
|
onClick: () => onSelect(reason.id),
|
|
448
460
|
className: cn("ck-reason-button", isSelected && "ck-reason-button--selected"),
|
|
449
461
|
children: [
|
|
@@ -459,50 +471,37 @@ function DefaultSurvey({
|
|
|
459
471
|
reasons,
|
|
460
472
|
selectedReason,
|
|
461
473
|
onSelectReason,
|
|
474
|
+
followupResponse,
|
|
475
|
+
onFollowupResponseChange,
|
|
462
476
|
onNext,
|
|
463
477
|
classNames,
|
|
464
478
|
components
|
|
465
479
|
}) {
|
|
466
480
|
const ReasonButton = components?.ReasonButton ?? DefaultReasonButton;
|
|
467
|
-
const
|
|
468
|
-
const
|
|
469
|
-
(e) => {
|
|
470
|
-
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
|
|
471
|
-
e.preventDefault();
|
|
472
|
-
const currentIdx = reasons.findIndex((r) => r.id === selectedReason);
|
|
473
|
-
let nextIdx;
|
|
474
|
-
if (e.key === "ArrowDown") {
|
|
475
|
-
nextIdx = currentIdx < reasons.length - 1 ? currentIdx + 1 : 0;
|
|
476
|
-
} else {
|
|
477
|
-
nextIdx = currentIdx > 0 ? currentIdx - 1 : reasons.length - 1;
|
|
478
|
-
}
|
|
479
|
-
onSelectReason(reasons[nextIdx].id);
|
|
480
|
-
const buttons = listRef.current?.querySelectorAll('[role="radio"]');
|
|
481
|
-
buttons?.[nextIdx]?.focus();
|
|
482
|
-
},
|
|
483
|
-
[reasons, selectedReason, onSelectReason]
|
|
484
|
-
);
|
|
481
|
+
const selected = reasons.find((r) => r.id === selectedReason);
|
|
482
|
+
const showFollowup = selected?.freeform === true;
|
|
485
483
|
return /* @__PURE__ */ jsxs("div", { className: cn("ck-step ck-step-survey", classNames?.root), children: [
|
|
486
484
|
/* @__PURE__ */ jsx("h2", { className: cn("ck-step-title", classNames?.title), children: title }),
|
|
487
485
|
description && /* @__PURE__ */ jsx(RichText, { html: description, className: cn("ck-step-description", classNames?.description) }),
|
|
488
|
-
/* @__PURE__ */ jsx(
|
|
489
|
-
|
|
486
|
+
/* @__PURE__ */ jsx("div", { className: cn("ck-reason-list", classNames?.reasonList), role: "radiogroup", "aria-label": title, children: reasons.map((reason, i) => /* @__PURE__ */ jsx(
|
|
487
|
+
ReasonButton,
|
|
490
488
|
{
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
))
|
|
489
|
+
reason,
|
|
490
|
+
index: i,
|
|
491
|
+
isSelected: selectedReason === reason.id,
|
|
492
|
+
onSelect: onSelectReason
|
|
493
|
+
},
|
|
494
|
+
reason.id
|
|
495
|
+
)) }),
|
|
496
|
+
showFollowup && /* @__PURE__ */ jsx(
|
|
497
|
+
"textarea",
|
|
498
|
+
{
|
|
499
|
+
className: cn("ck-reason-followup", classNames?.followupInput),
|
|
500
|
+
placeholder: "Tell us more (optional)",
|
|
501
|
+
rows: 3,
|
|
502
|
+
value: followupResponse,
|
|
503
|
+
onChange: (e) => onFollowupResponseChange(e.target.value),
|
|
504
|
+
"aria-label": "Additional detail"
|
|
506
505
|
}
|
|
507
506
|
),
|
|
508
507
|
/* @__PURE__ */ jsx(
|
|
@@ -563,7 +562,7 @@ function trapFocus(container) {
|
|
|
563
562
|
container.addEventListener("keydown", handleKeyDown);
|
|
564
563
|
return () => container.removeEventListener("keydown", handleKeyDown);
|
|
565
564
|
}
|
|
566
|
-
function DefaultModal({ open, onClose, children, className }) {
|
|
565
|
+
function DefaultModal({ open, onClose, children, className, overlayClassName }) {
|
|
567
566
|
const overlayRef = useRef(null);
|
|
568
567
|
const modalRef = useRef(null);
|
|
569
568
|
const previousFocusRef = useRef(null);
|
|
@@ -593,7 +592,7 @@ function DefaultModal({ open, onClose, children, className }) {
|
|
|
593
592
|
"div",
|
|
594
593
|
{
|
|
595
594
|
ref: overlayRef,
|
|
596
|
-
className: "ck-overlay",
|
|
595
|
+
className: cn("ck-overlay", overlayClassName),
|
|
597
596
|
onClick: (e) => {
|
|
598
597
|
if (e.target === overlayRef.current) onClose();
|
|
599
598
|
},
|
|
@@ -673,7 +672,7 @@ function LoadStatus({
|
|
|
673
672
|
const CloseButton = components?.CloseButton ?? DefaultCloseButton;
|
|
674
673
|
const handleClose = onClose ?? (() => {
|
|
675
674
|
});
|
|
676
|
-
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, children: [
|
|
675
|
+
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: [
|
|
677
676
|
/* @__PURE__ */ jsx(CloseButton, { onClose: handleClose, className: classNames?.closeButton }),
|
|
678
677
|
/* @__PURE__ */ jsxs("div", { className: "ck-content", children: [
|
|
679
678
|
isLoading && /* @__PURE__ */ jsxs("div", { className: "ck-loading", style: { padding: "32px", textAlign: "center" }, children: [
|
|
@@ -725,7 +724,7 @@ function FlowShell({ machine, state, appearance, classNames, components, customC
|
|
|
725
724
|
const Modal = components?.Modal ?? DefaultModal;
|
|
726
725
|
const CloseButton = components?.CloseButton ?? DefaultCloseButton;
|
|
727
726
|
const BackButton = components?.BackButton ?? DefaultBackButton;
|
|
728
|
-
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, children: [
|
|
727
|
+
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: [
|
|
729
728
|
/* @__PURE__ */ jsx(CloseButton, { onClose: machine.close, className: classNames?.closeButton }),
|
|
730
729
|
/* @__PURE__ */ jsxs("div", { className: "ck-content", children: [
|
|
731
730
|
machine.canGoBack && /* @__PURE__ */ jsx(BackButton, { onBack: machine.back, className: classNames?.backButton }),
|
|
@@ -750,9 +749,13 @@ function StepRenderer({
|
|
|
750
749
|
{
|
|
751
750
|
title: config?.title ?? defaultTitles.survey,
|
|
752
751
|
description: config?.description,
|
|
752
|
+
customer: state.customer,
|
|
753
|
+
subscriptions: state.subscriptions,
|
|
753
754
|
reasons: machine.reasons,
|
|
754
755
|
selectedReason: state.selectedReason,
|
|
755
756
|
onSelectReason: machine.selectReason,
|
|
757
|
+
followupResponse: state.followupResponse,
|
|
758
|
+
onFollowupResponseChange: machine.setFollowupResponse,
|
|
756
759
|
onNext: machine.next,
|
|
757
760
|
classNames: config?.classNames,
|
|
758
761
|
components
|
|
@@ -769,12 +772,16 @@ function StepRenderer({
|
|
|
769
772
|
{
|
|
770
773
|
offer,
|
|
771
774
|
customer: state.customer,
|
|
775
|
+
subscriptions: state.subscriptions,
|
|
772
776
|
onAccept: machine.accept,
|
|
773
777
|
onDecline: machine.decline,
|
|
774
778
|
isProcessing: state.isProcessing
|
|
775
779
|
}
|
|
776
780
|
);
|
|
777
781
|
}
|
|
782
|
+
if (!BUILT_IN_OFFER_TYPES.includes(offer.type)) {
|
|
783
|
+
return /* @__PURE__ */ jsx(UnregisteredOfferFallback, { offerType: offer.type, onSkip: machine.decline });
|
|
784
|
+
}
|
|
778
785
|
const Offer = components?.Offer ?? DefaultOffer;
|
|
779
786
|
const config = stepConfig;
|
|
780
787
|
return /* @__PURE__ */ jsx(
|
|
@@ -782,6 +789,8 @@ function StepRenderer({
|
|
|
782
789
|
{
|
|
783
790
|
title: config?.title,
|
|
784
791
|
description: config?.description,
|
|
792
|
+
customer: state.customer,
|
|
793
|
+
subscriptions: state.subscriptions,
|
|
785
794
|
offer,
|
|
786
795
|
onAccept: machine.accept,
|
|
787
796
|
onDecline: machine.decline,
|
|
@@ -799,6 +808,8 @@ function StepRenderer({
|
|
|
799
808
|
{
|
|
800
809
|
title: config?.title ?? defaultTitles.feedback,
|
|
801
810
|
description: config?.description,
|
|
811
|
+
customer: state.customer,
|
|
812
|
+
subscriptions: state.subscriptions,
|
|
802
813
|
placeholder: config?.placeholder,
|
|
803
814
|
required: config?.required ?? false,
|
|
804
815
|
minLength: config?.minLength ?? 0,
|
|
@@ -817,6 +828,8 @@ function StepRenderer({
|
|
|
817
828
|
{
|
|
818
829
|
title: config?.title ?? defaultTitles.confirm,
|
|
819
830
|
description: config?.description,
|
|
831
|
+
customer: state.customer,
|
|
832
|
+
subscriptions: state.subscriptions,
|
|
820
833
|
losses: config?.losses,
|
|
821
834
|
lossesLabel: config?.lossesLabel,
|
|
822
835
|
confirmLabel: config?.confirmLabel ?? "Cancel subscription",
|
|
@@ -839,6 +852,8 @@ function StepRenderer({
|
|
|
839
852
|
offer: machine.currentOffer ?? void 0,
|
|
840
853
|
title: isSaved ? config?.savedTitle ?? "Welcome back!" : config?.cancelledTitle ?? "Subscription cancelled",
|
|
841
854
|
description: isSaved ? config?.savedDescription ?? "Your offer has been applied." : config?.cancelledDescription ?? "We're sorry to see you go.",
|
|
855
|
+
customer: state.customer,
|
|
856
|
+
subscriptions: state.subscriptions,
|
|
842
857
|
onClose: machine.close,
|
|
843
858
|
classNames: config?.classNames
|
|
844
859
|
}
|
|
@@ -860,6 +875,7 @@ function StepRenderer({
|
|
|
860
875
|
data: config?.data
|
|
861
876
|
},
|
|
862
877
|
customer: state.customer,
|
|
878
|
+
subscriptions: state.subscriptions,
|
|
863
879
|
onNext: machine.next,
|
|
864
880
|
onBack: machine.back
|
|
865
881
|
}
|
|
@@ -874,6 +890,13 @@ function UnregisteredStepFallback({ step, onSkip }) {
|
|
|
874
890
|
}, [step, onSkip]);
|
|
875
891
|
return null;
|
|
876
892
|
}
|
|
893
|
+
function UnregisteredOfferFallback({ offerType, onSkip }) {
|
|
894
|
+
useEffect(() => {
|
|
895
|
+
console.warn(`[churnkey] No component registered for offer type "${offerType}". Skipping.`);
|
|
896
|
+
onSkip();
|
|
897
|
+
}, [offerType, onSkip]);
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
877
900
|
|
|
878
901
|
export { CancelFlow, DefaultBackButton, DefaultCloseButton, DefaultConfirm, DefaultContactOffer, DefaultDiscountOffer, DefaultFeedback, DefaultModal, DefaultOffer, DefaultPauseOffer, DefaultPlanChangeOffer, DefaultReasonButton, DefaultRedirectOffer, DefaultSuccess, DefaultSurvey, DefaultTrialExtensionOffer };
|
|
879
902
|
//# sourceMappingURL=index.js.map
|