@blocklet/payment-react 1.18.18 → 1.18.20
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 +23 -13
- package/es/components/livemode.js +2 -2
- package/es/hooks/keyboard.d.ts +44 -0
- package/es/hooks/keyboard.js +86 -0
- package/es/index.d.ts +1 -0
- package/es/index.js +1 -0
- package/es/locales/en.js +3 -2
- package/es/locales/zh.js +2 -1
- package/es/payment/donation-form.js +51 -14
- package/es/payment/form/index.js +11 -0
- package/es/payment/product-donation.js +223 -115
- package/lib/components/livemode.js +2 -2
- package/lib/hooks/keyboard.d.ts +44 -0
- package/lib/hooks/keyboard.js +85 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +12 -0
- package/lib/locales/en.js +3 -2
- package/lib/locales/zh.js +2 -1
- package/lib/payment/donation-form.js +42 -7
- package/lib/payment/form/index.js +11 -0
- package/lib/payment/product-donation.js +148 -40
- package/package.json +6 -6
- package/src/components/livemode.tsx +2 -2
- package/src/hooks/keyboard.ts +159 -0
- package/src/index.ts +1 -0
- package/src/locales/en.tsx +3 -2
- package/src/locales/zh.tsx +2 -1
- package/src/payment/donation-form.tsx +44 -11
- package/src/payment/form/index.tsx +20 -0
- package/src/payment/product-donation.tsx +180 -72
package/README.md
CHANGED
|
@@ -323,18 +323,20 @@ function DonationPage() {
|
|
|
323
323
|
|
|
324
324
|
- `OverdueInvoicePayment` component
|
|
325
325
|
- Display overdue invoices for a subscription, and support batch payment
|
|
326
|
-
-
|
|
327
|
-
- `subscriptionId`: [
|
|
328
|
-
- `
|
|
329
|
-
- `
|
|
330
|
-
- `
|
|
331
|
-
- `
|
|
332
|
-
|
|
333
|
-
- `
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
326
|
+
- Props:
|
|
327
|
+
- `subscriptionId`: [Optional] The subscription ID
|
|
328
|
+
- `customerId`: [Optional] The customer ID or DID
|
|
329
|
+
- `onPaid`: [Optional] Callback function called after successful payment, receives `(id, currencyId, type)`
|
|
330
|
+
- `mode`: [Optional] Component mode, `default` or `custom` (default is `default`)
|
|
331
|
+
- `dialogProps`: [Optional] Dialog properties, default is `{ open: true }`
|
|
332
|
+
- `detailLinkOptions`: [Optional] Detail link options, format: `{ enabled, onClick, title }`
|
|
333
|
+
- `successToast`: [Optional] Whether to show success toast, default is `true`
|
|
334
|
+
- `children`: [Optional] Custom rendering function, used only when `mode="custom"`
|
|
335
|
+
|
|
336
|
+
- Custom Mode:
|
|
337
|
+
- `children` function receives two parameters:
|
|
338
|
+
- `handlePay`: Function to start the payment process
|
|
339
|
+
- `data`: Payment data (includes `subscription`, `summary`, `invoices`, `subscriptionCount`, `detailUrl`)
|
|
338
340
|
```tsx
|
|
339
341
|
import {
|
|
340
342
|
PaymentProvider,
|
|
@@ -346,7 +348,7 @@ import {
|
|
|
346
348
|
function SubscriptionPage({ subscriptionId }) {
|
|
347
349
|
return (
|
|
348
350
|
<PaymentProvider session={session}>
|
|
349
|
-
{/* Handle overdue payments */}
|
|
351
|
+
{/* Handle subscription overdue payments */}
|
|
350
352
|
<OverdueInvoicePayment
|
|
351
353
|
subscriptionId={subscriptionId}
|
|
352
354
|
onPaid={() => {
|
|
@@ -354,6 +356,14 @@ function SubscriptionPage({ subscriptionId }) {
|
|
|
354
356
|
refetchSubscription();
|
|
355
357
|
}}
|
|
356
358
|
/>
|
|
359
|
+
{/* Handle customer overdue payment */}
|
|
360
|
+
<OverdueInvoicePayment
|
|
361
|
+
customerId={session.user.did}
|
|
362
|
+
onPaid={() => {
|
|
363
|
+
// Refresh customer status
|
|
364
|
+
refetch();
|
|
365
|
+
}}
|
|
366
|
+
/>
|
|
357
367
|
|
|
358
368
|
{/* Custom Overdue Invoice Payment */}
|
|
359
369
|
<OverdueInvoicePayment
|
|
@@ -11,8 +11,8 @@ export default function Livemode({ color, backgroundColor, sx }) {
|
|
|
11
11
|
color: "warning",
|
|
12
12
|
sx: {
|
|
13
13
|
ml: 2,
|
|
14
|
-
height:
|
|
15
|
-
lineHeight:
|
|
14
|
+
height: "18px",
|
|
15
|
+
lineHeight: "18px",
|
|
16
16
|
textTransform: "uppercase",
|
|
17
17
|
fontSize: "0.7rem",
|
|
18
18
|
fontWeight: "bold",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { KeyboardEvent, RefObject } from 'react';
|
|
2
|
+
type TabNavigationOptions<T> = {
|
|
3
|
+
/** whether to include custom option as the last item */
|
|
4
|
+
includeCustom?: boolean;
|
|
5
|
+
/** the value or index of the current selected item */
|
|
6
|
+
currentValue?: T | number;
|
|
7
|
+
/** whether the current selected item is custom */
|
|
8
|
+
isCustomSelected?: boolean;
|
|
9
|
+
/** a function to compare values, used to determine the current selected item */
|
|
10
|
+
compareValue?: (item: T, value: any) => boolean;
|
|
11
|
+
/** whether to allow Tab key navigation */
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
/** a selector to find navigable elements */
|
|
14
|
+
selector?: string;
|
|
15
|
+
/** an element container reference, limiting the query DOM range to improve performance */
|
|
16
|
+
containerRef?: RefObject<HTMLElement>;
|
|
17
|
+
/** the type of the current value, can be 'index' or 'value' */
|
|
18
|
+
valueType?: 'index' | 'value';
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Tab key navigation hook - implement Tab key circular navigation between a set of options
|
|
22
|
+
*
|
|
23
|
+
* @param items an array of options, can be a simple type (string, number) array or an object array
|
|
24
|
+
* @param onSelect callback when an item is selected
|
|
25
|
+
* @param options configuration options
|
|
26
|
+
* @returns an object containing the event handler and control functions
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // simple string array
|
|
30
|
+
* const { handleKeyDown } = useTabNavigation(['10', '20', '50'], handleSelect);
|
|
31
|
+
*
|
|
32
|
+
* // object array
|
|
33
|
+
* const { handleKeyDown } = useTabNavigation(
|
|
34
|
+
* [{id: 1, name: 'A'}, {id: 2, name: 'B'}],
|
|
35
|
+
* handleSelect,
|
|
36
|
+
* { compareValue: (item, value) => item.id === value.id }
|
|
37
|
+
* );
|
|
38
|
+
*/
|
|
39
|
+
export declare const useTabNavigation: <T>(items: T[], onSelect: (item: T | "custom", index: number) => void, options?: TabNavigationOptions<T>) => {
|
|
40
|
+
handleKeyDown: (e: KeyboardEvent) => void;
|
|
41
|
+
resetTabNavigation: () => void;
|
|
42
|
+
isTabNavigationActive: boolean;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
export const useTabNavigation = (items, onSelect, options) => {
|
|
3
|
+
const {
|
|
4
|
+
valueType = "value",
|
|
5
|
+
includeCustom = false,
|
|
6
|
+
currentValue,
|
|
7
|
+
isCustomSelected = false,
|
|
8
|
+
compareValue = (item, value) => item === value,
|
|
9
|
+
enabled = true,
|
|
10
|
+
selector = ".tab-navigable-card button",
|
|
11
|
+
containerRef
|
|
12
|
+
} = options || {};
|
|
13
|
+
const hasTabbed = useRef(false);
|
|
14
|
+
const findNavigableElements = useCallback(() => {
|
|
15
|
+
if (containerRef?.current) {
|
|
16
|
+
return containerRef.current.querySelectorAll(selector);
|
|
17
|
+
}
|
|
18
|
+
return document.querySelectorAll(selector);
|
|
19
|
+
}, [containerRef, selector]);
|
|
20
|
+
const determineCurrentIndex = useCallback(() => {
|
|
21
|
+
const allOptions = includeCustom ? [...items, "custom"] : items;
|
|
22
|
+
if (allOptions.length === 0)
|
|
23
|
+
return -1;
|
|
24
|
+
if (!hasTabbed.current) {
|
|
25
|
+
if (isCustomSelected && includeCustom) {
|
|
26
|
+
return items.length;
|
|
27
|
+
}
|
|
28
|
+
if (currentValue !== void 0) {
|
|
29
|
+
if (valueType === "index" && typeof currentValue === "number") {
|
|
30
|
+
return currentValue >= 0 && currentValue < items.length ? currentValue : -1;
|
|
31
|
+
}
|
|
32
|
+
return items.findIndex((item) => compareValue(item, currentValue));
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
const focusedElement = document.activeElement;
|
|
36
|
+
const navigableElements = findNavigableElements();
|
|
37
|
+
for (let i = 0; i < navigableElements.length; i++) {
|
|
38
|
+
if (navigableElements[i] === focusedElement) {
|
|
39
|
+
return i;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return -1;
|
|
44
|
+
}, [items, includeCustom, isCustomSelected, currentValue, valueType, compareValue, findNavigableElements]);
|
|
45
|
+
const getNextIndex = useCallback(
|
|
46
|
+
(currentIndex, isShiftKey) => {
|
|
47
|
+
const totalOptions = includeCustom ? items.length + 1 : items.length;
|
|
48
|
+
if (currentIndex === -1) {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
if (isShiftKey) {
|
|
52
|
+
return currentIndex === 0 ? totalOptions - 1 : currentIndex - 1;
|
|
53
|
+
}
|
|
54
|
+
return currentIndex === totalOptions - 1 ? 0 : currentIndex + 1;
|
|
55
|
+
},
|
|
56
|
+
[items, includeCustom]
|
|
57
|
+
);
|
|
58
|
+
const handleKeyDown = useCallback(
|
|
59
|
+
(e) => {
|
|
60
|
+
if (!enabled || e.key !== "Tab")
|
|
61
|
+
return;
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
const currentIndex = determineCurrentIndex();
|
|
65
|
+
const nextIndex = getNextIndex(currentIndex, e.shiftKey);
|
|
66
|
+
hasTabbed.current = true;
|
|
67
|
+
const selectedItem = nextIndex === items.length ? "custom" : items[nextIndex];
|
|
68
|
+
onSelect(selectedItem, nextIndex);
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
const elements = findNavigableElements();
|
|
71
|
+
if (elements[nextIndex]) {
|
|
72
|
+
elements[nextIndex].focus();
|
|
73
|
+
}
|
|
74
|
+
}, 0);
|
|
75
|
+
},
|
|
76
|
+
[items, onSelect, enabled, determineCurrentIndex, getNextIndex, findNavigableElements]
|
|
77
|
+
);
|
|
78
|
+
const resetTabNavigation = useCallback(() => {
|
|
79
|
+
hasTabbed.current = false;
|
|
80
|
+
}, []);
|
|
81
|
+
return {
|
|
82
|
+
handleKeyDown,
|
|
83
|
+
resetTabNavigation,
|
|
84
|
+
isTabNavigationActive: hasTabbed.current
|
|
85
|
+
};
|
|
86
|
+
};
|
package/es/index.d.ts
CHANGED
|
@@ -42,5 +42,6 @@ export * from './hooks/subscription';
|
|
|
42
42
|
export * from './hooks/mobile';
|
|
43
43
|
export * from './hooks/table';
|
|
44
44
|
export * from './hooks/scroll';
|
|
45
|
+
export * from './hooks/keyboard';
|
|
45
46
|
export { translations, createTranslator } from './locales';
|
|
46
47
|
export { createLazyComponent, api, dayjs, FormInput, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, OverdueInvoicePayment, PaymentBeneficiaries, LoadingButton, DonateDetails, };
|
package/es/index.js
CHANGED
|
@@ -42,6 +42,7 @@ export * from "./hooks/subscription.js";
|
|
|
42
42
|
export * from "./hooks/mobile.js";
|
|
43
43
|
export * from "./hooks/table.js";
|
|
44
44
|
export * from "./hooks/scroll.js";
|
|
45
|
+
export * from "./hooks/keyboard.js";
|
|
45
46
|
export { translations, createTranslator } from "./locales/index.js";
|
|
46
47
|
export {
|
|
47
48
|
createLazyComponent,
|
package/es/locales/en.js
CHANGED
|
@@ -131,6 +131,7 @@ export default flat({
|
|
|
131
131
|
empty: "No supporters yet",
|
|
132
132
|
gaveTips: "{count} people gave tips",
|
|
133
133
|
tipAmount: "Tip Amount",
|
|
134
|
+
tabHint: "to switch amount",
|
|
134
135
|
benefits: {
|
|
135
136
|
one: "{name} will receive all tips",
|
|
136
137
|
multiple: "Tips will be distributed to {count} beneficiaries",
|
|
@@ -144,7 +145,7 @@ export default flat({
|
|
|
144
145
|
later: "Configure Later",
|
|
145
146
|
configTip: "Configure donation settings in Payment Kit"
|
|
146
147
|
},
|
|
147
|
-
cardPay: "{action} with card",
|
|
148
|
+
cardPay: "{action} with bank card",
|
|
148
149
|
empty: "No thing to pay",
|
|
149
150
|
per: "per",
|
|
150
151
|
pay: "Pay {payee}",
|
|
@@ -161,7 +162,7 @@ export default flat({
|
|
|
161
162
|
payment: "Thanks for your purchase",
|
|
162
163
|
subscription: "Thanks for your subscribing",
|
|
163
164
|
setup: "Thanks for your subscribing",
|
|
164
|
-
donate: "Thanks for your
|
|
165
|
+
donate: "Thanks for your tip",
|
|
165
166
|
tip: "A payment to {payee} has been completed. You can view the details of this payment in your account."
|
|
166
167
|
},
|
|
167
168
|
confirm: "Confirming allows {payee} to charge or reduce your staking. You can cancel or revoke staking anytime.",
|
package/es/locales/zh.js
CHANGED
|
@@ -131,6 +131,7 @@ export default flat({
|
|
|
131
131
|
empty: "\u2764\uFE0F \u652F\u6301\u4E00\u4E0B",
|
|
132
132
|
gaveTips: "\u5DF2\u6709 {count} \u4EBA\u6253\u8D4F",
|
|
133
133
|
tipAmount: "\u6253\u8D4F\u91D1\u989D",
|
|
134
|
+
tabHint: "\u5FEB\u901F\u5207\u6362\u91D1\u989D",
|
|
134
135
|
benefits: {
|
|
135
136
|
one: "{name} \u5C06\u83B7\u5F97\u5168\u90E8\u6253\u8D4F",
|
|
136
137
|
multiple: "\u6253\u8D4F\u5C06\u6309\u6BD4\u4F8B\u5206\u914D\u7ED9 {count} \u4F4D\u53D7\u76CA\u4EBA",
|
|
@@ -144,7 +145,7 @@ export default flat({
|
|
|
144
145
|
later: "\u7A0D\u540E\u914D\u7F6E",
|
|
145
146
|
configTip: "\u524D\u5F80 Payment Kit \u914D\u7F6E\u6253\u8D4F\u9009\u9879"
|
|
146
147
|
},
|
|
147
|
-
cardPay: "\u4F7F\u7528\u5361
|
|
148
|
+
cardPay: "\u4F7F\u7528\u94F6\u884C\u5361{action}",
|
|
148
149
|
empty: "\u6CA1\u6709\u53EF\u652F\u4ED8\u7684\u9879\u76EE",
|
|
149
150
|
per: "\u6BCF",
|
|
150
151
|
pay: "\u4ED8\u6B3E\u7ED9 {payee}",
|
|
@@ -55,6 +55,7 @@ function PaymentInner({
|
|
|
55
55
|
}) {
|
|
56
56
|
const { t } = useLocaleContext();
|
|
57
57
|
const { settings, session } = usePaymentContext();
|
|
58
|
+
const { isMobile } = useMobile();
|
|
58
59
|
const [state, setState] = useSetState({
|
|
59
60
|
checkoutSession,
|
|
60
61
|
submitting: false,
|
|
@@ -202,20 +203,56 @@ function PaymentInner({
|
|
|
202
203
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
203
204
|
benefitsState.open && /* @__PURE__ */ jsx(PaymentBeneficiaries, { data: benefits, currency, totalAmount: benefitsState.amount }),
|
|
204
205
|
/* @__PURE__ */ jsxs(Stack, { sx: { display: benefitsState.open ? "none" : "block" }, children: [
|
|
205
|
-
/* @__PURE__ */
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
206
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { mb: 2 }, children: [
|
|
207
|
+
/* @__PURE__ */ jsx(
|
|
208
|
+
Typography,
|
|
209
|
+
{
|
|
210
|
+
title: t("payment.checkout.orderSummary"),
|
|
211
|
+
sx: {
|
|
212
|
+
color: "text.primary",
|
|
213
|
+
fontSize: "18px",
|
|
214
|
+
fontWeight: "500",
|
|
215
|
+
lineHeight: "24px"
|
|
216
|
+
},
|
|
217
|
+
children: t("payment.checkout.donation.tipAmount")
|
|
218
|
+
}
|
|
219
|
+
),
|
|
220
|
+
!isMobile && donationSettings?.amount?.presets && donationSettings.amount.presets.length > 0 && /* @__PURE__ */ jsxs(
|
|
221
|
+
Typography,
|
|
222
|
+
{
|
|
223
|
+
sx: {
|
|
224
|
+
color: "text.secondary",
|
|
225
|
+
fontSize: "13px",
|
|
226
|
+
display: "flex",
|
|
227
|
+
alignItems: "center",
|
|
228
|
+
gap: 0.5,
|
|
229
|
+
opacity: 0.8
|
|
230
|
+
},
|
|
231
|
+
children: [
|
|
232
|
+
/* @__PURE__ */ jsx(
|
|
233
|
+
Box,
|
|
234
|
+
{
|
|
235
|
+
component: "span",
|
|
236
|
+
sx: {
|
|
237
|
+
border: "1px solid",
|
|
238
|
+
borderColor: "divider",
|
|
239
|
+
borderRadius: 0.75,
|
|
240
|
+
px: 0.75,
|
|
241
|
+
py: 0.25,
|
|
242
|
+
fontSize: "12px",
|
|
243
|
+
lineHeight: 1,
|
|
244
|
+
color: "text.secondary",
|
|
245
|
+
fontWeight: "400",
|
|
246
|
+
bgcolor: "transparent"
|
|
247
|
+
},
|
|
248
|
+
children: "Tab"
|
|
249
|
+
}
|
|
250
|
+
),
|
|
251
|
+
t("payment.checkout.donation.tabHint")
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
] }),
|
|
219
256
|
items.map((x) => /* @__PURE__ */ jsx(
|
|
220
257
|
ProductDonation,
|
|
221
258
|
{
|
package/es/payment/form/index.js
CHANGED
|
@@ -342,6 +342,17 @@ export default function PaymentForm({
|
|
|
342
342
|
const onStripeCancel = () => {
|
|
343
343
|
setState({ stripePaying: false });
|
|
344
344
|
};
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
const handleKeyDown = (e) => {
|
|
347
|
+
if (e.key === "Enter" && !state.submitting && !state.paying && !state.stripePaying && quantityInventoryStatus && payable) {
|
|
348
|
+
onAction();
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
352
|
+
return () => {
|
|
353
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
354
|
+
};
|
|
355
|
+
}, [state.submitting, state.paying, state.stripePaying, quantityInventoryStatus, payable]);
|
|
345
356
|
if (onlyShowBtn) {
|
|
346
357
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
347
358
|
/* @__PURE__ */ jsx(Box, { className: "cko-payment-submit-btn", children: /* @__PURE__ */ jsxs(
|