@sudobility/subscription_pages 0.0.2 → 0.0.3

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/index.js CHANGED
@@ -1,54 +1,54 @@
1
- import { jsx as n } from "react/jsx-runtime";
2
- import { useState as g } from "react";
3
- import { usePackagesByDuration as M, useUserSubscription as z, getSubscriptionInstance as G, refreshSubscription as H, useAllOfferings as Q, useOfferingPackages as V } from "@sudobility/subscription_lib";
4
- import { SubscriptionLayout as F, SubscriptionTile as J, SegmentedControl as K } from "@sudobility/subscription-components";
5
- function ee({
6
- isLoggedIn: i,
7
- onNavigateToLogin: v,
8
- userId: y,
9
- userEmail: b,
10
- featuresByPackage: o,
11
- freeFeatures: l,
12
- title: C = "Choose Your Plan",
13
- className: S
1
+ import { jsx as c } from "react/jsx-runtime";
2
+ import { useState as I } from "react";
3
+ import { usePackagesByDuration as N, useUserSubscription as H, getSubscriptionInstance as J, refreshSubscription as K, useAllOfferings as ee, useOfferingPackages as te, periodToMonths as G } from "@sudobility/subscription_lib";
4
+ import { SubscriptionLayout as y, SubscriptionTile as Q, SegmentedControl as W } from "@sudobility/subscription-components";
5
+ function oe({
6
+ isLoggedIn: n,
7
+ onNavigateToLogin: s,
8
+ userId: f,
9
+ userEmail: o,
10
+ featuresByPackage: i,
11
+ freeFeatures: u,
12
+ title: g = "Choose Your Plan",
13
+ className: m
14
14
  }) {
15
15
  const {
16
- packagesByDuration: m,
17
- availableDurations: p,
18
- isLoading: O,
19
- error: e
20
- } = M(), {
21
- subscription: r,
22
- isLoading: T,
23
- error: s
24
- } = z({ userId: y, userEmail: b }), [B, R] = g(null), [P, k] = g(null), [w, A] = g(!1), h = B ?? p[0] ?? null, U = O || T, E = e || s, D = async (t, u) => {
16
+ packagesByDuration: P,
17
+ availableDurations: l,
18
+ isLoading: k,
19
+ error: B
20
+ } = N(), {
21
+ subscription: e,
22
+ isLoading: t,
23
+ error: F
24
+ } = H({ userId: f, userEmail: o }), [T, h] = I(null), [O, w] = I(null), [A, E] = I(!1), S = T ?? l[0] ?? null, U = k || t, D = B || F, C = async (r, d) => {
25
25
  try {
26
- A(!0), k(null), await G().purchase({
27
- packageId: t,
28
- offeringId: u,
29
- customerEmail: b
30
- }), await H();
31
- } catch (c) {
32
- k(
33
- c instanceof Error ? c.message : "Purchase failed"
26
+ E(!0), w(null), await J().purchase({
27
+ packageId: r,
28
+ offeringId: d,
29
+ customerEmail: o
30
+ }), await K();
31
+ } catch (v) {
32
+ w(
33
+ v instanceof Error ? v.message : "Purchase failed"
34
34
  );
35
35
  } finally {
36
- A(!1);
36
+ E(!1);
37
37
  }
38
- }, Y = () => i ? (r == null ? void 0 : r.isActive) && r.packageId ? {
38
+ }, M = () => n ? (e == null ? void 0 : e.isActive) && e.packageId ? {
39
39
  title: "Free",
40
40
  price: "$0",
41
- features: l ?? [],
41
+ features: u ?? [],
42
42
  ctaButton: {
43
43
  label: "Cancel Subscription",
44
44
  onClick: () => {
45
- r.managementUrl && window.open(r.managementUrl, "_blank");
45
+ e.managementUrl && window.open(e.managementUrl, "_blank");
46
46
  }
47
47
  }
48
48
  } : {
49
49
  title: "Free",
50
50
  price: "$0",
51
- features: l ?? [],
51
+ features: u ?? [],
52
52
  ctaButton: {
53
53
  label: "Current Plan"
54
54
  },
@@ -56,252 +56,273 @@ function ee({
56
56
  } : {
57
57
  title: "Free",
58
58
  price: "$0",
59
- features: l ?? [],
59
+ features: u ?? [],
60
60
  ctaButton: {
61
61
  label: "Try it for Free",
62
- onClick: v
62
+ onClick: s
63
63
  }
64
- }, L = (t) => {
65
- if (!i)
64
+ }, R = (r) => {
65
+ if (!n)
66
66
  return {
67
67
  label: "Log in to Continue",
68
- onClick: v
68
+ onClick: s
69
69
  };
70
- const u = (r == null ? void 0 : r.isActive) && r.packageId;
71
- if ((r == null ? void 0 : r.packageId) !== t.package.packageId)
72
- return u ? {
70
+ const d = (e == null ? void 0 : e.isActive) && e.packageId;
71
+ if ((e == null ? void 0 : e.packageId) !== r.package.packageId)
72
+ return d ? {
73
73
  label: "Change Subscription",
74
- onClick: () => D(t.package.packageId, t.offerId)
74
+ onClick: () => C(r.package.packageId, r.offerId)
75
75
  } : {
76
76
  label: "Subscribe",
77
- onClick: () => D(t.package.packageId, t.offerId)
77
+ onClick: () => C(r.package.packageId, r.offerId)
78
78
  };
79
79
  };
80
80
  if (U)
81
- return /* @__PURE__ */ n(
82
- F,
81
+ return /* @__PURE__ */ c(
82
+ y,
83
83
  {
84
- title: C,
85
- className: S,
84
+ title: g,
85
+ className: m,
86
86
  variant: "cta",
87
- children: /* @__PURE__ */ n("p", { children: "Loading subscription plans..." })
87
+ children: /* @__PURE__ */ c("p", { children: "Loading subscription plans..." })
88
88
  }
89
89
  );
90
- const $ = P ?? (E ? E.message : null), x = i && (r != null && r.isActive) && r.packageId ? {
90
+ const Y = O ?? (D ? D.message : null), L = n && (e != null && e.isActive) && e.packageId ? {
91
91
  isActive: !0,
92
92
  activeContent: {
93
93
  title: "Active Subscription",
94
94
  fields: [
95
- ...r.productId ? [{ label: "Plan", value: r.productId }] : [],
96
- ...r.expirationDate ? [
95
+ ...e.productId ? [{ label: "Plan", value: e.productId }] : [],
96
+ ...e.expirationDate ? [
97
97
  {
98
98
  label: "Expires",
99
- value: r.expirationDate.toLocaleDateString()
99
+ value: e.expirationDate.toLocaleDateString()
100
100
  }
101
101
  ] : [],
102
- ...r.willRenew !== void 0 ? [
102
+ ...e.willRenew !== void 0 ? [
103
103
  {
104
104
  label: "Auto-Renew",
105
- value: r.willRenew ? "Yes" : "No"
105
+ value: e.willRenew ? "Yes" : "No"
106
106
  }
107
107
  ] : []
108
108
  ]
109
109
  }
110
- } : void 0, _ = h ? m[h] ?? [] : [];
111
- return /* @__PURE__ */ n(
112
- F,
110
+ } : void 0, $ = S ? P[S] ?? [] : [];
111
+ return /* @__PURE__ */ c(
112
+ y,
113
113
  {
114
- title: C,
115
- className: S,
114
+ title: g,
115
+ className: m,
116
116
  variant: "cta",
117
- error: $,
118
- currentStatus: x,
119
- freeTileConfig: Y(),
120
- aboveProducts: p.length > 1 ? /* @__PURE__ */ n(
121
- K,
117
+ error: Y,
118
+ currentStatus: L,
119
+ freeTileConfig: M(),
120
+ aboveProducts: l.length > 1 ? /* @__PURE__ */ c(
121
+ W,
122
122
  {
123
- options: p.map((t) => ({
124
- value: t,
125
- label: t.charAt(0).toUpperCase() + t.slice(1)
123
+ options: l.map((r) => ({
124
+ value: r,
125
+ label: r.charAt(0).toUpperCase() + r.slice(1)
126
126
  })),
127
- value: h ?? p[0],
128
- onChange: (t) => R(t)
127
+ value: S ?? l[0],
128
+ onChange: (r) => h(r)
129
129
  }
130
130
  ) : void 0,
131
- children: _.map((t) => {
132
- var I;
133
- const u = i && (r == null ? void 0 : r.isActive) && r.packageId === t.package.packageId, c = L(t);
134
- return /* @__PURE__ */ n(
135
- J,
131
+ children: $.map((r) => {
132
+ var x;
133
+ const d = n && (e == null ? void 0 : e.isActive) && e.packageId === r.package.packageId, v = R(r);
134
+ return /* @__PURE__ */ c(
135
+ Q,
136
136
  {
137
- id: t.package.packageId,
138
- title: t.package.name,
139
- price: ((I = t.package.product) == null ? void 0 : I.priceString) ?? "$0",
140
- periodLabel: t.package.product ? `/${t.package.product.period}` : void 0,
141
- features: (o == null ? void 0 : o[t.package.packageId]) ?? [],
137
+ id: r.package.packageId,
138
+ title: r.package.name,
139
+ price: ((x = r.package.product) == null ? void 0 : x.priceString) ?? "$0",
140
+ periodLabel: r.package.product ? `/${r.package.product.period}` : void 0,
141
+ features: (i == null ? void 0 : i[r.package.packageId]) ?? [],
142
142
  isSelected: !1,
143
143
  onSelect: () => {
144
144
  },
145
- isCurrentPlan: u,
146
- ctaButton: c,
147
- disabled: w
145
+ isCurrentPlan: d,
146
+ ctaButton: v,
147
+ disabled: A
148
148
  },
149
- `${t.offerId}-${t.package.packageId}`
149
+ `${r.offerId}-${r.package.packageId}`
150
150
  );
151
151
  })
152
152
  }
153
153
  );
154
154
  }
155
- function re({
156
- isLoggedIn: i,
157
- onNavigateToLogin: v,
158
- userId: y,
159
- userEmail: b,
160
- featuresByPackage: o,
161
- freeFeatures: l,
162
- title: C = "Choose Your Plan",
163
- className: S
155
+ function re(n, s) {
156
+ if (!n.product || !s.product) return null;
157
+ const f = G(n.product.period), o = G(s.product.period);
158
+ if (f <= 0 || o <= 0 || f === 1 / 0 || o === 1 / 0 || f === o) return null;
159
+ const i = n.product.price / f, u = s.product.price / o;
160
+ if (i <= 0) return null;
161
+ const g = Math.round(
162
+ (i - u) / i * 100
163
+ );
164
+ return g > 0 ? g : null;
165
+ }
166
+ function le({
167
+ isLoggedIn: n,
168
+ onNavigateToLogin: s,
169
+ userId: f,
170
+ userEmail: o,
171
+ featuresByPackage: i,
172
+ freeFeatures: u,
173
+ title: g = "Choose Your Plan",
174
+ className: m,
175
+ t: P
164
176
  }) {
165
- var q;
166
- const {
167
- offerings: m,
168
- isLoading: p,
169
- error: O
170
- } = Q(), {
171
- subscription: e,
172
- isLoading: r,
177
+ var V;
178
+ const l = (a, p) => P ? P(a, p) : p, {
179
+ offerings: k,
180
+ isLoading: B,
181
+ error: e
182
+ } = ee(), {
183
+ subscription: t,
184
+ isLoading: F,
173
185
  error: T
174
- } = z({ userId: y, userEmail: b }), [s, B] = g("free"), [R, P] = g(null), [k, w] = g(!1), A = ((q = m[0]) == null ? void 0 : q.offerId) ?? "", h = s !== "free" ? s : A, {
175
- packages: U,
176
- isLoading: E,
177
- error: D
178
- } = V(h), Y = p || r || E, L = O || T || D, $ = async (a, f) => {
186
+ } = H({ userId: f, userEmail: o }), [h, O] = I("free"), [w, A] = I(null), [E, S] = I(!1), U = ((V = k[0]) == null ? void 0 : V.offerId) ?? "", D = h !== "free" ? h : U, {
187
+ packages: C,
188
+ isLoading: M,
189
+ error: R
190
+ } = te(D), Y = B || F || M, L = e || T || R, $ = async (a, p) => {
179
191
  try {
180
- w(!0), P(null), await G().purchase({
192
+ S(!0), A(null), await J().purchase({
181
193
  packageId: a,
182
- offeringId: f,
183
- customerEmail: b
184
- }), await H();
185
- } catch (d) {
186
- P(
187
- d instanceof Error ? d.message : "Purchase failed"
194
+ offeringId: p,
195
+ customerEmail: o
196
+ }), await K();
197
+ } catch (b) {
198
+ A(
199
+ b instanceof Error ? b.message : "Purchase failed"
188
200
  );
189
201
  } finally {
190
- w(!1);
202
+ S(!1);
191
203
  }
192
- }, x = [
193
- { value: "free", label: "Free" },
194
- ...m.map((a) => ({
204
+ }, r = [
205
+ { value: "free", label: l("free", "Free") },
206
+ ...k.map((a) => ({
195
207
  value: a.offerId,
196
- label: a.offerId
208
+ label: l(a.offerId, a.offerId.charAt(0).toUpperCase() + a.offerId.slice(1))
197
209
  }))
198
- ], _ = () => i ? (e == null ? void 0 : e.isActive) && e.packageId ? {
199
- title: "Free",
210
+ ], d = C[0] ?? null, v = () => n ? (t == null ? void 0 : t.isActive) && t.packageId ? {
211
+ title: l("free", "Free"),
200
212
  price: "$0",
201
- features: l ?? [],
213
+ features: u ?? [],
202
214
  ctaButton: {
203
215
  label: "Cancel Subscription",
204
216
  onClick: () => {
205
- e.managementUrl && window.open(e.managementUrl, "_blank");
217
+ t.managementUrl && window.open(t.managementUrl, "_blank");
206
218
  }
207
219
  }
208
220
  } : {
209
- title: "Free",
221
+ title: l("free", "Free"),
210
222
  price: "$0",
211
- features: l ?? [],
223
+ features: u ?? [],
212
224
  ctaButton: {
213
225
  label: "Current Plan"
214
226
  },
215
227
  topBadge: { text: "Current Plan", color: "blue" }
216
228
  } : {
217
- title: "Free",
229
+ title: l("free", "Free"),
218
230
  price: "$0",
219
- features: l ?? [],
231
+ features: u ?? [],
220
232
  ctaButton: {
221
233
  label: "Try it for Free",
222
- onClick: v
234
+ onClick: s
223
235
  }
224
- }, t = (a, f) => {
225
- if (!i)
236
+ }, x = (a, p) => {
237
+ if (!n)
226
238
  return {
227
239
  label: "Log in to Continue",
228
- onClick: v
240
+ onClick: s
229
241
  };
230
- const d = (e == null ? void 0 : e.isActive) && e.packageId;
231
- if ((e == null ? void 0 : e.packageId) !== a.packageId)
232
- return d ? {
242
+ const b = (t == null ? void 0 : t.isActive) && t.packageId;
243
+ if ((t == null ? void 0 : t.packageId) !== a.packageId)
244
+ return b ? {
233
245
  label: "Change Subscription",
234
- onClick: () => $(a.packageId, f)
246
+ onClick: () => $(a.packageId, p)
235
247
  } : {
236
248
  label: "Subscribe",
237
- onClick: () => $(a.packageId, f)
249
+ onClick: () => $(a.packageId, p)
238
250
  };
239
251
  };
240
252
  if (Y)
241
- return /* @__PURE__ */ n(
242
- F,
253
+ return /* @__PURE__ */ c(
254
+ y,
243
255
  {
244
- title: C,
245
- className: S,
256
+ title: g,
257
+ className: m,
246
258
  variant: "cta",
247
- children: /* @__PURE__ */ n("p", { children: "Loading subscription plans..." })
259
+ children: /* @__PURE__ */ c("p", { children: "Loading subscription plans..." })
248
260
  }
249
261
  );
250
- const u = R ?? (L ? L.message : null), c = i && (e != null && e.isActive) && e.packageId ? {
262
+ const X = w ?? (L ? L.message : null), Z = n && (t != null && t.isActive) && t.packageId ? {
251
263
  isActive: !0,
252
264
  activeContent: {
253
265
  title: "Active Subscription",
254
266
  fields: [
255
- ...e.productId ? [{ label: "Plan", value: e.productId }] : [],
256
- ...e.expirationDate ? [
267
+ ...t.productId ? [{ label: "Plan", value: t.productId }] : [],
268
+ ...t.expirationDate ? [
257
269
  {
258
270
  label: "Expires",
259
- value: e.expirationDate.toLocaleDateString()
271
+ value: t.expirationDate.toLocaleDateString()
260
272
  }
261
273
  ] : [],
262
- ...e.willRenew !== void 0 ? [
274
+ ...t.willRenew !== void 0 ? [
263
275
  {
264
276
  label: "Auto-Renew",
265
- value: e.willRenew ? "Yes" : "No"
277
+ value: t.willRenew ? "Yes" : "No"
266
278
  }
267
279
  ] : []
268
280
  ]
269
281
  }
270
- } : void 0, I = s === "free";
271
- return /* @__PURE__ */ n(
272
- F,
282
+ } : void 0, _ = h === "free";
283
+ return /* @__PURE__ */ c(
284
+ y,
273
285
  {
274
- title: C,
275
- className: S,
286
+ title: g,
287
+ className: m,
276
288
  variant: "cta",
277
- error: u,
278
- currentStatus: c,
279
- freeTileConfig: I ? _() : void 0,
280
- aboveProducts: x.length > 1 ? /* @__PURE__ */ n(
281
- K,
289
+ error: X,
290
+ currentStatus: Z,
291
+ freeTileConfig: _ ? v() : void 0,
292
+ aboveProducts: r.length > 1 ? /* @__PURE__ */ c("div", { className: "flex justify-center", children: /* @__PURE__ */ c(
293
+ W,
282
294
  {
283
- options: x,
284
- value: s,
285
- onChange: B
295
+ options: r,
296
+ value: h,
297
+ onChange: O
286
298
  }
287
- ) : void 0,
288
- children: !I && U.map((a) => {
289
- var j;
290
- const f = i && (e == null ? void 0 : e.isActive) && e.packageId === a.packageId, d = t(a, s);
291
- return /* @__PURE__ */ n(
292
- J,
299
+ ) }) : void 0,
300
+ children: !_ && C.map((a) => {
301
+ var q;
302
+ const p = n && (t == null ? void 0 : t.isActive) && t.packageId === a.packageId, b = x(a, h);
303
+ let j;
304
+ if (d && a.packageId !== d.packageId) {
305
+ const z = re(d, a);
306
+ z !== null && (j = {
307
+ text: `Save ${z}%`,
308
+ isBestValue: a === C[C.length - 1]
309
+ });
310
+ }
311
+ return /* @__PURE__ */ c(
312
+ Q,
293
313
  {
294
314
  id: a.packageId,
295
315
  title: a.name,
296
- price: ((j = a.product) == null ? void 0 : j.priceString) ?? "$0",
316
+ price: ((q = a.product) == null ? void 0 : q.priceString) ?? "$0",
297
317
  periodLabel: a.product ? `/${a.product.period}` : void 0,
298
- features: (o == null ? void 0 : o[a.packageId]) ?? [],
318
+ features: (i == null ? void 0 : i[a.packageId]) ?? [],
299
319
  isSelected: !1,
300
320
  onSelect: () => {
301
321
  },
302
- isCurrentPlan: f,
303
- ctaButton: d,
304
- disabled: k
322
+ isCurrentPlan: p,
323
+ ctaButton: b,
324
+ disabled: E,
325
+ discountBadge: j
305
326
  },
306
327
  a.packageId
307
328
  );
@@ -310,6 +331,6 @@ function re({
310
331
  );
311
332
  }
312
333
  export {
313
- ee as SubscriptionByDurationPage,
314
- re as SubscriptionByOfferPage
334
+ oe as SubscriptionByDurationPage,
335
+ le as SubscriptionByOfferPage
315
336
  };
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * Subscription page that organizes packages by offering.
5
5
  * Uses a SegmentedControl to switch between offerings (with a 'Free' option).
6
+ * Packages within each offer are sorted by duration (short to long).
7
+ * Savings are calculated relative to the shortest duration package.
6
8
  */
7
9
  export interface SubscriptionByOfferPageProps {
8
10
  /** Whether the user is logged in */
@@ -21,5 +23,11 @@ export interface SubscriptionByOfferPageProps {
21
23
  title?: string;
22
24
  /** Additional CSS classes */
23
25
  className?: string;
26
+ /**
27
+ * Translation function for localizing offer names and labels.
28
+ * Keys passed: offer identifiers (e.g. "basic", "premium"), "free".
29
+ * Falls back to the fallback string if not provided.
30
+ */
31
+ t?: (key: string, fallback: string) => string;
24
32
  }
25
- export declare function SubscriptionByOfferPage({ isLoggedIn, onNavigateToLogin, userId, userEmail, featuresByPackage, freeFeatures, title, className, }: SubscriptionByOfferPageProps): import("react/jsx-runtime").JSX.Element;
33
+ export declare function SubscriptionByOfferPage({ isLoggedIn, onNavigateToLogin, userId, userEmail, featuresByPackage, freeFeatures, title, className, t: translate, }: SubscriptionByOfferPageProps): import("react/jsx-runtime").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/subscription_pages",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Subscription page components for React web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,13 +28,13 @@
28
28
  "peerDependencies": {
29
29
  "react": "^18.0.0 || ^19.0.0",
30
30
  "react-dom": "^18.0.0 || ^19.0.0",
31
- "@sudobility/subscription_lib": "^0.0.25",
31
+ "@sudobility/subscription_lib": "^0.0.26",
32
32
  "@sudobility/subscription-components": "^1.0.27",
33
33
  "@sudobility/types": "^1.9.58"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@sudobility/subscription-components": "^1.0.27",
37
- "@sudobility/subscription_lib": "^0.0.25",
37
+ "@sudobility/subscription_lib": "^0.0.26",
38
38
  "@sudobility/types": "^1.9.58",
39
39
  "@testing-library/jest-dom": "^6.0.0",
40
40
  "@testing-library/react": "^16.0.0",