@mesub/react 0.1.0 → 0.1.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.d.cts CHANGED
@@ -33,8 +33,12 @@ interface MesubPlan {
33
33
  ends_at: string | null;
34
34
  }
35
35
  type MesubSubscriptionStatus = 'pending' | 'active' | 'cancelled' | 'unpaid' | 'stopped' | 'ended' | 'failed' | 'expired' | 'superseded';
36
- /** Why charges are held back: the merchant's own setup by name, `mesub` for any cause of Mesub's. */
37
- type MesubBlockedReason = 'puller_not_allowed' | 'receiver_not_allowed' | 'receiver_account_missing' | 'receiver_account_frozen' | 'mint_gone' | 'mesub';
36
+ /**
37
+ * Why charges are held back: the merchant's own setup by name (the last two
38
+ * the token issuer's to lift), `mesub` for any cause of Mesub's. The widget
39
+ * words every one the same to the subscriber.
40
+ */
41
+ type MesubBlockedReason = 'puller_not_allowed' | 'receiver_not_allowed' | 'receiver_account_missing' | 'receiver_account_frozen' | 'receiver_account_needs_memo' | 'receiver_account_confidential_only' | 'mint_gone' | 'mint_paused' | 'mint_transfer_hook' | 'mesub';
38
42
  /**
39
43
  * Why a `stopped` one stopped (Mesub-io/backend#402): every retry failed, the
40
44
  * Free date to pay by passed, or the wallet closed the account that lets
@@ -59,6 +63,8 @@ interface MesubSubscription {
59
63
  current_period_end: string | null;
60
64
  next_charge_at: string | null;
61
65
  next_retry_at: string | null;
66
+ next_retry_number?: number | null;
67
+ retries_allowed?: number | null;
62
68
  retry_deadline: string | null;
63
69
  access_until: string | null;
64
70
  closable?: boolean;
@@ -125,6 +131,7 @@ interface PayNowResult {
125
131
  interface UseSubscriptionsResult {
126
132
  state: 'loading' | 'ready' | 'signed-out' | 'error';
127
133
  subscriptions: MesubHeldSubscription[];
134
+ hasMore: boolean;
128
135
  error: string | null;
129
136
  reload(): Promise<void>;
130
137
  payNow(id: string): Promise<PayNowResult>;
package/dist/index.d.ts CHANGED
@@ -33,8 +33,12 @@ interface MesubPlan {
33
33
  ends_at: string | null;
34
34
  }
35
35
  type MesubSubscriptionStatus = 'pending' | 'active' | 'cancelled' | 'unpaid' | 'stopped' | 'ended' | 'failed' | 'expired' | 'superseded';
36
- /** Why charges are held back: the merchant's own setup by name, `mesub` for any cause of Mesub's. */
37
- type MesubBlockedReason = 'puller_not_allowed' | 'receiver_not_allowed' | 'receiver_account_missing' | 'receiver_account_frozen' | 'mint_gone' | 'mesub';
36
+ /**
37
+ * Why charges are held back: the merchant's own setup by name (the last two
38
+ * the token issuer's to lift), `mesub` for any cause of Mesub's. The widget
39
+ * words every one the same to the subscriber.
40
+ */
41
+ type MesubBlockedReason = 'puller_not_allowed' | 'receiver_not_allowed' | 'receiver_account_missing' | 'receiver_account_frozen' | 'receiver_account_needs_memo' | 'receiver_account_confidential_only' | 'mint_gone' | 'mint_paused' | 'mint_transfer_hook' | 'mesub';
38
42
  /**
39
43
  * Why a `stopped` one stopped (Mesub-io/backend#402): every retry failed, the
40
44
  * Free date to pay by passed, or the wallet closed the account that lets
@@ -59,6 +63,8 @@ interface MesubSubscription {
59
63
  current_period_end: string | null;
60
64
  next_charge_at: string | null;
61
65
  next_retry_at: string | null;
66
+ next_retry_number?: number | null;
67
+ retries_allowed?: number | null;
62
68
  retry_deadline: string | null;
63
69
  access_until: string | null;
64
70
  closable?: boolean;
@@ -125,6 +131,7 @@ interface PayNowResult {
125
131
  interface UseSubscriptionsResult {
126
132
  state: 'loading' | 'ready' | 'signed-out' | 'error';
127
133
  subscriptions: MesubHeldSubscription[];
134
+ hasMore: boolean;
128
135
  error: string | null;
129
136
  reload(): Promise<void>;
130
137
  payNow(id: string): Promise<PayNowResult>;
package/dist/index.js CHANGED
@@ -41,6 +41,10 @@ function paidOf(answer) {
41
41
  const whole = Array.isArray(answer.payments) && answer.payments.length < LISTED_AT_MOST;
42
42
  return whole ? read(answer.listed_paid) : null;
43
43
  }
44
+ function paymentsErrorOf(answer) {
45
+ const error = answer.payments_error;
46
+ return isObject(error) && typeof error.code === "string" && typeof error.message === "string" ? { code: error.code, message: error.message } : null;
47
+ }
44
48
  function createApi(options) {
45
49
  const base = options.endpoint.replace(/\/+$/, "");
46
50
  const doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
@@ -111,7 +115,11 @@ function createApi(options) {
111
115
  ),
112
116
  subscriptions: async () => {
113
117
  const answer = await request("GET", "/subscriptions", void 0, REQUEST_TIMEOUT_MS);
114
- return Array.isArray(answer.subscriptions) ? answer.subscriptions : [];
118
+ return {
119
+ subscriptions: Array.isArray(answer.subscriptions) ? answer.subscriptions : [],
120
+ // An older server does not say: read as whole.
121
+ has_more: answer.has_more === true
122
+ };
115
123
  },
116
124
  subscription: async (id) => {
117
125
  const answer = await request("GET", at(id), void 0, REQUEST_TIMEOUT_MS);
@@ -119,7 +127,8 @@ function createApi(options) {
119
127
  subscription: answer.subscription,
120
128
  upcoming: Array.isArray(answer.upcoming) ? answer.upcoming : [],
121
129
  payments: Array.isArray(answer.payments) ? answer.payments : null,
122
- paid: paidOf(answer)
130
+ paid: paidOf(answer),
131
+ payments_error: paymentsErrorOf(answer)
123
132
  };
124
133
  },
125
134
  prepare: async (plan, wallet) => await request(
@@ -427,6 +436,14 @@ function moment(iso, now = Date.now()) {
427
436
  function shortMoment(iso, now = Date.now()) {
428
437
  return written(iso, { month: "short", day: "numeric" }, now);
429
438
  }
439
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
440
+ var two = (n) => String(n).padStart(2, "0");
441
+ function planEnd(iso) {
442
+ if (!iso) return null;
443
+ const date = new Date(iso);
444
+ if (Number.isNaN(date.getTime())) return null;
445
+ return `${MONTHS[date.getUTCMonth()]} ${date.getUTCDate()}, ${date.getUTCFullYear()}, ${two(date.getUTCHours())}:${two(date.getUTCMinutes())} UTC`;
446
+ }
430
447
  function shortAddress(address) {
431
448
  return `${address.slice(0, 4)}\u2026${address.slice(-4)}`;
432
449
  }
@@ -1396,7 +1413,7 @@ function planScreen(loaded, titleId, reload, close) {
1396
1413
  function stageScreen(props) {
1397
1414
  const { view, plan, chain, titleId } = props;
1398
1415
  const { text: price, title: exact } = briefPrice(plan);
1399
- const ends = moment(plan.ends_at);
1416
+ const ends = planEnd(plan.ends_at);
1400
1417
  const single = view.name === "review" ? singleCharge(plan, view.prepared.terms.message) : view.name === "signing" || view.name === "subscribed" ? view.single : singleCharge(plan);
1401
1418
  const every = single ? "once" : cadence(plan.period_hours);
1402
1419
  const merchant = /* @__PURE__ */ jsx4(
@@ -1596,7 +1613,7 @@ function stageScreen(props) {
1596
1613
  };
1597
1614
  }
1598
1615
  const nextCharge = view.single ? null : moment(view.subscription.next_charge_at);
1599
- const accessUntil = view.single ? moment(view.subscription.access_until) ?? ends : null;
1616
+ const accessUntil = view.single ? ends ?? moment(view.subscription.access_until) : null;
1600
1617
  const manage = manageLink(props.manageUrl, view.cancelUrl);
1601
1618
  return {
1602
1619
  step: "subscribed",
@@ -1717,7 +1734,7 @@ function consequence(action, subscription, planEndsAt) {
1717
1734
  }
1718
1735
  if (action === "resume") {
1719
1736
  if (end && until && noChargeAt(Date.parse(end), planEndsAt)) {
1720
- return `It runs again until ${until}, when the plan ends. Nothing more is charged.`;
1737
+ return `It runs again until ${planEnd(planEndsAt)}, when the plan ends. Nothing more is charged.`;
1721
1738
  }
1722
1739
  return until ? `Charges start again on ${until}, as before.` : "Charges start again, as before.";
1723
1740
  }
@@ -2153,6 +2170,7 @@ function payableOf(row, now) {
2153
2170
  const deadline = Date.parse(row.retry_deadline ?? "");
2154
2171
  return Number.isNaN(deadline) || deadline > now;
2155
2172
  }
2173
+ var MORE_NOT_LISTED = "Only your most recent subscriptions are listed here.";
2156
2174
  var PAY_NOW_SENT = "Payment sent. It shows here once the network confirms it.";
2157
2175
  function payNowRefusal(failure) {
2158
2176
  if (!(failure instanceof MesubClientError)) return "Something went wrong.";
@@ -2178,6 +2196,7 @@ function useSubscriptions() {
2178
2196
  const { api, revision, manage: open } = useMesubInternal();
2179
2197
  const [state, setState] = useState4("loading");
2180
2198
  const [rows, setRows] = useState4([]);
2199
+ const [hasMore, setHasMore] = useState4(false);
2181
2200
  const [error, setError] = useState4(null);
2182
2201
  const [now, setNow] = useState4(() => Date.now());
2183
2202
  const read = useRef4(0);
@@ -2193,7 +2212,8 @@ function useSubscriptions() {
2193
2212
  try {
2194
2213
  const served = await api.subscriptions();
2195
2214
  if (!alive.current || id !== read.current) return;
2196
- setRows(served.filter((row) => HELD.has(row.status)));
2215
+ setRows(served.subscriptions.filter((row) => HELD.has(row.status)));
2216
+ setHasMore(served.has_more);
2197
2217
  setNow(Date.now());
2198
2218
  setError(null);
2199
2219
  setState("ready");
@@ -2201,6 +2221,7 @@ function useSubscriptions() {
2201
2221
  if (!alive.current || id !== read.current) return;
2202
2222
  if (signedOut(failure)) {
2203
2223
  setRows([]);
2224
+ setHasMore(false);
2204
2225
  setError(null);
2205
2226
  setState("signed-out");
2206
2227
  return;
@@ -2254,7 +2275,7 @@ function useSubscriptions() {
2254
2275
  },
2255
2276
  [api, reload]
2256
2277
  );
2257
- return { state, subscriptions, error, reload, payNow, manage };
2278
+ return { state, subscriptions, hasMore, error, reload, payNow, manage };
2258
2279
  }
2259
2280
  var STATUS = {
2260
2281
  active: "Active",
@@ -2386,7 +2407,7 @@ function PayNow({ pay }) {
2386
2407
  }
2387
2408
  function ManageSubscriptions({ onChanged, ...rest }) {
2388
2409
  const { theme } = useMesubInternal();
2389
- const { state, subscriptions, error, reload, payNow, manage } = useSubscriptions();
2410
+ const { state, subscriptions, hasMore, error, reload, payNow, manage } = useSubscriptions();
2390
2411
  const plans = usePlans(subscriptions.flatMap((row) => row.plan ? [row.plan] : []));
2391
2412
  const [retrying, setRetrying] = useState4(false);
2392
2413
  async function act(id) {
@@ -2421,7 +2442,7 @@ function ManageSubscriptions({ onChanged, ...rest }) {
2421
2442
  }
2422
2443
  )
2423
2444
  ] });
2424
- } else if (subscriptions.length === 0) {
2445
+ } else if (subscriptions.length === 0 && !hasMore) {
2425
2446
  body = /* @__PURE__ */ jsx6("p", { "data-mesub-empty": "none", children: "You have no subscription yet." });
2426
2447
  } else {
2427
2448
  body = /* @__PURE__ */ jsx6("ul", { "data-mesub-subscription-list": "", "aria-label": "Your subscriptions", children: subscriptions.map((row) => {
@@ -2483,14 +2504,17 @@ function ManageSubscriptions({ onChanged, ...rest }) {
2483
2504
  ] }, row.id);
2484
2505
  }) });
2485
2506
  }
2486
- return /* @__PURE__ */ jsx6(
2507
+ return /* @__PURE__ */ jsxs5(
2487
2508
  "div",
2488
2509
  {
2489
2510
  ...rest,
2490
2511
  "data-mesub-subscriptions": "",
2491
2512
  "data-mesub-state": state,
2492
2513
  "data-mesub-theme": theme,
2493
- children: body
2514
+ children: [
2515
+ body,
2516
+ state === "ready" && hasMore ? /* @__PURE__ */ jsx6("p", { "data-mesub-subscription-note": "", children: MORE_NOT_LISTED }) : null
2517
+ ]
2494
2518
  }
2495
2519
  );
2496
2520
  }
@@ -2555,6 +2579,7 @@ function useDetail(row) {
2555
2579
  }, [api, id, revision]);
2556
2580
  return detail?.subscription.id === id ? detail : null;
2557
2581
  }
2582
+ var PAYMENTS_UNREAD = "Past payments could not be read just now.";
2558
2583
  var PAST_SHOWN = 5;
2559
2584
  var PAST_STEP = 10;
2560
2585
  function SubscriptionDialog({
@@ -2564,7 +2589,7 @@ function SubscriptionDialog({
2564
2589
  const [shown, setShown] = useState5(PAST_SHOWN);
2565
2590
  const titleId = useId3();
2566
2591
  const { chain } = useMesubInternal();
2567
- const { state, subscriptions, error, reload, payNow, manage } = useSubscriptions();
2592
+ const { state, subscriptions, hasMore, error, reload, payNow, manage } = useSubscriptions();
2568
2593
  const row = pickSubscription(subscriptions, slug) ?? null;
2569
2594
  const plan = usePlan(row?.plan);
2570
2595
  const detail = useDetail(row);
@@ -2588,7 +2613,7 @@ function SubscriptionDialog({
2588
2613
  } else if (!row) {
2589
2614
  body = /* @__PURE__ */ jsxs6(Fragment6, { children: [
2590
2615
  /* @__PURE__ */ jsx7("h2", { id: titleId, children: "No subscription yet" }),
2591
- /* @__PURE__ */ jsx7("p", { children: "You are not subscribed to this plan." })
2616
+ /* @__PURE__ */ jsx7("p", { children: hasMore ? "None of your most recent subscriptions is to this plan." : "You are not subscribed to this plan." })
2592
2617
  ] });
2593
2618
  } else {
2594
2619
  const name = plan?.name ?? row.plan ?? "Subscription";
@@ -2597,6 +2622,7 @@ function SubscriptionDialog({
2597
2622
  const note = noteOf(row);
2598
2623
  const upcoming = detail?.upcoming ?? [];
2599
2624
  const past = detail?.payments ?? [];
2625
+ const unread = detail?.payments === null && detail.payments_error !== null;
2600
2626
  const total = detail?.paid?.amount ? paidBrief(detail.paid.amount, plan, formatAmount) : null;
2601
2627
  const rate = plan ? briefPrice(plan) : null;
2602
2628
  const due = (display) => display ? `${grouped(display)}${symbolOf(plan)}` : "";
@@ -2635,9 +2661,9 @@ function SubscriptionDialog({
2635
2661
  /* @__PURE__ */ jsx7("dt", { children: "Total paid" }),
2636
2662
  /* @__PURE__ */ jsx7("dd", { title: total.title, children: total.text })
2637
2663
  ] }) : null,
2638
- moment(plan?.ends_at) ? /* @__PURE__ */ jsxs6("div", { "data-mesub-row": "", children: [
2664
+ planEnd(plan?.ends_at) ? /* @__PURE__ */ jsxs6("div", { "data-mesub-row": "", children: [
2639
2665
  /* @__PURE__ */ jsx7("dt", { children: "Plan ends" }),
2640
- /* @__PURE__ */ jsx7("dd", { children: moment(plan?.ends_at) })
2666
+ /* @__PURE__ */ jsx7("dd", { children: planEnd(plan?.ends_at) })
2641
2667
  ] }) : null,
2642
2668
  closeFrom(row) ? /* @__PURE__ */ jsxs6("div", { "data-mesub-row": "", children: [
2643
2669
  /* @__PURE__ */ jsx7("dt", { children: "Close from" }),
@@ -2648,7 +2674,7 @@ function SubscriptionDialog({
2648
2674
  /* @__PURE__ */ jsx7("dd", { title: row.wallet, children: shortAddress(row.wallet) })
2649
2675
  ] })
2650
2676
  ] }),
2651
- upcoming.length > 0 || past.length > 0 ? (
2677
+ upcoming.length > 0 || past.length > 0 || unread ? (
2652
2678
  // Closed until asked for, as the terms are on the checkout: same toggle.
2653
2679
  /* @__PURE__ */ jsxs6("details", { "data-mesub-terms": "", "data-mesub-payments": "", children: [
2654
2680
  /* @__PURE__ */ jsx7("summary", { children: "Payments" }),
@@ -2670,6 +2696,10 @@ function SubscriptionDialog({
2670
2696
  `next-${next.due_at}`
2671
2697
  )) })
2672
2698
  ] }) : null,
2699
+ unread ? /* @__PURE__ */ jsxs6("section", { "aria-label": "Past payments", children: [
2700
+ /* @__PURE__ */ jsx7("h3", { children: "Past" }),
2701
+ /* @__PURE__ */ jsx7("p", { "data-mesub-payments-error": "", children: PAYMENTS_UNREAD })
2702
+ ] }) : null,
2673
2703
  past.length > 0 ? /* @__PURE__ */ jsxs6("section", { "aria-label": "Past payments", children: [
2674
2704
  /* @__PURE__ */ jsx7("h3", { children: "Past" }),
2675
2705
  /* @__PURE__ */ jsx7("ul", { children: past.slice(0, shown).map((payment) => {
@@ -2752,7 +2782,7 @@ function SubscriptionDialog({
2752
2782
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2753
2783
  function MesubProvider({
2754
2784
  endpoint,
2755
- chain = "solana:devnet",
2785
+ chain = "solana:mainnet",
2756
2786
  fetch,
2757
2787
  theme,
2758
2788
  manageUrl,