@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/README.md +27 -6
- package/dist/index.cjs +47 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +47 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/styles.css +10 -1
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
|
-
/**
|
|
37
|
-
|
|
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
|
-
/**
|
|
37
|
-
|
|
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
|
|
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 =
|
|
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)
|
|
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 ${
|
|
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__ */
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
2785
|
+
chain = "solana:mainnet",
|
|
2756
2786
|
fetch,
|
|
2757
2787
|
theme,
|
|
2758
2788
|
manageUrl,
|