@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 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
- - props:
327
- - `subscriptionId`: [required] The subscription ID
328
- - `onPaid`: [optional] Callback function called after successful payment
329
- - `mode`: [optional] Component mode, `default` or `custom` (default is `default`)
330
- - `dialogProps`: [optional] Dialog properties, default is `{ open: true }`
331
- - `children`: [optional] Custom rendering with payment data when `mode` is `custom`
332
- - Custom mode:
333
- - `children` will receive two parameters:
334
- - `handlePay`: Function to start the
335
-
336
- payment process
337
- - `data`: Payment data (includes `subscription`, `summary`, `invoices`, `subscriptionUrl`)
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: 18,
15
- lineHeight: 1.2,
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 support",
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\u7247{action}",
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__ */ jsx(
206
- Typography,
207
- {
208
- title: t("payment.checkout.orderSummary"),
209
- sx: {
210
- color: "text.primary",
211
- fontSize: "18px",
212
- fontWeight: "500",
213
- lineHeight: "24px",
214
- mb: 2
215
- },
216
- children: t("payment.checkout.donation.tipAmount")
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
  {
@@ -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(