@churnkey/react 0.2.0 → 0.4.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.
Files changed (37) hide show
  1. package/dist/{chunk-YSNMTOAW.cjs → chunk-4BMB42WI.cjs} +9 -7
  2. package/dist/chunk-4BMB42WI.cjs.map +1 -0
  3. package/dist/{chunk-EHZJAQQ5.cjs → chunk-BDBZ5OS3.cjs} +21 -2
  4. package/dist/chunk-BDBZ5OS3.cjs.map +1 -0
  5. package/dist/{chunk-CPCWJTSG.js → chunk-F4ESJBOS.js} +6 -4
  6. package/dist/chunk-F4ESJBOS.js.map +1 -0
  7. package/dist/{chunk-SXGORJOX.js → chunk-G57YLYM5.js} +20 -3
  8. package/dist/chunk-G57YLYM5.js.map +1 -0
  9. package/dist/{chunk-OL5HQUE4.js → chunk-QYNW3HF3.js} +66 -26
  10. package/dist/chunk-QYNW3HF3.js.map +1 -0
  11. package/dist/{chunk-ZEYUKGAR.cjs → chunk-SNTKIKHU.cjs} +66 -26
  12. package/dist/chunk-SNTKIKHU.cjs.map +1 -0
  13. package/dist/core.cjs +20 -12
  14. package/dist/core.d.cts +30 -5
  15. package/dist/core.d.ts +30 -5
  16. package/dist/core.js +2 -2
  17. package/dist/headless.cjs +3 -3
  18. package/dist/headless.d.cts +5 -1
  19. package/dist/headless.d.ts +5 -1
  20. package/dist/headless.js +2 -2
  21. package/dist/index.cjs +218 -135
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +10 -8
  24. package/dist/index.d.ts +10 -8
  25. package/dist/index.js +125 -51
  26. package/dist/index.js.map +1 -1
  27. package/dist/{step-graph-Cbi0DRHX.d.cts → step-graph-DKvaV3Dg.d.cts} +56 -21
  28. package/dist/{step-graph-Cbi0DRHX.d.ts → step-graph-DKvaV3Dg.d.ts} +56 -21
  29. package/dist/styles.css +95 -19
  30. package/package.json +10 -10
  31. package/dist/chunk-CPCWJTSG.js.map +0 -1
  32. package/dist/chunk-EHZJAQQ5.cjs.map +0 -1
  33. package/dist/chunk-OL5HQUE4.js.map +0 -1
  34. package/dist/chunk-SXGORJOX.js.map +0 -1
  35. package/dist/chunk-YSNMTOAW.cjs.map +0 -1
  36. package/dist/chunk-ZEYUKGAR.cjs.map +0 -1
  37. package/dist/styles.css.d.ts +0 -1
@@ -129,12 +129,10 @@ interface PauseOffer {
129
129
  type: 'pause';
130
130
  months: number;
131
131
  interval?: 'month' | 'week';
132
- datePicker?: boolean;
133
132
  }
134
133
  interface PlanChangeOffer {
135
134
  type: 'plan_change';
136
135
  plans: PlanOption[];
137
- currentPlanId?: string;
138
136
  }
139
137
  /**
140
138
  * Plan option in a `plan_change` offer — `DirectPrice` plus optional
@@ -164,11 +162,23 @@ interface RedirectOffer {
164
162
  url: string;
165
163
  label: string;
166
164
  }
165
+ interface RebateOffer {
166
+ type: 'rebate';
167
+ /** Cash refunded to the card, smallest currency unit. */
168
+ amountMinor: number;
169
+ currency: string;
170
+ /** Gross paid on the target invoice. Server-resolved in token mode. */
171
+ amountPaidMinor?: number;
172
+ /** amountPaidMinor − amountMinor. Server-resolved in token mode. */
173
+ netAfterRebateMinor?: number;
174
+ paymentMethodBrand?: string;
175
+ paymentMethodLast4?: string;
176
+ }
167
177
  interface CustomOfferConfig {
168
178
  type: string;
169
179
  data?: Record<string, unknown>;
170
180
  }
171
- type BuiltInOfferConfig = DiscountOffer | PauseOffer | PlanChangeOffer | TrialExtensionOffer | ContactOffer | RedirectOffer;
181
+ type BuiltInOfferConfig = DiscountOffer | PauseOffer | PlanChangeOffer | TrialExtensionOffer | ContactOffer | RedirectOffer | RebateOffer;
172
182
  type OfferConfig = BuiltInOfferConfig | CustomOfferConfig;
173
183
  type OfferDecision = OfferConfig & {
174
184
  copy: OfferCopy;
@@ -181,13 +191,22 @@ interface OfferCopy {
181
191
  declineCta: string;
182
192
  }
183
193
  type AcceptedOffer = OfferConfig & {
184
- reasonId: string;
185
- decisionId?: string;
194
+ /** Survey reason that routed to this offer. Absent when the offer was
195
+ * declared as a standalone `OfferStep`. */
196
+ reasonId?: string;
197
+ /** Payload from custom offers — whatever your component passed to
198
+ * `onAccept(result)`. Built-in offer types do not populate this. */
186
199
  result?: Record<string, unknown>;
187
200
  };
188
201
  interface ReasonConfig {
189
202
  id: string;
190
203
  label: string;
204
+ /**
205
+ * When true, picking this reason reveals a text input below the reason list.
206
+ * The typed text lands on the session as `followupResponse`. `surveyChoiceId`
207
+ * still carries the reason's `id` and `surveyChoiceValue` still carries the
208
+ * static `label`, so analytics groupings stay stable.
209
+ */
191
210
  freeform?: boolean;
192
211
  offer?: OfferConfig;
193
212
  }
@@ -205,11 +224,12 @@ interface OfferStep {
205
224
  title?: string;
206
225
  description?: string;
207
226
  /**
208
- * Offer attached to this step. Set this for proactive save offers shown
209
- * outside a survey; the SDK also populates it automatically on synthetic
210
- * offer steps spawned from survey choices.
227
+ * Offer attached to this step. Set this to declare a standalone offer
228
+ * step (one that isn't routed from a survey reason). `copy` is optional —
229
+ * the SDK synthesizes default copy from the offer config when none is
230
+ * provided, the same way it does for survey-attached offers.
211
231
  */
212
- offer?: OfferDecision;
232
+ offer?: OfferConfig | OfferDecision;
213
233
  classNames?: OfferClassNames;
214
234
  }
215
235
  interface FeedbackStep {
@@ -262,7 +282,7 @@ interface SurveyClassNames {
262
282
  reasonButton?: string;
263
283
  reasonButtonSelected?: string;
264
284
  reasonLabel?: string;
265
- freeformInput?: string;
285
+ followupInput?: string;
266
286
  continueButton?: string;
267
287
  }
268
288
  interface OfferClassNames {
@@ -358,9 +378,9 @@ interface AppearanceVariables {
358
378
  shadowModal: string;
359
379
  shadowCard: string;
360
380
  /**
361
- * Color of the dim behind the modal. Accepts any CSS color (rgba,
362
- * color-mix, etc.). Defaults to a tinted version of the primary color
363
- * so any primary swap automatically re-tints the overlay.
381
+ * Color of the dim behind the modal. Defaults to a neutral translucent
382
+ * ink. Set to `color-mix(in srgb, var(--ck-color-primary) 40%, transparent)`
383
+ * to derive the overlay from your primary color, or any CSS color value.
364
384
  */
365
385
  overlayColor: string;
366
386
  }
@@ -375,12 +395,14 @@ interface Appearance {
375
395
  interface CustomStepProps {
376
396
  step: CustomStepConfig;
377
397
  customer: DirectCustomer | null;
398
+ subscriptions: DirectSubscription[];
378
399
  onNext: (result?: Record<string, unknown>) => void;
379
400
  onBack: () => void;
380
401
  }
381
402
  interface CustomOfferProps {
382
403
  offer: OfferDecision;
383
404
  customer: DirectCustomer | null;
405
+ subscriptions: DirectSubscription[];
384
406
  onAccept: (result?: Record<string, unknown>) => Promise<void>;
385
407
  onDecline: () => void;
386
408
  isProcessing: boolean;
@@ -401,6 +423,7 @@ interface ComponentOverrides {
401
423
  TrialExtensionOffer?: (props: OfferStepProps) => ReactElement;
402
424
  ContactOffer?: (props: OfferStepProps) => ReactElement;
403
425
  RedirectOffer?: (props: OfferStepProps) => ReactElement;
426
+ RebateOffer?: (props: OfferStepProps) => ReactElement;
404
427
  }
405
428
  type CustomComponents = Record<string, ComponentType<CustomStepProps> | ComponentType<CustomOfferProps>>;
406
429
  interface ModalProps {
@@ -408,6 +431,7 @@ interface ModalProps {
408
431
  onClose: () => void;
409
432
  children: ReactNode;
410
433
  className?: string;
434
+ overlayClassName?: string;
411
435
  }
412
436
  interface CloseButtonProps {
413
437
  onClose: () => void;
@@ -420,9 +444,15 @@ interface BackButtonProps {
420
444
  interface SurveyStepProps {
421
445
  title: string;
422
446
  description?: string;
447
+ customer: DirectCustomer | null;
448
+ subscriptions: DirectSubscription[];
423
449
  reasons: ReasonConfig[];
424
450
  selectedReason: string | null;
425
451
  onSelectReason: (id: string) => void;
452
+ /** Free-text value when the selected reason has `freeform: true`. Lands on the session as `followupResponse`. */
453
+ followupResponse: string;
454
+ /** Set the follow-up response. The SDK forwards the value to the session. */
455
+ onFollowupResponseChange: (text: string) => void;
426
456
  onNext: () => void;
427
457
  classNames?: SurveyClassNames;
428
458
  components?: Partial<ComponentOverrides>;
@@ -430,6 +460,8 @@ interface SurveyStepProps {
430
460
  interface OfferStepProps {
431
461
  title?: string;
432
462
  description?: string;
463
+ customer: DirectCustomer | null;
464
+ subscriptions: DirectSubscription[];
433
465
  offer: OfferDecision;
434
466
  /**
435
467
  * Accept the offer. The optional `result` is included on the resulting
@@ -450,6 +482,8 @@ interface OfferStepProps {
450
482
  interface FeedbackStepProps {
451
483
  title: string;
452
484
  description?: string;
485
+ customer: DirectCustomer | null;
486
+ subscriptions: DirectSubscription[];
453
487
  placeholder?: string;
454
488
  required: boolean;
455
489
  minLength: number;
@@ -461,11 +495,12 @@ interface FeedbackStepProps {
461
495
  interface ConfirmStepProps {
462
496
  title: string;
463
497
  description?: string;
498
+ customer: DirectCustomer | null;
499
+ subscriptions: DirectSubscription[];
464
500
  losses?: string[];
465
501
  lossesLabel?: string;
466
502
  confirmLabel: string;
467
503
  goBackLabel: string;
468
- periodEnd?: string;
469
504
  onConfirm: () => Promise<void>;
470
505
  onGoBack: () => void;
471
506
  isProcessing: boolean;
@@ -476,6 +511,8 @@ interface SuccessStepProps {
476
511
  offer?: OfferDecision;
477
512
  title: string;
478
513
  description?: string;
514
+ customer: DirectCustomer | null;
515
+ subscriptions: DirectSubscription[];
479
516
  onClose: () => void;
480
517
  classNames?: SuccessClassNames;
481
518
  }
@@ -489,11 +526,13 @@ interface FlowState {
489
526
  step: string;
490
527
  currentStepId: string;
491
528
  selectedReason: string | null;
529
+ followupResponse: string;
492
530
  feedback: string;
493
531
  outcome: 'saved' | 'cancelled' | null;
494
532
  isProcessing: boolean;
495
533
  error: Error | null;
496
534
  customer: DirectCustomer | null;
535
+ subscriptions: DirectSubscription[];
497
536
  }
498
537
  interface FlowConfig extends FlowCallbacks {
499
538
  appId?: string;
@@ -529,12 +568,14 @@ interface FlowCallbacks {
529
568
  handlePause?: OfferCallback;
530
569
  handlePlanChange?: OfferCallback;
531
570
  handleTrialExtension?: OfferCallback;
571
+ handleRebate?: OfferCallback;
532
572
  handleCancel?: CancelCallback;
533
573
  onAccept?: OfferCallback;
534
574
  onDiscount?: OfferCallback;
535
575
  onPause?: OfferCallback;
536
576
  onPlanChange?: OfferCallback;
537
577
  onTrialExtension?: OfferCallback;
578
+ onRebate?: OfferCallback;
538
579
  onCancel?: CancelCallback;
539
580
  onClose?: () => void;
540
581
  onStepChange?: (step: string, prevStep: string) => void;
@@ -552,12 +593,6 @@ interface CancelFlowProps extends FlowCallbacks {
552
593
  classNames?: StructuralClassNames;
553
594
  components?: Partial<ComponentOverrides>;
554
595
  customComponents?: CustomComponents;
555
- layout?: {
556
- desktop?: 'modal' | 'inline' | 'drawer';
557
- mobile?: 'sheet' | 'fullscreen' | 'inline';
558
- breakpoint?: number;
559
- };
560
- animation?: 'css' | 'framer' | 'none';
561
596
  }
562
597
 
563
598
  interface ResolvedStep {
@@ -586,4 +621,4 @@ interface ResolvedStep {
586
621
  classNames?: unknown;
587
622
  }
588
623
 
589
- export type { SurveyStep as $, AcceptedOffer as A, BackButtonProps as B, CancelFlowProps as C, DirectAddress as D, FlowState as E, FeedbackStepProps as F, OfferClassNames as G, OfferConfig as H, OfferCopy as I, OfferDecision as J, OfferStep as K, PlanChangeOffer as L, ModalProps as M, PlanOption as N, OfferStepProps as O, PauseOffer as P, ReasonConfig as Q, ReasonButtonProps as R, SuccessStepProps as S, RedirectOffer as T, ResolvedStep as U, Step as V, StructuralClassNames as W, SubscriptionStatus as X, SuccessClassNames as Y, SuccessStep as Z, SurveyClassNames as _, ConfirmStepProps as a, TrialExtensionOffer as a0, SurveyStepProps as b, CloseButtonProps as c, Appearance as d, AppearanceVariables as e, BuiltInOfferConfig as f, BuiltInStep as g, BuiltInStepType as h, ComponentOverrides as i, ConfirmClassNames as j, ConfirmStep as k, ContactOffer as l, CustomComponents as m, CustomOfferConfig as n, CustomOfferProps as o, CustomStepConfig as p, CustomStepProps as q, DirectCoupon as r, DirectCustomer as s, DirectPrice as t, DirectSubscription as u, DirectSubscriptionItem as v, DiscountOffer as w, FeedbackClassNames as x, FeedbackStep as y, FlowConfig as z };
624
+ export type { SurveyClassNames as $, AcceptedOffer as A, BackButtonProps as B, CancelFlowProps as C, DirectAddress as D, FlowState as E, FeedbackStepProps as F, OfferClassNames as G, OfferConfig as H, OfferCopy as I, OfferDecision as J, OfferStep as K, PlanChangeOffer as L, ModalProps as M, PlanOption as N, OfferStepProps as O, PauseOffer as P, ReasonConfig as Q, ReasonButtonProps as R, SuccessStepProps as S, RebateOffer as T, RedirectOffer as U, ResolvedStep as V, Step as W, StructuralClassNames as X, SubscriptionStatus as Y, SuccessClassNames as Z, SuccessStep as _, ConfirmStepProps as a, SurveyStep as a0, TrialExtensionOffer as a1, SurveyStepProps as b, CloseButtonProps as c, Appearance as d, AppearanceVariables as e, BuiltInOfferConfig as f, BuiltInStep as g, BuiltInStepType as h, ComponentOverrides as i, ConfirmClassNames as j, ConfirmStep as k, ContactOffer as l, CustomComponents as m, CustomOfferConfig as n, CustomOfferProps as o, CustomStepConfig as p, CustomStepProps as q, DirectCoupon as r, DirectCustomer as s, DirectPrice as t, DirectSubscription as u, DirectSubscriptionItem as v, DiscountOffer as w, FeedbackClassNames as x, FeedbackStep as y, FlowConfig as z };
@@ -129,12 +129,10 @@ interface PauseOffer {
129
129
  type: 'pause';
130
130
  months: number;
131
131
  interval?: 'month' | 'week';
132
- datePicker?: boolean;
133
132
  }
134
133
  interface PlanChangeOffer {
135
134
  type: 'plan_change';
136
135
  plans: PlanOption[];
137
- currentPlanId?: string;
138
136
  }
139
137
  /**
140
138
  * Plan option in a `plan_change` offer — `DirectPrice` plus optional
@@ -164,11 +162,23 @@ interface RedirectOffer {
164
162
  url: string;
165
163
  label: string;
166
164
  }
165
+ interface RebateOffer {
166
+ type: 'rebate';
167
+ /** Cash refunded to the card, smallest currency unit. */
168
+ amountMinor: number;
169
+ currency: string;
170
+ /** Gross paid on the target invoice. Server-resolved in token mode. */
171
+ amountPaidMinor?: number;
172
+ /** amountPaidMinor − amountMinor. Server-resolved in token mode. */
173
+ netAfterRebateMinor?: number;
174
+ paymentMethodBrand?: string;
175
+ paymentMethodLast4?: string;
176
+ }
167
177
  interface CustomOfferConfig {
168
178
  type: string;
169
179
  data?: Record<string, unknown>;
170
180
  }
171
- type BuiltInOfferConfig = DiscountOffer | PauseOffer | PlanChangeOffer | TrialExtensionOffer | ContactOffer | RedirectOffer;
181
+ type BuiltInOfferConfig = DiscountOffer | PauseOffer | PlanChangeOffer | TrialExtensionOffer | ContactOffer | RedirectOffer | RebateOffer;
172
182
  type OfferConfig = BuiltInOfferConfig | CustomOfferConfig;
173
183
  type OfferDecision = OfferConfig & {
174
184
  copy: OfferCopy;
@@ -181,13 +191,22 @@ interface OfferCopy {
181
191
  declineCta: string;
182
192
  }
183
193
  type AcceptedOffer = OfferConfig & {
184
- reasonId: string;
185
- decisionId?: string;
194
+ /** Survey reason that routed to this offer. Absent when the offer was
195
+ * declared as a standalone `OfferStep`. */
196
+ reasonId?: string;
197
+ /** Payload from custom offers — whatever your component passed to
198
+ * `onAccept(result)`. Built-in offer types do not populate this. */
186
199
  result?: Record<string, unknown>;
187
200
  };
188
201
  interface ReasonConfig {
189
202
  id: string;
190
203
  label: string;
204
+ /**
205
+ * When true, picking this reason reveals a text input below the reason list.
206
+ * The typed text lands on the session as `followupResponse`. `surveyChoiceId`
207
+ * still carries the reason's `id` and `surveyChoiceValue` still carries the
208
+ * static `label`, so analytics groupings stay stable.
209
+ */
191
210
  freeform?: boolean;
192
211
  offer?: OfferConfig;
193
212
  }
@@ -205,11 +224,12 @@ interface OfferStep {
205
224
  title?: string;
206
225
  description?: string;
207
226
  /**
208
- * Offer attached to this step. Set this for proactive save offers shown
209
- * outside a survey; the SDK also populates it automatically on synthetic
210
- * offer steps spawned from survey choices.
227
+ * Offer attached to this step. Set this to declare a standalone offer
228
+ * step (one that isn't routed from a survey reason). `copy` is optional —
229
+ * the SDK synthesizes default copy from the offer config when none is
230
+ * provided, the same way it does for survey-attached offers.
211
231
  */
212
- offer?: OfferDecision;
232
+ offer?: OfferConfig | OfferDecision;
213
233
  classNames?: OfferClassNames;
214
234
  }
215
235
  interface FeedbackStep {
@@ -262,7 +282,7 @@ interface SurveyClassNames {
262
282
  reasonButton?: string;
263
283
  reasonButtonSelected?: string;
264
284
  reasonLabel?: string;
265
- freeformInput?: string;
285
+ followupInput?: string;
266
286
  continueButton?: string;
267
287
  }
268
288
  interface OfferClassNames {
@@ -358,9 +378,9 @@ interface AppearanceVariables {
358
378
  shadowModal: string;
359
379
  shadowCard: string;
360
380
  /**
361
- * Color of the dim behind the modal. Accepts any CSS color (rgba,
362
- * color-mix, etc.). Defaults to a tinted version of the primary color
363
- * so any primary swap automatically re-tints the overlay.
381
+ * Color of the dim behind the modal. Defaults to a neutral translucent
382
+ * ink. Set to `color-mix(in srgb, var(--ck-color-primary) 40%, transparent)`
383
+ * to derive the overlay from your primary color, or any CSS color value.
364
384
  */
365
385
  overlayColor: string;
366
386
  }
@@ -375,12 +395,14 @@ interface Appearance {
375
395
  interface CustomStepProps {
376
396
  step: CustomStepConfig;
377
397
  customer: DirectCustomer | null;
398
+ subscriptions: DirectSubscription[];
378
399
  onNext: (result?: Record<string, unknown>) => void;
379
400
  onBack: () => void;
380
401
  }
381
402
  interface CustomOfferProps {
382
403
  offer: OfferDecision;
383
404
  customer: DirectCustomer | null;
405
+ subscriptions: DirectSubscription[];
384
406
  onAccept: (result?: Record<string, unknown>) => Promise<void>;
385
407
  onDecline: () => void;
386
408
  isProcessing: boolean;
@@ -401,6 +423,7 @@ interface ComponentOverrides {
401
423
  TrialExtensionOffer?: (props: OfferStepProps) => ReactElement;
402
424
  ContactOffer?: (props: OfferStepProps) => ReactElement;
403
425
  RedirectOffer?: (props: OfferStepProps) => ReactElement;
426
+ RebateOffer?: (props: OfferStepProps) => ReactElement;
404
427
  }
405
428
  type CustomComponents = Record<string, ComponentType<CustomStepProps> | ComponentType<CustomOfferProps>>;
406
429
  interface ModalProps {
@@ -408,6 +431,7 @@ interface ModalProps {
408
431
  onClose: () => void;
409
432
  children: ReactNode;
410
433
  className?: string;
434
+ overlayClassName?: string;
411
435
  }
412
436
  interface CloseButtonProps {
413
437
  onClose: () => void;
@@ -420,9 +444,15 @@ interface BackButtonProps {
420
444
  interface SurveyStepProps {
421
445
  title: string;
422
446
  description?: string;
447
+ customer: DirectCustomer | null;
448
+ subscriptions: DirectSubscription[];
423
449
  reasons: ReasonConfig[];
424
450
  selectedReason: string | null;
425
451
  onSelectReason: (id: string) => void;
452
+ /** Free-text value when the selected reason has `freeform: true`. Lands on the session as `followupResponse`. */
453
+ followupResponse: string;
454
+ /** Set the follow-up response. The SDK forwards the value to the session. */
455
+ onFollowupResponseChange: (text: string) => void;
426
456
  onNext: () => void;
427
457
  classNames?: SurveyClassNames;
428
458
  components?: Partial<ComponentOverrides>;
@@ -430,6 +460,8 @@ interface SurveyStepProps {
430
460
  interface OfferStepProps {
431
461
  title?: string;
432
462
  description?: string;
463
+ customer: DirectCustomer | null;
464
+ subscriptions: DirectSubscription[];
433
465
  offer: OfferDecision;
434
466
  /**
435
467
  * Accept the offer. The optional `result` is included on the resulting
@@ -450,6 +482,8 @@ interface OfferStepProps {
450
482
  interface FeedbackStepProps {
451
483
  title: string;
452
484
  description?: string;
485
+ customer: DirectCustomer | null;
486
+ subscriptions: DirectSubscription[];
453
487
  placeholder?: string;
454
488
  required: boolean;
455
489
  minLength: number;
@@ -461,11 +495,12 @@ interface FeedbackStepProps {
461
495
  interface ConfirmStepProps {
462
496
  title: string;
463
497
  description?: string;
498
+ customer: DirectCustomer | null;
499
+ subscriptions: DirectSubscription[];
464
500
  losses?: string[];
465
501
  lossesLabel?: string;
466
502
  confirmLabel: string;
467
503
  goBackLabel: string;
468
- periodEnd?: string;
469
504
  onConfirm: () => Promise<void>;
470
505
  onGoBack: () => void;
471
506
  isProcessing: boolean;
@@ -476,6 +511,8 @@ interface SuccessStepProps {
476
511
  offer?: OfferDecision;
477
512
  title: string;
478
513
  description?: string;
514
+ customer: DirectCustomer | null;
515
+ subscriptions: DirectSubscription[];
479
516
  onClose: () => void;
480
517
  classNames?: SuccessClassNames;
481
518
  }
@@ -489,11 +526,13 @@ interface FlowState {
489
526
  step: string;
490
527
  currentStepId: string;
491
528
  selectedReason: string | null;
529
+ followupResponse: string;
492
530
  feedback: string;
493
531
  outcome: 'saved' | 'cancelled' | null;
494
532
  isProcessing: boolean;
495
533
  error: Error | null;
496
534
  customer: DirectCustomer | null;
535
+ subscriptions: DirectSubscription[];
497
536
  }
498
537
  interface FlowConfig extends FlowCallbacks {
499
538
  appId?: string;
@@ -529,12 +568,14 @@ interface FlowCallbacks {
529
568
  handlePause?: OfferCallback;
530
569
  handlePlanChange?: OfferCallback;
531
570
  handleTrialExtension?: OfferCallback;
571
+ handleRebate?: OfferCallback;
532
572
  handleCancel?: CancelCallback;
533
573
  onAccept?: OfferCallback;
534
574
  onDiscount?: OfferCallback;
535
575
  onPause?: OfferCallback;
536
576
  onPlanChange?: OfferCallback;
537
577
  onTrialExtension?: OfferCallback;
578
+ onRebate?: OfferCallback;
538
579
  onCancel?: CancelCallback;
539
580
  onClose?: () => void;
540
581
  onStepChange?: (step: string, prevStep: string) => void;
@@ -552,12 +593,6 @@ interface CancelFlowProps extends FlowCallbacks {
552
593
  classNames?: StructuralClassNames;
553
594
  components?: Partial<ComponentOverrides>;
554
595
  customComponents?: CustomComponents;
555
- layout?: {
556
- desktop?: 'modal' | 'inline' | 'drawer';
557
- mobile?: 'sheet' | 'fullscreen' | 'inline';
558
- breakpoint?: number;
559
- };
560
- animation?: 'css' | 'framer' | 'none';
561
596
  }
562
597
 
563
598
  interface ResolvedStep {
@@ -586,4 +621,4 @@ interface ResolvedStep {
586
621
  classNames?: unknown;
587
622
  }
588
623
 
589
- export type { SurveyStep as $, AcceptedOffer as A, BackButtonProps as B, CancelFlowProps as C, DirectAddress as D, FlowState as E, FeedbackStepProps as F, OfferClassNames as G, OfferConfig as H, OfferCopy as I, OfferDecision as J, OfferStep as K, PlanChangeOffer as L, ModalProps as M, PlanOption as N, OfferStepProps as O, PauseOffer as P, ReasonConfig as Q, ReasonButtonProps as R, SuccessStepProps as S, RedirectOffer as T, ResolvedStep as U, Step as V, StructuralClassNames as W, SubscriptionStatus as X, SuccessClassNames as Y, SuccessStep as Z, SurveyClassNames as _, ConfirmStepProps as a, TrialExtensionOffer as a0, SurveyStepProps as b, CloseButtonProps as c, Appearance as d, AppearanceVariables as e, BuiltInOfferConfig as f, BuiltInStep as g, BuiltInStepType as h, ComponentOverrides as i, ConfirmClassNames as j, ConfirmStep as k, ContactOffer as l, CustomComponents as m, CustomOfferConfig as n, CustomOfferProps as o, CustomStepConfig as p, CustomStepProps as q, DirectCoupon as r, DirectCustomer as s, DirectPrice as t, DirectSubscription as u, DirectSubscriptionItem as v, DiscountOffer as w, FeedbackClassNames as x, FeedbackStep as y, FlowConfig as z };
624
+ export type { SurveyClassNames as $, AcceptedOffer as A, BackButtonProps as B, CancelFlowProps as C, DirectAddress as D, FlowState as E, FeedbackStepProps as F, OfferClassNames as G, OfferConfig as H, OfferCopy as I, OfferDecision as J, OfferStep as K, PlanChangeOffer as L, ModalProps as M, PlanOption as N, OfferStepProps as O, PauseOffer as P, ReasonConfig as Q, ReasonButtonProps as R, SuccessStepProps as S, RebateOffer as T, RedirectOffer as U, ResolvedStep as V, Step as W, StructuralClassNames as X, SubscriptionStatus as Y, SuccessClassNames as Z, SuccessStep as _, ConfirmStepProps as a, SurveyStep as a0, TrialExtensionOffer as a1, SurveyStepProps as b, CloseButtonProps as c, Appearance as d, AppearanceVariables as e, BuiltInOfferConfig as f, BuiltInStep as g, BuiltInStepType as h, ComponentOverrides as i, ConfirmClassNames as j, ConfirmStep as k, ContactOffer as l, CustomComponents as m, CustomOfferConfig as n, CustomOfferProps as o, CustomStepConfig as p, CustomStepProps as q, DirectCoupon as r, DirectCustomer as s, DirectPrice as t, DirectSubscription as u, DirectSubscriptionItem as v, DiscountOffer as w, FeedbackClassNames as x, FeedbackStep as y, FlowConfig as z };
package/dist/styles.css CHANGED
@@ -71,12 +71,11 @@
71
71
  --ck-shadow-modal: 0 20px 25px -5px rgba(12, 10, 9, 0.1), 0 8px 10px -6px rgba(12, 10, 9, 0.06);
72
72
  --ck-shadow-card: 0 1px 2px rgba(12, 10, 9, 0.04), 0 1px 3px rgba(12, 10, 9, 0.06);
73
73
 
74
- /* Overlay tint behind the modal. Defaults to a tinted version of the
75
- primary color so any primary swap naturally re-tints the overlay.
76
- Falls back gracefully on browsers without color-mix() support the
77
- declaration is invalid in those, so the rule below it (or the inline
78
- style) wins. */
79
- --ck-overlay-color: color-mix(in srgb, var(--ck-color-primary) 40%, transparent);
74
+ /* Dim behind the modal. Neutral translucent ink by default. Brands that
75
+ want a primary-tinted overlay can override this with their own value
76
+ e.g. `color-mix(in srgb, var(--ck-color-primary) 40%, transparent)` for
77
+ auto re-tinting on primary swaps. */
78
+ --ck-overlay-color: rgba(12, 10, 9, 0.5);
80
79
 
81
80
  /* Motion */
82
81
  --ck-motion-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
@@ -144,11 +143,6 @@
144
143
  display: flex;
145
144
  align-items: center;
146
145
  justify-content: center;
147
- /* Fallback for browsers without color-mix() support — used if the token
148
- evaluates to an invalid value. Modern engines (Chrome 111+, Safari
149
- 16.2+, Firefox 113+) resolve --ck-overlay-color from the :root rule. */
150
- background: rgba(12, 10, 9, 0.5);
151
- /* biome-ignore lint/suspicious/noDuplicateProperties: progressive enhancement fallback */
152
146
  background: var(--ck-overlay-color);
153
147
  z-index: 9999;
154
148
  }
@@ -353,6 +347,25 @@
353
347
  margin-bottom: 20px;
354
348
  }
355
349
 
350
+ .ck-cancel-flow .ck-reason-followup {
351
+ width: 100%;
352
+ font-family: var(--ck-font);
353
+ font-size: 14px;
354
+ color: var(--ck-color-text);
355
+ background: var(--ck-color-surface);
356
+ border: 1px solid var(--ck-color-border);
357
+ border-radius: var(--ck-radius-md);
358
+ padding: 10px 12px;
359
+ margin-bottom: 20px;
360
+ resize: vertical;
361
+ min-height: 72px;
362
+ }
363
+
364
+ .ck-cancel-flow .ck-reason-followup:focus {
365
+ outline: none;
366
+ border-color: var(--ck-color-primary);
367
+ }
368
+
356
369
  .ck-cancel-flow .ck-reason-button {
357
370
  display: flex;
358
371
  align-items: center;
@@ -418,10 +431,17 @@
418
431
  margin-bottom: 20px;
419
432
  }
420
433
 
421
- /* Discount: centered phrase in a soft tinted panel. */
422
- .ck-cancel-flow .ck-offer-discount {
423
- background: var(--ck-color-primary-soft);
434
+ /* Offer panels are soft callouts (colorSurfaceMuted), not selected-state
435
+ highlights (colorPrimarySoft). Shared so the offer types stay consistent. */
436
+ .ck-cancel-flow .ck-offer-discount,
437
+ .ck-cancel-flow .ck-pause-card,
438
+ .ck-cancel-flow .ck-trial-block {
439
+ background: var(--ck-color-surface-muted);
424
440
  border-radius: var(--ck-radius-lg);
441
+ }
442
+
443
+ /* Discount: centered phrase. */
444
+ .ck-cancel-flow .ck-offer-discount {
425
445
  padding: 24px 20px;
426
446
  text-align: center;
427
447
  }
@@ -444,11 +464,43 @@
444
464
  color: var(--ck-color-text);
445
465
  }
446
466
 
467
+ /* Rebate: an itemized invoice for the period — the charge, the rebate we
468
+ credit (accented), and what's still due. Lines sit on the surface rather
469
+ than in a filled panel. */
470
+ .ck-cancel-flow .ck-offer-rebate {
471
+ margin: 4px 0 26px;
472
+ font-variant-numeric: tabular-nums;
473
+ }
474
+
475
+ .ck-cancel-flow .ck-offer-rebate-row {
476
+ display: flex;
477
+ justify-content: space-between;
478
+ align-items: baseline;
479
+ gap: 12px;
480
+ padding: 7px 0;
481
+ font-size: 13px;
482
+ color: var(--ck-color-text-muted);
483
+ }
484
+
485
+ /* The rebate line — the figure we want the eye to land on. */
486
+ .ck-cancel-flow .ck-offer-rebate-credit {
487
+ font-size: 14px;
488
+ font-weight: 600;
489
+ color: var(--ck-color-primary);
490
+ }
491
+
492
+ .ck-cancel-flow .ck-offer-rebate-total {
493
+ margin-top: 6px;
494
+ padding-top: 14px;
495
+ border-top: 1px solid var(--ck-color-border);
496
+ font-size: 15px;
497
+ font-weight: 600;
498
+ color: var(--ck-color-text);
499
+ }
500
+
447
501
  /* Pause: resume date is the typographic anchor, month chips below. */
448
502
  .ck-cancel-flow .ck-pause-card {
449
503
  padding: 32px 24px 22px;
450
- background: var(--ck-color-surface-muted);
451
- border-radius: var(--ck-radius-lg);
452
504
  text-align: center;
453
505
  margin: 8px 0 20px;
454
506
  }
@@ -502,14 +554,12 @@
502
554
  border-color: var(--ck-color-primary);
503
555
  }
504
556
 
505
- /* Trial extension: day count badge + new end date in accent-soft container. */
557
+ /* Trial extension: day count badge + new end date. */
506
558
  .ck-cancel-flow .ck-trial-block {
507
559
  display: flex;
508
560
  align-items: center;
509
561
  gap: 16px;
510
562
  padding: 20px;
511
- background: var(--ck-color-primary-soft);
512
- border-radius: var(--ck-radius-lg);
513
563
  }
514
564
 
515
565
  .ck-cancel-flow .ck-trial-badge {
@@ -613,6 +663,17 @@
613
663
  box-shadow: var(--ck-shadow-card);
614
664
  }
615
665
 
666
+ .ck-cancel-flow .ck-plan-card--current {
667
+ cursor: not-allowed;
668
+ opacity: 0.6;
669
+ background: var(--ck-color-surface-muted);
670
+ }
671
+
672
+ .ck-cancel-flow .ck-plan-card--current:hover {
673
+ border-color: var(--ck-color-border);
674
+ background: var(--ck-color-surface-muted);
675
+ }
676
+
616
677
  .ck-cancel-flow .ck-plan-name {
617
678
  font-family: var(--ck-font-display);
618
679
  font-size: 16px;
@@ -620,6 +681,21 @@
620
681
  letter-spacing: -0.01em;
621
682
  color: var(--ck-color-text);
622
683
  margin-bottom: 4px;
684
+ display: flex;
685
+ align-items: center;
686
+ gap: 8px;
687
+ }
688
+
689
+ .ck-cancel-flow .ck-plan-current-badge {
690
+ font-family: var(--ck-font);
691
+ font-size: 11px;
692
+ font-weight: 500;
693
+ letter-spacing: 0;
694
+ color: var(--ck-color-text-secondary);
695
+ background: var(--ck-color-surface);
696
+ border: 1px solid var(--ck-color-border);
697
+ padding: 2px 6px;
698
+ border-radius: var(--ck-radius-sm);
623
699
  }
624
700
 
625
701
  .ck-cancel-flow .ck-plan-tagline {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@churnkey/react",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Production-ready cancel flow for React. Drop-in component, headless hook, or full customization. Works standalone or with Churnkey for AI-powered retention.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -65,6 +65,14 @@
65
65
  "dist",
66
66
  "package.json"
67
67
  ],
68
+ "scripts": {
69
+ "build": "tsup && node -e \"require('fs').writeFileSync('dist/styles.css.d.ts','export {}\\n')\"",
70
+ "dev": "tsup --watch",
71
+ "test": "vitest run",
72
+ "test:watch": "vitest",
73
+ "typecheck": "tsc --noEmit",
74
+ "lint": "biome check ."
75
+ },
68
76
  "peerDependencies": {
69
77
  "react": "^18.0.0 || ^19.0.0",
70
78
  "react-dom": "^18.0.0 || ^19.0.0"
@@ -82,13 +90,5 @@
82
90
  "tsup": "^8.0.0",
83
91
  "typescript": "^5.5.0",
84
92
  "vitest": "^2.0.0"
85
- },
86
- "scripts": {
87
- "build": "tsup && node -e \"require('fs').writeFileSync('dist/styles.css.d.ts','export {}\\n')\"",
88
- "dev": "tsup --watch",
89
- "test": "vitest run",
90
- "test:watch": "vitest",
91
- "typecheck": "tsc --noEmit",
92
- "lint": "biome check ."
93
93
  }
94
- }
94
+ }