@blocklet/payment-react 1.18.11 → 1.18.13

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.
@@ -12,7 +12,8 @@ import { Box, Button, CircularProgress, Hidden, Stack, Typography } from '@mui/m
12
12
  import { styled } from '@mui/system';
13
13
  import { useInfiniteScroll, useRequest, useSetState } from 'ahooks';
14
14
  import React, { useEffect, useRef, useState } from 'react';
15
- import { joinURL } from 'ufo';
15
+ // eslint-disable-next-line import/no-extraneous-dependencies
16
+ import { useNavigate } from 'react-router-dom';
16
17
 
17
18
  import debounce from 'lodash/debounce';
18
19
  import Status from '../../components/status';
@@ -26,10 +27,10 @@ import {
26
27
  formatToDatetime,
27
28
  getInvoiceDescriptionAndReason,
28
29
  getInvoiceStatusColor,
29
- getPrefix,
30
30
  getTxLink,
31
31
  } from '../../libs/util';
32
32
  import Table from '../../components/table';
33
+ import { createLink, handleNavigation, LinkInfo } from '../../libs/navigation';
33
34
 
34
35
  type Result = Paginated<TInvoiceExpanded> & { subscription: TSubscription };
35
36
 
@@ -72,23 +73,23 @@ type Props = {
72
73
 
73
74
  const getInvoiceLink = (invoice: TInvoiceExpanded, action?: string) => {
74
75
  if (invoice.id.startsWith('in_')) {
76
+ const path = `/customer/invoice/${invoice.id}${invoice.status === 'uncollectible' && action ? `?action=${action}` : ''}`;
75
77
  return {
76
- external: false,
77
78
  connect: invoice.status === 'uncollectible',
78
- url: joinURL(
79
- getPrefix(),
80
- `/customer/invoice/${invoice.id}?action=${invoice.status === 'uncollectible' ? action : ''}`
81
- ),
79
+ link: createLink(path),
82
80
  };
83
81
  }
84
82
 
85
83
  return {
86
- external: true,
87
84
  connect: false,
88
- url: getTxLink(invoice.paymentMethod, invoice.metadata?.payment_details).link,
85
+ link: createLink(getTxLink(invoice.paymentMethod, invoice.metadata?.payment_details).link, true),
89
86
  };
90
87
  };
91
88
 
89
+ const linkStyle = {
90
+ cursor: 'pointer',
91
+ };
92
+
92
93
  const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) => void }) => {
93
94
  const {
94
95
  pageSize,
@@ -106,6 +107,7 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
106
107
  } = props;
107
108
  const listKey = 'invoice-table';
108
109
  const { t, locale } = useLocaleContext();
110
+ const navigate = useNavigate();
109
111
 
110
112
  const [search, setSearch] = useState<{ pageSize: number; page: number }>({
111
113
  pageSize: pageSize || 10,
@@ -170,6 +172,11 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
170
172
  // eslint-disable-next-line react-hooks/exhaustive-deps
171
173
  }, [subscription]);
172
174
 
175
+ const handleLinkClick = (e: React.MouseEvent, invoice: TInvoiceExpanded) => {
176
+ const { link } = getInvoiceLink(invoice, action);
177
+ handleNavigation(e, link, navigate, { target: link.external ? '_blank' : target });
178
+ };
179
+
173
180
  const columns = [
174
181
  {
175
182
  label: t('common.amount'),
@@ -179,14 +186,13 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
179
186
  options: {
180
187
  customBodyRenderLite: (_: string, index: number) => {
181
188
  const invoice = data?.list[index] as TInvoiceExpanded;
182
- const link = getInvoiceLink(invoice, action);
183
189
  return (
184
- <a href={link.url} target={link.external ? '_blank' : target} rel="noreferrer">
190
+ <Box onClick={(e) => handleLinkClick(e, invoice)} sx={linkStyle}>
185
191
  <Typography>
186
192
  {formatBNStr(invoice.total, invoice.paymentCurrency.decimal)}&nbsp;
187
193
  {invoice.paymentCurrency.symbol}
188
194
  </Typography>
189
- </a>
195
+ </Box>
190
196
  );
191
197
  },
192
198
  },
@@ -197,11 +203,10 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
197
203
  options: {
198
204
  customBodyRenderLite: (_: string, index: number) => {
199
205
  const invoice = data.list[index] as TInvoiceExpanded;
200
- const link = getInvoiceLink(invoice, action);
201
206
  return (
202
- <a href={link.url} target={link.external ? '_blank' : target} rel="noreferrer">
207
+ <Box onClick={(e) => handleLinkClick(e, invoice)} sx={linkStyle}>
203
208
  <Status label={getInvoiceDescriptionAndReason(invoice, locale)?.type} />
204
- </a>
209
+ </Box>
205
210
  );
206
211
  },
207
212
  },
@@ -212,11 +217,10 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
212
217
  options: {
213
218
  customBodyRenderLite: (_: string, index: number) => {
214
219
  const invoice = data?.list[index] as TInvoiceExpanded;
215
- const link = getInvoiceLink(invoice, action);
216
220
  return (
217
- <a href={link.url} target={link.external ? '_blank' : target} rel="noreferrer">
221
+ <Box onClick={(e) => handleLinkClick(e, invoice)} sx={linkStyle}>
218
222
  {invoice?.number}
219
- </a>
223
+ </Box>
220
224
  );
221
225
  },
222
226
  },
@@ -227,11 +231,11 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
227
231
  options: {
228
232
  customBodyRenderLite: (val: string, index: number) => {
229
233
  const invoice = data?.list[index] as TInvoiceExpanded;
230
- const link = getInvoiceLink(invoice, action);
234
+
231
235
  return (
232
- <a href={link.url} target={link.external ? '_blank' : target} rel="noreferrer">
236
+ <Box onClick={(e) => handleLinkClick(e, invoice)} sx={linkStyle}>
233
237
  {formatToDate(invoice.created_at, locale, 'YYYY-MM-DD HH:mm:ss')}
234
- </a>
238
+ </Box>
235
239
  );
236
240
  },
237
241
  },
@@ -243,11 +247,11 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
243
247
  sort: false,
244
248
  customBodyRenderLite: (val: string, index: number) => {
245
249
  const invoice = data?.list[index] as TInvoiceExpanded;
246
- const link = getInvoiceLink(invoice, action);
250
+
247
251
  return (
248
- <a href={link.url} target={link.external ? '_blank' : target} rel="noreferrer">
252
+ <Box onClick={(e) => handleLinkClick(e, invoice)} sx={linkStyle}>
249
253
  {getInvoiceDescriptionAndReason(invoice, locale)?.description || invoice.id}
250
- </a>
254
+ </Box>
251
255
  );
252
256
  },
253
257
  },
@@ -258,10 +262,10 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
258
262
  options: {
259
263
  customBodyRenderLite: (val: string, index: number) => {
260
264
  const invoice = data?.list[index] as TInvoiceExpanded;
261
- const link = getInvoiceLink(invoice, action);
262
265
  const hidePay = invoice.billing_reason === 'overdraft-protection';
266
+ const { connect } = getInvoiceLink(invoice, action);
263
267
  if (action && !hidePay) {
264
- return link.connect ? (
268
+ return connect ? (
265
269
  <Button variant="text" size="small" onClick={() => onPay(invoice.id)} sx={{ color: 'text.link' }}>
266
270
  {t('payment.customer.invoice.pay')}
267
271
  </Button>
@@ -270,8 +274,7 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
270
274
  component="a"
271
275
  variant="text"
272
276
  size="small"
273
- href={link.url}
274
- target={link.external ? '_blank' : target}
277
+ onClick={(e) => handleLinkClick(e, invoice)}
275
278
  sx={{ color: 'var(--foregrounds-fg-interactive, #0086FF) !important' }}
276
279
  rel="noreferrer">
277
280
  {t('payment.customer.invoice.pay')}
@@ -279,9 +282,9 @@ const InvoiceTable = React.memo((props: Props & { onPay: (invoiceId: string) =>
279
282
  );
280
283
  }
281
284
  return (
282
- <a href={link.url} target={link.external ? '_blank' : target} rel="noreferrer">
285
+ <Box onClick={(e) => handleLinkClick(e, invoice)} sx={linkStyle}>
283
286
  <Status label={invoice.status} color={getInvoiceStatusColor(invoice.status)} />
284
- </a>
287
+ </Box>
285
288
  );
286
289
  },
287
290
  },
@@ -354,6 +357,7 @@ const InvoiceList = React.memo((props: Props & { onPay: (invoiceId: string) => v
354
357
 
355
358
  const subscription = useSubscription('events');
356
359
  const { t, locale } = useLocaleContext();
360
+ const navigate = useNavigate();
357
361
 
358
362
  const { data, loadMore, loadingMore, loading, reloadAsync } = useInfiniteScroll<Result>(
359
363
  (d) => {
@@ -433,13 +437,17 @@ const InvoiceList = React.memo((props: Props & { onPay: (invoiceId: string) => v
433
437
 
434
438
  const grouped = groupByDate(data.list as any);
435
439
 
440
+ const handleLinkClick = (e: React.MouseEvent, link: LinkInfo) => {
441
+ handleNavigation(e, link, navigate, { target: link.external ? '_blank' : target });
442
+ };
443
+
436
444
  return (
437
445
  <Root direction="column" gap={1} sx={{ mt: 1 }}>
438
446
  {Object.entries(grouped).map(([date, invoices]) => (
439
447
  <Box key={date}>
440
448
  <Typography sx={{ fontWeight: 'bold', color: 'text.secondary', mt: 2, mb: 1 }}>{date}</Typography>
441
449
  {invoices.map((invoice) => {
442
- const link = getInvoiceLink(invoice, action);
450
+ const { link, connect } = getInvoiceLink(invoice, action);
443
451
  return (
444
452
  <Stack
445
453
  key={invoice.id}
@@ -453,7 +461,11 @@ const InvoiceList = React.memo((props: Props & { onPay: (invoiceId: string) => v
453
461
  alignItems="center"
454
462
  flexWrap="nowrap">
455
463
  <Box flex={2}>
456
- <a href={link.url} target={link.external ? '_blank' : target} rel="noreferrer">
464
+ <a
465
+ href={link.url}
466
+ target={link.external ? '_blank' : target}
467
+ rel="noreferrer"
468
+ onClick={(e) => handleLinkClick(e, link)}>
457
469
  <Stack direction="row" alignItems="center" spacing={0.5}>
458
470
  <Typography component="span">{invoice.number}</Typography>
459
471
  {link.external && (
@@ -482,7 +494,7 @@ const InvoiceList = React.memo((props: Props & { onPay: (invoiceId: string) => v
482
494
  )}
483
495
  <Box flex={1} textAlign="right">
484
496
  {action ? (
485
- link.connect ? (
497
+ connect ? (
486
498
  <Button
487
499
  variant="contained"
488
500
  color="primary"
@@ -496,8 +508,7 @@ const InvoiceList = React.memo((props: Props & { onPay: (invoiceId: string) => v
496
508
  component="a"
497
509
  variant="contained"
498
510
  size="small"
499
- href={link.url}
500
- target={link.external ? '_blank' : target}
511
+ onClick={(e) => handleLinkClick(e, link)}
501
512
  sx={{ whiteSpace: 'nowrap' }}
502
513
  rel="noreferrer">
503
514
  {t('payment.customer.invoice.pay')}
@@ -0,0 +1,89 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import { NavigateFunction } from 'react-router-dom';
3
+ import { joinURL } from 'ufo';
4
+ import { getPrefix, PAYMENT_KIT_DID } from './util';
5
+
6
+ export interface LinkInfo {
7
+ // Full URL for href attribute
8
+ url: string;
9
+ // Whether this is an external link
10
+ external: boolean;
11
+ // Original path (used for React Router)
12
+ path: string;
13
+ }
14
+
15
+ /**
16
+ * Check if currently running inside Payment Kit
17
+ */
18
+ export function isInPaymentKit(): boolean {
19
+ const componentId = (window.blocklet?.componentId || '').split('/').pop();
20
+ return componentId === PAYMENT_KIT_DID;
21
+ }
22
+
23
+ /**
24
+ * Create link information object
25
+ * @param path Path or URL
26
+ * @param external Force external link behavior
27
+ */
28
+ export function createLink(path: string, external = false): LinkInfo {
29
+ const isAbsoluteUrl = /^https?:\/\//.test(path);
30
+ const isExternal = external || isAbsoluteUrl;
31
+ const url = isExternal ? path : joinURL(getPrefix(), path);
32
+ return {
33
+ url,
34
+ external: isExternal,
35
+ path,
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Get HTML attributes for a link
41
+ * @param link Link information
42
+ * @param target Link target (default: _self)
43
+ */
44
+ export function getLinkProps(link: LinkInfo, target = '_self'): Record<string, any> {
45
+ const props: Record<string, any> = {
46
+ href: link.url,
47
+ };
48
+
49
+ if (link.external) {
50
+ props.target = target;
51
+ props.rel = target === '_blank' ? 'noopener noreferrer' : undefined;
52
+ }
53
+
54
+ return props;
55
+ }
56
+
57
+ /**
58
+ * Handle link navigation
59
+ * @param e Click event
60
+ * @param link Link information
61
+ * @param navigate React Router navigate function
62
+ * @param options Navigation options
63
+ */
64
+ export function handleNavigation(
65
+ e: React.MouseEvent,
66
+ link: LinkInfo,
67
+ navigate?: NavigateFunction,
68
+ options: { replace?: boolean; target?: string } = {}
69
+ ): void {
70
+ if (e) {
71
+ e.preventDefault();
72
+ }
73
+ const { replace = false, target = '_self' } = options;
74
+ if (link.external || target === '_blank') {
75
+ window.open(link.url, target);
76
+ return;
77
+ }
78
+
79
+ if (isInPaymentKit() && navigate) {
80
+ navigate(link.path, { replace });
81
+ return;
82
+ }
83
+
84
+ if (replace) {
85
+ window.location.replace(link.url);
86
+ } else {
87
+ window.location.href = link.url;
88
+ }
89
+ }
@@ -254,8 +254,8 @@ export default flat({
254
254
  },
255
255
  },
256
256
  recover: {
257
- button: 'Renew',
258
- title: 'Renew your subscription',
257
+ button: 'Resume Subscription',
258
+ title: 'Resume Your Subscription',
259
259
  description:
260
260
  'Your subscription will not be canceled and will be automatically renewed on {date}, please confirm to continue',
261
261
  },
@@ -247,8 +247,8 @@ export default flat({
247
247
  },
248
248
  },
249
249
  recover: {
250
- button: '续订',
251
- title: '续订您的订阅',
250
+ button: '恢复订阅',
251
+ title: '恢复您的订阅',
252
252
  description: '您的订阅将不会被取消,并将在{date}自动续订,请确认是否继续',
253
253
  },
254
254
  changePlan: {