@funnelsgrove/payments 0.1.17 → 0.1.18

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.
@@ -22,6 +22,8 @@ type ManageSubscription = RuntimeSubscription & {
22
22
  export declare function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId, plans, }: ManageSubscriptionScreenProps): import("react/jsx-runtime").JSX.Element;
23
23
  export declare const __manageSubscriptionScreenTestables: {
24
24
  resolveBillingDateLabel: (subscription: Pick<ManageSubscription, "cancelAtPeriodEnd" | "currentPeriodEnd" | "status">, options?: {
25
+ cancelled?: boolean;
25
26
  past?: boolean;
26
27
  }) => string;
28
+ resolveSubscriptionStatusLabel: (subscription: ManageSubscription) => string;
27
29
  };
@@ -4,9 +4,13 @@ import { useEffect, useMemo, useState } from 'react';
4
4
  import { apiService, buildPreviewManageSubscriptionsFallback, isPreviewFrameRuntime, runtimePublicConfig, useFunnel, } from '@funnelsgrove/runtime';
5
5
  import { getFallbackBillingPlans } from '../services/planCatalog.service.js';
6
6
  const pastSubscriptionsNote = 'Past subscriptions are already canceled or expired. Dates below are historical, not upcoming charges.';
7
+ const cancelledSubscriptionsNote = 'These subscriptions are cancelled and remain active until the date shown below.';
8
+ const getNormalizedStatus = (subscription) => subscription.status.trim().toLowerCase();
9
+ const isCancelledActiveSubscription = (subscription) => {
10
+ return subscription.cancelAtPeriodEnd && getNormalizedStatus(subscription) !== 'canceled';
11
+ };
7
12
  const isCancellableSubscription = (subscription) => {
8
- const normalizedStatus = subscription.status.trim().toLowerCase();
9
- return normalizedStatus !== 'canceled' && !subscription.cancelAtPeriodEnd;
13
+ return getNormalizedStatus(subscription) !== 'canceled' && !subscription.cancelAtPeriodEnd;
10
14
  };
11
15
  const formatDate = (value) => {
12
16
  if (!value) {
@@ -51,8 +55,8 @@ const resolveCreatedDate = (subscription) => {
51
55
  return formatDate(subscription.createdAt || subscription.currentPeriodStart);
52
56
  };
53
57
  const resolveBillingDateLabel = (subscription, options = {}) => {
54
- const normalizedStatus = subscription.status.trim().toLowerCase();
55
- if (subscription.cancelAtPeriodEnd) {
58
+ const normalizedStatus = getNormalizedStatus(subscription);
59
+ if (options.cancelled || subscription.cancelAtPeriodEnd) {
56
60
  return `Active until: ${formatDate(subscription.currentPeriodEnd)}`;
57
61
  }
58
62
  if (options.past && normalizedStatus === 'canceled') {
@@ -64,6 +68,16 @@ const resolveBillingDateLabel = (subscription, options = {}) => {
64
68
  return `Next charge: ${formatDate(subscription.currentPeriodEnd)}`;
65
69
  };
66
70
  const toManageSubscriptions = (subscriptions) => subscriptions;
71
+ const resolveSubscriptionStatusLabel = (subscription) => {
72
+ if (isCancelledActiveSubscription(subscription)) {
73
+ return 'Cancelled';
74
+ }
75
+ const normalizedStatus = getNormalizedStatus(subscription);
76
+ if (!normalizedStatus) {
77
+ return 'Active';
78
+ }
79
+ return `${normalizedStatus.charAt(0).toUpperCase()}${normalizedStatus.slice(1)}`;
80
+ };
67
81
  export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId, plans, }) {
68
82
  var _a, _b;
69
83
  const { attributes, completeStep, goToStep, setAnswer, setUser, user } = useFunnel();
@@ -114,7 +128,8 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
114
128
  }, [content.errorMessages.load]);
115
129
  const subscriptions = useMemo(() => { var _a; return toManageSubscriptions((_a = data === null || data === void 0 ? void 0 : data.subscriptions) !== null && _a !== void 0 ? _a : []); }, [data]);
116
130
  const activeSubscriptions = useMemo(() => subscriptions.filter(isCancellableSubscription), [subscriptions]);
117
- const pastSubscriptions = useMemo(() => subscriptions.filter((subscription) => !isCancellableSubscription(subscription)), [subscriptions]);
131
+ const cancelledSubscriptions = useMemo(() => subscriptions.filter(isCancelledActiveSubscription), [subscriptions]);
132
+ const pastSubscriptions = useMemo(() => subscriptions.filter((subscription) => !isCancellableSubscription(subscription) && !isCancelledActiveSubscription(subscription)), [subscriptions]);
118
133
  const selectedSubscription = (_a = activeSubscriptions.find((subscription) => subscription.id === selectedSubscriptionId)) !== null && _a !== void 0 ? _a : null;
119
134
  const selectedReason = (_b = content.whyStage.reasons.find((reason) => reason.id === selectedReasonId)) !== null && _b !== void 0 ? _b : null;
120
135
  const handleSubscriptionSelected = (subscription) => {
@@ -192,11 +207,11 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
192
207
  setCancelInFlight(false);
193
208
  }
194
209
  };
195
- return (_jsxs(_Fragment, { children: [_jsxs("section", { className: 'manage-subscription', "data-node-id": nodeId || stepId, children: [stage === 'subscriptions' ? (_jsxs("div", { className: 'manage-subscription-inner', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.subscriptionsStage.title }), loading ? (_jsx("p", { className: 'manage-subscription-muted', children: content.subscriptionsStage.loadingLabel })) : null, error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, !loading && !error && subscriptions.length === 0 ? (_jsx("p", { className: 'manage-subscription-empty', children: content.subscriptionsStage.emptyLabel })) : null, !loading && activeSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: 'Active Subscriptions', subscriptions: activeSubscriptions, plans: planList, onSelect: handleSubscriptionSelected })) : null, !loading && pastSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: 'Past Subscriptions', subscriptions: pastSubscriptions, plans: planList, onSelect: handleSubscriptionSelected, description: pastSubscriptionsNote, past: true })) : null, _jsxs("p", { className: 'manage-subscription-support', children: [content.subscriptionsStage.supportPrefix, ' ', _jsx("a", { href: `mailto:${supportEmail}`, children: supportEmail })] })] })) : null, stage === 'why' ? (_jsxs("div", { className: 'manage-subscription-inner', children: [_jsx("h1", { className: 'manage-subscription-title is-reason', children: content.whyStage.title }), _jsx("ul", { className: 'manage-subscription-reasons', role: 'list', "aria-label": content.whyStage.reasonsAriaLabel, children: content.whyStage.reasons.map((reason) => (_jsx("li", { children: _jsxs("button", { type: 'button', className: 'manage-subscription-reason', onClick: () => handleReasonSelected(reason.id), children: [_jsx("span", { className: 'manage-subscription-reason-icon', "aria-hidden": true, children: reason.icon }), _jsx("span", { children: reason.label }), _jsx("span", { className: 'manage-subscription-reason-arrow', "aria-hidden": true, children: ">" })] }) }, reason.id))) })] })) : null, stage === 'confirm' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.confirmStage.title }), error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', disabled: !selectedSubscription || !selectedReason || cancelInFlight, onClick: () => {
210
+ return (_jsxs(_Fragment, { children: [_jsxs("section", { className: 'manage-subscription', "data-node-id": nodeId || stepId, children: [stage === 'subscriptions' ? (_jsxs("div", { className: 'manage-subscription-inner', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.subscriptionsStage.title }), loading ? (_jsx("p", { className: 'manage-subscription-muted', children: content.subscriptionsStage.loadingLabel })) : null, error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, !loading && !error && subscriptions.length === 0 ? (_jsx("p", { className: 'manage-subscription-empty', children: content.subscriptionsStage.emptyLabel })) : null, !loading && activeSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: 'Active Subscriptions', subscriptions: activeSubscriptions, plans: planList, onSelect: handleSubscriptionSelected })) : null, !loading && cancelledSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: 'Cancelled Subscriptions', subscriptions: cancelledSubscriptions, plans: planList, onSelect: handleSubscriptionSelected, description: cancelledSubscriptionsNote, cancelled: true })) : null, !loading && pastSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: 'Past Subscriptions', subscriptions: pastSubscriptions, plans: planList, onSelect: handleSubscriptionSelected, description: pastSubscriptionsNote, past: true })) : null, _jsxs("p", { className: 'manage-subscription-support', children: [content.subscriptionsStage.supportPrefix, ' ', _jsx("a", { href: `mailto:${supportEmail}`, children: supportEmail })] })] })) : null, stage === 'why' ? (_jsxs("div", { className: 'manage-subscription-inner', children: [_jsx("h1", { className: 'manage-subscription-title is-reason', children: content.whyStage.title }), _jsx("ul", { className: 'manage-subscription-reasons', role: 'list', "aria-label": content.whyStage.reasonsAriaLabel, children: content.whyStage.reasons.map((reason) => (_jsx("li", { children: _jsxs("button", { type: 'button', className: 'manage-subscription-reason', onClick: () => handleReasonSelected(reason.id), children: [_jsx("span", { className: 'manage-subscription-reason-icon', "aria-hidden": true, children: reason.icon }), _jsx("span", { children: reason.label }), _jsx("span", { className: 'manage-subscription-reason-arrow', "aria-hidden": true, children: ">" })] }) }, reason.id))) })] })) : null, stage === 'confirm' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.confirmStage.title }), error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', disabled: !selectedSubscription || !selectedReason || cancelInFlight, onClick: () => {
196
211
  void handleCancelSubscription();
197
212
  }, children: cancelInFlight
198
213
  ? content.confirmStage.cancellingLabel
199
- : content.confirmStage.cancelLabel })] })) : null, stage === 'done' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.doneStage.title }), _jsx("p", { className: 'manage-subscription-done-copy', children: content.doneStage.cancelledMessage }), cancelledActiveUntilDate ? (_jsxs("p", { className: 'manage-subscription-done-copy is-active-until', children: ["Active until: ", cancelledActiveUntilDate] })) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', onClick: () => {
214
+ : content.confirmStage.cancelLabel })] })) : null, stage === 'done' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.doneStage.title }), _jsx("p", { className: 'manage-subscription-done-status', children: "Cancelled" }), _jsx("p", { className: 'manage-subscription-done-copy', children: content.doneStage.cancelledMessage }), cancelledActiveUntilDate ? (_jsxs("p", { className: 'manage-subscription-done-copy is-active-until', children: ["Active until: ", cancelledActiveUntilDate] })) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', onClick: () => {
200
215
  if (homeStepId) {
201
216
  goToStep(homeStepId);
202
217
  return;
@@ -204,8 +219,16 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
204
219
  setStage('subscriptions');
205
220
  }, children: content.doneStage.returnHomeLabel })] })) : null] }), _jsx("style", { children: manageSubscriptionStyles })] }));
206
221
  }
207
- function SubscriptionSection({ title, subscriptions, plans, onSelect, description, past = false, }) {
208
- return (_jsxs("section", { className: 'manage-subscription-section', "aria-label": title, children: [_jsx("h2", { children: title }), description ? _jsx("p", { className: 'manage-subscription-section-note', children: description }) : null, _jsx("ul", { className: 'manage-subscription-list', role: 'list', children: subscriptions.map((subscription) => (_jsx("li", { children: _jsxs("button", { type: 'button', className: past ? 'manage-subscription-row is-past' : 'manage-subscription-row', disabled: past, onClick: () => onSelect(subscription), children: [_jsx("span", { className: 'manage-subscription-radio', "aria-hidden": true }), _jsxs("span", { className: 'manage-subscription-row-title', children: ["Subscription #", formatSubscriptionNumber(subscription)] }), _jsx("span", { className: 'manage-subscription-row-plan', children: resolvePlanLine(subscription, plans) }), _jsxs("span", { className: 'manage-subscription-row-meta', children: [_jsx("span", { children: resolveBillingDateLabel(subscription, { past }) }), _jsxs("span", { children: ["Created at: ", resolveCreatedDate(subscription)] })] })] }) }, subscription.id))) })] }));
222
+ function SubscriptionSection({ title, subscriptions, plans, onSelect, description, cancelled = false, past = false, }) {
223
+ const isDisabled = cancelled || past;
224
+ const rowClassName = cancelled
225
+ ? 'manage-subscription-row is-cancelled'
226
+ : past
227
+ ? 'manage-subscription-row is-past'
228
+ : 'manage-subscription-row';
229
+ return (_jsxs("section", { className: 'manage-subscription-section', "aria-label": title, children: [_jsx("h2", { children: title }), description ? _jsx("p", { className: 'manage-subscription-section-note', children: description }) : null, _jsx("ul", { className: 'manage-subscription-list', role: 'list', children: subscriptions.map((subscription) => (_jsx("li", { children: _jsxs("button", { type: 'button', className: rowClassName, disabled: isDisabled, onClick: () => onSelect(subscription), children: [_jsx("span", { className: 'manage-subscription-radio', "aria-hidden": true }), _jsxs("span", { className: 'manage-subscription-row-heading', children: [_jsxs("span", { className: 'manage-subscription-row-title', children: ["Subscription #", formatSubscriptionNumber(subscription)] }), _jsx("span", { className: 'manage-subscription-row-status', children: resolveSubscriptionStatusLabel(subscription) })] }), _jsx("span", { className: 'manage-subscription-row-plan', children: resolvePlanLine(subscription, plans) }), _jsxs("span", { className: 'manage-subscription-row-meta', children: [_jsx("span", { className: cancelled ? 'is-active-until' : undefined, children: cancelled
230
+ ? resolveBillingDateLabel(subscription, { cancelled })
231
+ : resolveBillingDateLabel(subscription, { past }) }), _jsxs("span", { children: ["Created at: ", resolveCreatedDate(subscription)] })] })] }) }, subscription.id))) })] }));
209
232
  }
210
233
  const manageSubscriptionStyles = `
211
234
  .manage-subscription {
@@ -295,6 +318,11 @@ const manageSubscriptionStyles = `
295
318
  opacity: 0.82;
296
319
  }
297
320
 
321
+ .manage-subscription-row.is-cancelled {
322
+ border-left-color: var(--color-warning, #f28100);
323
+ cursor: default;
324
+ }
325
+
298
326
  .manage-subscription-radio {
299
327
  width: 14px;
300
328
  height: 14px;
@@ -303,12 +331,31 @@ const manageSubscriptionStyles = `
303
331
  display: block;
304
332
  }
305
333
 
334
+ .manage-subscription-row-heading {
335
+ width: 100%;
336
+ display: flex;
337
+ align-items: center;
338
+ justify-content: space-between;
339
+ gap: 8px;
340
+ }
341
+
306
342
  .manage-subscription-row-title {
307
343
  font-size: 15px;
308
344
  line-height: 1.25;
309
345
  font-weight: 800;
310
346
  }
311
347
 
348
+ .manage-subscription-row-status {
349
+ border-radius: 999px;
350
+ background: color-mix(in srgb, var(--color-warning, #f28100) 16%, var(--color-surface, #fff) 84%);
351
+ color: var(--color-warning, #f28100);
352
+ padding: 3px 8px;
353
+ font-size: 10px;
354
+ line-height: 1.2;
355
+ font-weight: 800;
356
+ text-transform: uppercase;
357
+ }
358
+
312
359
  .manage-subscription-row-plan {
313
360
  font-size: 13px;
314
361
  line-height: 1.2;
@@ -326,6 +373,11 @@ const manageSubscriptionStyles = `
326
373
  line-height: 1.3;
327
374
  }
328
375
 
376
+ .manage-subscription-row-meta .is-active-until {
377
+ color: var(--color-text, #262729);
378
+ font-weight: 800;
379
+ }
380
+
329
381
  .manage-subscription-support {
330
382
  margin: 30px 0 0;
331
383
  color: var(--color-text, #262729);
@@ -371,6 +423,19 @@ const manageSubscriptionStyles = `
371
423
  font-weight: 800;
372
424
  }
373
425
 
426
+ .manage-subscription-done-status {
427
+ width: fit-content;
428
+ margin: 14px auto 0;
429
+ border-radius: 999px;
430
+ background: color-mix(in srgb, var(--color-warning, #f28100) 16%, var(--color-surface, #fff) 84%);
431
+ color: var(--color-warning, #f28100);
432
+ padding: 4px 10px;
433
+ font-size: 11px;
434
+ line-height: 1.2;
435
+ font-weight: 800;
436
+ text-transform: uppercase;
437
+ }
438
+
374
439
  .manage-subscription-reasons {
375
440
  margin: 18px 0 0;
376
441
  padding: 0;
@@ -446,4 +511,5 @@ const manageSubscriptionStyles = `
446
511
  `;
447
512
  export const __manageSubscriptionScreenTestables = {
448
513
  resolveBillingDateLabel,
514
+ resolveSubscriptionStatusLabel,
449
515
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.js",