@blocklet/payment-react 1.18.12 → 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.
@@ -11,6 +11,7 @@ export default function Link({ to, children, onClick, replace, target, outLink =
11
11
  e.preventDefault();
12
12
  window.location.href = to;
13
13
  }
14
+ e.preventDefault();
14
15
  onClick?.(e);
15
16
  };
16
17
  return /* @__PURE__ */ jsx("a", { href: to, onClick: handleClick, target, rel: "noreferrer", ...props, children });
@@ -9,6 +9,7 @@ type Props = {
9
9
  mode?: 'default' | 'custom';
10
10
  onPaid?: (subscriptionId: string, currencyId: string) => void;
11
11
  dialogProps?: DialogProps;
12
+ inSubscriptionDetail?: boolean;
12
13
  children?: (handlePay: (item: SummaryItem) => void, data: {
13
14
  subscription: Subscription;
14
15
  summary: {
@@ -23,7 +24,7 @@ type SummaryItem = {
23
24
  currency: PaymentCurrency;
24
25
  method: PaymentMethod;
25
26
  };
26
- declare function OverdueInvoicePayment({ subscriptionId, mode, dialogProps, children, onPaid, }: Props): import("react").JSX.Element;
27
+ declare function OverdueInvoicePayment({ subscriptionId, mode, dialogProps, children, onPaid, inSubscriptionDetail, }: Props): import("react").JSX.Element | null;
27
28
  declare namespace OverdueInvoicePayment {
28
29
  var defaultProps: {
29
30
  mode: string;
@@ -32,6 +33,7 @@ declare namespace OverdueInvoicePayment {
32
33
  open: boolean;
33
34
  };
34
35
  children: null;
36
+ inSubscriptionDetail: boolean;
35
37
  };
36
38
  }
37
39
  export default OverdueInvoicePayment;
@@ -20,7 +20,8 @@ function OverdueInvoicePayment({
20
20
  dialogProps = {},
21
21
  children,
22
22
  onPaid = () => {
23
- }
23
+ },
24
+ inSubscriptionDetail = false
24
25
  }) {
25
26
  const { t } = useLocaleContext();
26
27
  const { connect } = usePaymentContext();
@@ -100,8 +101,14 @@ function OverdueInvoicePayment({
100
101
  setDialogOpen(false);
101
102
  dialogProps.onClose?.();
102
103
  };
104
+ const handleViewDetailClick = (e) => {
105
+ if (inSubscriptionDetail) {
106
+ e.preventDefault();
107
+ handleClose();
108
+ }
109
+ };
103
110
  if (loading) {
104
- return /* @__PURE__ */ jsx(CircularProgress, {});
111
+ return null;
105
112
  }
106
113
  const renderPayButton = (item, props) => {
107
114
  const isPayLoading = payLoading && item.currency.id === selectCurrencyId;
@@ -130,6 +137,7 @@ function OverdueInvoicePayment({
130
137
  ...dialogProps || {},
131
138
  open: dialogOpen,
132
139
  title: dialogProps?.title || t("payment.subscription.overdue.pastDue"),
140
+ sx: { "& .MuiDialogContent-root": { pt: 0 } },
133
141
  onClose: handleClose,
134
142
  children: error ? /* @__PURE__ */ jsx(Alert, { severity: "error", children: error.message }) : /* @__PURE__ */ jsxs(Stack, { gap: 1, children: [
135
143
  summaryList.length === 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -155,6 +163,7 @@ function OverdueInvoicePayment({
155
163
  {
156
164
  href: subscriptionUrl,
157
165
  target: "_blank",
166
+ onClick: handleViewDetailClick,
158
167
  rel: "noreferrer",
159
168
  style: { color: "var(--foregrounds-fg-interactive, 0086FF)" },
160
169
  children: t("payment.subscription.overdue.view")
@@ -181,6 +190,7 @@ function OverdueInvoicePayment({
181
190
  href: subscriptionUrl,
182
191
  target: "_blank",
183
192
  rel: "noreferrer",
193
+ onClick: handleViewDetailClick,
184
194
  style: { color: "var(--foregrounds-fg-interactive, 0086FF)" },
185
195
  children: t("payment.subscription.overdue.view")
186
196
  }
@@ -229,6 +239,7 @@ OverdueInvoicePayment.defaultProps = {
229
239
  dialogProps: {
230
240
  open: true
231
241
  },
232
- children: null
242
+ children: null,
243
+ inSubscriptionDetail: false
233
244
  };
234
245
  export default OverdueInvoicePayment;
@@ -6,7 +6,7 @@ import { Box, Button, CircularProgress, Hidden, Stack, Typography } from "@mui/m
6
6
  import { styled } from "@mui/system";
7
7
  import { useInfiniteScroll, useRequest, useSetState } from "ahooks";
8
8
  import React, { useEffect, useRef, useState } from "react";
9
- import { joinURL } from "ufo";
9
+ import { useNavigate } from "react-router-dom";
10
10
  import debounce from "lodash/debounce";
11
11
  import Status from "../../components/status.js";
12
12
  import { usePaymentContext } from "../../contexts/payment.js";
@@ -19,10 +19,10 @@ import {
19
19
  formatToDatetime,
20
20
  getInvoiceDescriptionAndReason,
21
21
  getInvoiceStatusColor,
22
- getPrefix,
23
22
  getTxLink
24
23
  } from "../../libs/util.js";
25
24
  import Table from "../../components/table.js";
25
+ import { createLink, handleNavigation } from "../../libs/navigation.js";
26
26
  const groupByDate = (items) => {
27
27
  const grouped = {};
28
28
  items.forEach((item) => {
@@ -45,21 +45,20 @@ const fetchData = (params = {}) => {
45
45
  };
46
46
  const getInvoiceLink = (invoice, action) => {
47
47
  if (invoice.id.startsWith("in_")) {
48
+ const path = `/customer/invoice/${invoice.id}${invoice.status === "uncollectible" && action ? `?action=${action}` : ""}`;
48
49
  return {
49
- external: false,
50
50
  connect: invoice.status === "uncollectible",
51
- url: joinURL(
52
- getPrefix(),
53
- `/customer/invoice/${invoice.id}?action=${invoice.status === "uncollectible" ? action : ""}`
54
- )
51
+ link: createLink(path)
55
52
  };
56
53
  }
57
54
  return {
58
- external: true,
59
55
  connect: false,
60
- url: getTxLink(invoice.paymentMethod, invoice.metadata?.payment_details).link
56
+ link: createLink(getTxLink(invoice.paymentMethod, invoice.metadata?.payment_details).link, true)
61
57
  };
62
58
  };
59
+ const linkStyle = {
60
+ cursor: "pointer"
61
+ };
63
62
  const InvoiceTable = React.memo((props) => {
64
63
  const {
65
64
  pageSize,
@@ -77,6 +76,7 @@ const InvoiceTable = React.memo((props) => {
77
76
  } = props;
78
77
  const listKey = "invoice-table";
79
78
  const { t, locale } = useLocaleContext();
79
+ const navigate = useNavigate();
80
80
  const [search, setSearch] = useState({
81
81
  pageSize: pageSize || 10,
82
82
  page: 1
@@ -131,6 +131,10 @@ const InvoiceTable = React.memo((props) => {
131
131
  });
132
132
  }
133
133
  }, [subscription]);
134
+ const handleLinkClick = (e, invoice) => {
135
+ const { link } = getInvoiceLink(invoice, action);
136
+ handleNavigation(e, link, navigate, { target: link.external ? "_blank" : target });
137
+ };
134
138
  const columns = [
135
139
  {
136
140
  label: t("common.amount"),
@@ -140,8 +144,7 @@ const InvoiceTable = React.memo((props) => {
140
144
  options: {
141
145
  customBodyRenderLite: (_, index) => {
142
146
  const invoice = data?.list[index];
143
- const link = getInvoiceLink(invoice, action);
144
- return /* @__PURE__ */ jsx("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", children: /* @__PURE__ */ jsxs(Typography, { children: [
147
+ return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleLinkClick(e, invoice), sx: linkStyle, children: /* @__PURE__ */ jsxs(Typography, { children: [
145
148
  formatBNStr(invoice.total, invoice.paymentCurrency.decimal),
146
149
  "\xA0",
147
150
  invoice.paymentCurrency.symbol
@@ -155,8 +158,7 @@ const InvoiceTable = React.memo((props) => {
155
158
  options: {
156
159
  customBodyRenderLite: (_, index) => {
157
160
  const invoice = data.list[index];
158
- const link = getInvoiceLink(invoice, action);
159
- return /* @__PURE__ */ jsx("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", children: /* @__PURE__ */ jsx(Status, { label: getInvoiceDescriptionAndReason(invoice, locale)?.type }) });
161
+ return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleLinkClick(e, invoice), sx: linkStyle, children: /* @__PURE__ */ jsx(Status, { label: getInvoiceDescriptionAndReason(invoice, locale)?.type }) });
160
162
  }
161
163
  }
162
164
  },
@@ -166,8 +168,7 @@ const InvoiceTable = React.memo((props) => {
166
168
  options: {
167
169
  customBodyRenderLite: (_, index) => {
168
170
  const invoice = data?.list[index];
169
- const link = getInvoiceLink(invoice, action);
170
- return /* @__PURE__ */ jsx("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", children: invoice?.number });
171
+ return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleLinkClick(e, invoice), sx: linkStyle, children: invoice?.number });
171
172
  }
172
173
  }
173
174
  },
@@ -177,8 +178,7 @@ const InvoiceTable = React.memo((props) => {
177
178
  options: {
178
179
  customBodyRenderLite: (val, index) => {
179
180
  const invoice = data?.list[index];
180
- const link = getInvoiceLink(invoice, action);
181
- return /* @__PURE__ */ jsx("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", children: formatToDate(invoice.created_at, locale, "YYYY-MM-DD HH:mm:ss") });
181
+ return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleLinkClick(e, invoice), sx: linkStyle, children: formatToDate(invoice.created_at, locale, "YYYY-MM-DD HH:mm:ss") });
182
182
  }
183
183
  }
184
184
  },
@@ -189,8 +189,7 @@ const InvoiceTable = React.memo((props) => {
189
189
  sort: false,
190
190
  customBodyRenderLite: (val, index) => {
191
191
  const invoice = data?.list[index];
192
- const link = getInvoiceLink(invoice, action);
193
- return /* @__PURE__ */ jsx("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", children: getInvoiceDescriptionAndReason(invoice, locale)?.description || invoice.id });
192
+ return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleLinkClick(e, invoice), sx: linkStyle, children: getInvoiceDescriptionAndReason(invoice, locale)?.description || invoice.id });
194
193
  }
195
194
  }
196
195
  },
@@ -200,24 +199,23 @@ const InvoiceTable = React.memo((props) => {
200
199
  options: {
201
200
  customBodyRenderLite: (val, index) => {
202
201
  const invoice = data?.list[index];
203
- const link = getInvoiceLink(invoice, action);
204
202
  const hidePay = invoice.billing_reason === "overdraft-protection";
203
+ const { connect } = getInvoiceLink(invoice, action);
205
204
  if (action && !hidePay) {
206
- return link.connect ? /* @__PURE__ */ jsx(Button, { variant: "text", size: "small", onClick: () => onPay(invoice.id), sx: { color: "text.link" }, children: t("payment.customer.invoice.pay") }) : /* @__PURE__ */ jsx(
205
+ return connect ? /* @__PURE__ */ jsx(Button, { variant: "text", size: "small", onClick: () => onPay(invoice.id), sx: { color: "text.link" }, children: t("payment.customer.invoice.pay") }) : /* @__PURE__ */ jsx(
207
206
  Button,
208
207
  {
209
208
  component: "a",
210
209
  variant: "text",
211
210
  size: "small",
212
- href: link.url,
213
- target: link.external ? "_blank" : target,
211
+ onClick: (e) => handleLinkClick(e, invoice),
214
212
  sx: { color: "var(--foregrounds-fg-interactive, #0086FF) !important" },
215
213
  rel: "noreferrer",
216
214
  children: t("payment.customer.invoice.pay")
217
215
  }
218
216
  );
219
217
  }
220
- return /* @__PURE__ */ jsx("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", children: /* @__PURE__ */ jsx(Status, { label: invoice.status, color: getInvoiceStatusColor(invoice.status) }) });
218
+ return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleLinkClick(e, invoice), sx: linkStyle, children: /* @__PURE__ */ jsx(Status, { label: invoice.status, color: getInvoiceStatusColor(invoice.status) }) });
221
219
  }
222
220
  }
223
221
  }
@@ -283,6 +281,7 @@ const InvoiceList = React.memo((props) => {
283
281
  const size = pageSize || 10;
284
282
  const subscription = useSubscription("events");
285
283
  const { t, locale } = useLocaleContext();
284
+ const navigate = useNavigate();
286
285
  const { data, loadMore, loadingMore, loading, reloadAsync } = useInfiniteScroll(
287
286
  (d) => {
288
287
  const page = d ? Math.ceil(d.list.length / size) + 1 : 1;
@@ -342,11 +341,14 @@ const InvoiceList = React.memo((props) => {
342
341
  }
343
342
  const hasMore = data && data.list.length < data.count;
344
343
  const grouped = groupByDate(data.list);
344
+ const handleLinkClick = (e, link) => {
345
+ handleNavigation(e, link, navigate, { target: link.external ? "_blank" : target });
346
+ };
345
347
  return /* @__PURE__ */ jsxs(Root, { direction: "column", gap: 1, sx: { mt: 1 }, children: [
346
348
  Object.entries(grouped).map(([date, invoices]) => /* @__PURE__ */ jsxs(Box, { children: [
347
349
  /* @__PURE__ */ jsx(Typography, { sx: { fontWeight: "bold", color: "text.secondary", mt: 2, mb: 1 }, children: date }),
348
350
  invoices.map((invoice) => {
349
- const link = getInvoiceLink(invoice, action);
351
+ const { link, connect } = getInvoiceLink(invoice, action);
350
352
  return /* @__PURE__ */ jsxs(
351
353
  Stack,
352
354
  {
@@ -360,10 +362,19 @@ const InvoiceList = React.memo((props) => {
360
362
  alignItems: "center",
361
363
  flexWrap: "nowrap",
362
364
  children: [
363
- /* @__PURE__ */ jsx(Box, { flex: 2, children: /* @__PURE__ */ jsx("a", { href: link.url, target: link.external ? "_blank" : target, rel: "noreferrer", children: /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [
364
- /* @__PURE__ */ jsx(Typography, { component: "span", children: invoice.number }),
365
- link.external && /* @__PURE__ */ jsx(Hidden, { mdDown: true, children: /* @__PURE__ */ jsx(OpenInNewOutlined, { fontSize: "small", sx: { color: "text.secondary" } }) })
366
- ] }) }) }),
365
+ /* @__PURE__ */ jsx(Box, { flex: 2, children: /* @__PURE__ */ jsx(
366
+ "a",
367
+ {
368
+ href: link.url,
369
+ target: link.external ? "_blank" : target,
370
+ rel: "noreferrer",
371
+ onClick: (e) => handleLinkClick(e, link),
372
+ children: /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [
373
+ /* @__PURE__ */ jsx(Typography, { component: "span", children: invoice.number }),
374
+ link.external && /* @__PURE__ */ jsx(Hidden, { mdDown: true, children: /* @__PURE__ */ jsx(OpenInNewOutlined, { fontSize: "small", sx: { color: "text.secondary" } }) })
375
+ ] })
376
+ }
377
+ ) }),
367
378
  /* @__PURE__ */ jsx(Box, { flex: 1, textAlign: "right", children: /* @__PURE__ */ jsxs(Typography, { children: [
368
379
  formatBNStr(invoice.total, invoice.paymentCurrency.decimal),
369
380
  "\xA0",
@@ -371,7 +382,7 @@ const InvoiceList = React.memo((props) => {
371
382
  ] }) }),
372
383
  /* @__PURE__ */ jsx(Box, { flex: 1, textAlign: "right", children: /* @__PURE__ */ jsx(Typography, { children: formatToDate(invoice.created_at, locale, "HH:mm:ss") }) }),
373
384
  !action && /* @__PURE__ */ jsx(Hidden, { mdDown: true, children: /* @__PURE__ */ jsx(Box, { flex: 2, className: "invoice-description", textAlign: "right", children: /* @__PURE__ */ jsx(Typography, { children: invoice.description || invoice.id }) }) }),
374
- /* @__PURE__ */ jsx(Box, { flex: 1, textAlign: "right", children: action ? link.connect ? /* @__PURE__ */ jsx(
385
+ /* @__PURE__ */ jsx(Box, { flex: 1, textAlign: "right", children: action ? connect ? /* @__PURE__ */ jsx(
375
386
  Button,
376
387
  {
377
388
  variant: "contained",
@@ -387,8 +398,7 @@ const InvoiceList = React.memo((props) => {
387
398
  component: "a",
388
399
  variant: "contained",
389
400
  size: "small",
390
- href: link.url,
391
- target: link.external ? "_blank" : target,
401
+ onClick: (e) => handleLinkClick(e, link),
392
402
  sx: { whiteSpace: "nowrap" },
393
403
  rel: "noreferrer",
394
404
  children: t("payment.customer.invoice.pay")
@@ -0,0 +1,33 @@
1
+ import { NavigateFunction } from 'react-router-dom';
2
+ export interface LinkInfo {
3
+ url: string;
4
+ external: boolean;
5
+ path: string;
6
+ }
7
+ /**
8
+ * Check if currently running inside Payment Kit
9
+ */
10
+ export declare function isInPaymentKit(): boolean;
11
+ /**
12
+ * Create link information object
13
+ * @param path Path or URL
14
+ * @param external Force external link behavior
15
+ */
16
+ export declare function createLink(path: string, external?: boolean): LinkInfo;
17
+ /**
18
+ * Get HTML attributes for a link
19
+ * @param link Link information
20
+ * @param target Link target (default: _self)
21
+ */
22
+ export declare function getLinkProps(link: LinkInfo, target?: string): Record<string, any>;
23
+ /**
24
+ * Handle link navigation
25
+ * @param e Click event
26
+ * @param link Link information
27
+ * @param navigate React Router navigate function
28
+ * @param options Navigation options
29
+ */
30
+ export declare function handleNavigation(e: React.MouseEvent, link: LinkInfo, navigate?: NavigateFunction, options?: {
31
+ replace?: boolean;
32
+ target?: string;
33
+ }): void;
@@ -0,0 +1,45 @@
1
+ import { joinURL } from "ufo";
2
+ import { getPrefix, PAYMENT_KIT_DID } from "./util.js";
3
+ export function isInPaymentKit() {
4
+ const componentId = (window.blocklet?.componentId || "").split("/").pop();
5
+ return componentId === PAYMENT_KIT_DID;
6
+ }
7
+ export function createLink(path, external = false) {
8
+ const isAbsoluteUrl = /^https?:\/\//.test(path);
9
+ const isExternal = external || isAbsoluteUrl;
10
+ const url = isExternal ? path : joinURL(getPrefix(), path);
11
+ return {
12
+ url,
13
+ external: isExternal,
14
+ path
15
+ };
16
+ }
17
+ export function getLinkProps(link, target = "_self") {
18
+ const props = {
19
+ href: link.url
20
+ };
21
+ if (link.external) {
22
+ props.target = target;
23
+ props.rel = target === "_blank" ? "noopener noreferrer" : void 0;
24
+ }
25
+ return props;
26
+ }
27
+ export function handleNavigation(e, link, navigate, options = {}) {
28
+ if (e) {
29
+ e.preventDefault();
30
+ }
31
+ const { replace = false, target = "_self" } = options;
32
+ if (link.external || target === "_blank") {
33
+ window.open(link.url, target);
34
+ return;
35
+ }
36
+ if (isInPaymentKit() && navigate) {
37
+ navigate(link.path, { replace });
38
+ return;
39
+ }
40
+ if (replace) {
41
+ window.location.replace(link.url);
42
+ } else {
43
+ window.location.href = link.url;
44
+ }
45
+ }
package/es/locales/en.js CHANGED
@@ -245,8 +245,8 @@ export default flat({
245
245
  }
246
246
  },
247
247
  recover: {
248
- button: "Renew",
249
- title: "Renew your subscription",
248
+ button: "Resume Subscription",
249
+ title: "Resume Your Subscription",
250
250
  description: "Your subscription will not be canceled and will be automatically renewed on {date}, please confirm to continue"
251
251
  },
252
252
  changePlan: {
package/es/locales/zh.js CHANGED
@@ -245,8 +245,8 @@ export default flat({
245
245
  }
246
246
  },
247
247
  recover: {
248
- button: "\u7EED\u8BA2",
249
- title: "\u7EED\u8BA2\u60A8\u7684\u8BA2\u9605",
248
+ button: "\u6062\u590D\u8BA2\u9605",
249
+ title: "\u6062\u590D\u60A8\u7684\u8BA2\u9605",
250
250
  description: "\u60A8\u7684\u8BA2\u9605\u5C06\u4E0D\u4F1A\u88AB\u53D6\u6D88\uFF0C\u5E76\u5C06\u5728{date}\u81EA\u52A8\u7EED\u8BA2\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u7EE7\u7EED"
251
251
  },
252
252
  changePlan: {
@@ -27,6 +27,7 @@ function Link({
27
27
  e.preventDefault();
28
28
  window.location.href = to;
29
29
  }
30
+ e.preventDefault();
30
31
  onClick?.(e);
31
32
  };
32
33
  return /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
@@ -9,6 +9,7 @@ type Props = {
9
9
  mode?: 'default' | 'custom';
10
10
  onPaid?: (subscriptionId: string, currencyId: string) => void;
11
11
  dialogProps?: DialogProps;
12
+ inSubscriptionDetail?: boolean;
12
13
  children?: (handlePay: (item: SummaryItem) => void, data: {
13
14
  subscription: Subscription;
14
15
  summary: {
@@ -23,7 +24,7 @@ type SummaryItem = {
23
24
  currency: PaymentCurrency;
24
25
  method: PaymentMethod;
25
26
  };
26
- declare function OverdueInvoicePayment({ subscriptionId, mode, dialogProps, children, onPaid, }: Props): import("react").JSX.Element;
27
+ declare function OverdueInvoicePayment({ subscriptionId, mode, dialogProps, children, onPaid, inSubscriptionDetail, }: Props): import("react").JSX.Element | null;
27
28
  declare namespace OverdueInvoicePayment {
28
29
  var defaultProps: {
29
30
  mode: string;
@@ -32,6 +33,7 @@ declare namespace OverdueInvoicePayment {
32
33
  open: boolean;
33
34
  };
34
35
  children: null;
36
+ inSubscriptionDetail: boolean;
35
37
  };
36
38
  }
37
39
  export default OverdueInvoicePayment;
@@ -26,7 +26,8 @@ function OverdueInvoicePayment({
26
26
  mode = "default",
27
27
  dialogProps = {},
28
28
  children,
29
- onPaid = () => {}
29
+ onPaid = () => {},
30
+ inSubscriptionDetail = false
30
31
  }) {
31
32
  const {
32
33
  t
@@ -121,8 +122,14 @@ function OverdueInvoicePayment({
121
122
  setDialogOpen(false);
122
123
  dialogProps.onClose?.();
123
124
  };
125
+ const handleViewDetailClick = e => {
126
+ if (inSubscriptionDetail) {
127
+ e.preventDefault();
128
+ handleClose();
129
+ }
130
+ };
124
131
  if (loading) {
125
- return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {});
132
+ return null;
126
133
  }
127
134
  const renderPayButton = (item, props) => {
128
135
  const isPayLoading = payLoading && item.currency.id === selectCurrencyId;
@@ -169,6 +176,11 @@ function OverdueInvoicePayment({
169
176
  ...(dialogProps || {}),
170
177
  open: dialogOpen,
171
178
  title: dialogProps?.title || t("payment.subscription.overdue.pastDue"),
179
+ sx: {
180
+ "& .MuiDialogContent-root": {
181
+ pt: 0
182
+ }
183
+ },
172
184
  onClose: handleClose,
173
185
  children: error ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Alert, {
174
186
  severity: "error",
@@ -209,6 +221,7 @@ function OverdueInvoicePayment({
209
221
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)("br", {}), t("payment.subscription.overdue.description"), /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
210
222
  href: subscriptionUrl,
211
223
  target: "_blank",
224
+ onClick: handleViewDetailClick,
212
225
  rel: "noreferrer",
213
226
  style: {
214
227
  color: "var(--foregrounds-fg-interactive, 0086FF)"
@@ -239,6 +252,7 @@ function OverdueInvoicePayment({
239
252
  href: subscriptionUrl,
240
253
  target: "_blank",
241
254
  rel: "noreferrer",
255
+ onClick: handleViewDetailClick,
242
256
  style: {
243
257
  color: "var(--foregrounds-fg-interactive, 0086FF)"
244
258
  },
@@ -285,6 +299,7 @@ OverdueInvoicePayment.defaultProps = {
285
299
  dialogProps: {
286
300
  open: true
287
301
  },
288
- children: null
302
+ children: null,
303
+ inSubscriptionDetail: false
289
304
  };
290
305
  module.exports = OverdueInvoicePayment;
@@ -12,7 +12,7 @@ var _material = require("@mui/material");
12
12
  var _system = require("@mui/system");
13
13
  var _ahooks = require("ahooks");
14
14
  var _react = _interopRequireWildcard(require("react"));
15
- var _ufo = require("ufo");
15
+ var _reactRouterDom = require("react-router-dom");
16
16
  var _debounce = _interopRequireDefault(require("lodash/debounce"));
17
17
  var _status = _interopRequireDefault(require("../../components/status"));
18
18
  var _payment = require("../../contexts/payment");
@@ -20,6 +20,7 @@ var _subscription = require("../../hooks/subscription");
20
20
  var _api = _interopRequireDefault(require("../../libs/api"));
21
21
  var _util = require("../../libs/util");
22
22
  var _table = _interopRequireDefault(require("../../components/table"));
23
+ var _navigation = require("../../libs/navigation");
23
24
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
24
25
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
25
26
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -45,18 +46,20 @@ const fetchData = (params = {}) => {
45
46
  };
46
47
  const getInvoiceLink = (invoice, action) => {
47
48
  if (invoice.id.startsWith("in_")) {
49
+ const path = `/customer/invoice/${invoice.id}${invoice.status === "uncollectible" && action ? `?action=${action}` : ""}`;
48
50
  return {
49
- external: false,
50
51
  connect: invoice.status === "uncollectible",
51
- url: (0, _ufo.joinURL)((0, _util.getPrefix)(), `/customer/invoice/${invoice.id}?action=${invoice.status === "uncollectible" ? action : ""}`)
52
+ link: (0, _navigation.createLink)(path)
52
53
  };
53
54
  }
54
55
  return {
55
- external: true,
56
56
  connect: false,
57
- url: (0, _util.getTxLink)(invoice.paymentMethod, invoice.metadata?.payment_details).link
57
+ link: (0, _navigation.createLink)((0, _util.getTxLink)(invoice.paymentMethod, invoice.metadata?.payment_details).link, true)
58
58
  };
59
59
  };
60
+ const linkStyle = {
61
+ cursor: "pointer"
62
+ };
60
63
  const InvoiceTable = _react.default.memo(props => {
61
64
  const {
62
65
  pageSize,
@@ -77,6 +80,7 @@ const InvoiceTable = _react.default.memo(props => {
77
80
  t,
78
81
  locale
79
82
  } = (0, _context.useLocaleContext)();
83
+ const navigate = (0, _reactRouterDom.useNavigate)();
80
84
  const [search, setSearch] = (0, _react.useState)({
81
85
  pageSize: pageSize || 10,
82
86
  page: 1
@@ -129,6 +133,14 @@ const InvoiceTable = _react.default.memo(props => {
129
133
  });
130
134
  }
131
135
  }, [subscription]);
136
+ const handleLinkClick = (e, invoice) => {
137
+ const {
138
+ link
139
+ } = getInvoiceLink(invoice, action);
140
+ (0, _navigation.handleNavigation)(e, link, navigate, {
141
+ target: link.external ? "_blank" : target
142
+ });
143
+ };
132
144
  const columns = [{
133
145
  label: t("common.amount"),
134
146
  name: "total",
@@ -137,11 +149,9 @@ const InvoiceTable = _react.default.memo(props => {
137
149
  options: {
138
150
  customBodyRenderLite: (_, index) => {
139
151
  const invoice = data?.list[index];
140
- const link = getInvoiceLink(invoice, action);
141
- return /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
142
- href: link.url,
143
- target: link.external ? "_blank" : target,
144
- rel: "noreferrer",
152
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
153
+ onClick: e => handleLinkClick(e, invoice),
154
+ sx: linkStyle,
145
155
  children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
146
156
  children: [(0, _util.formatBNStr)(invoice.total, invoice.paymentCurrency.decimal), "\xA0", invoice.paymentCurrency.symbol]
147
157
  })
@@ -154,11 +164,9 @@ const InvoiceTable = _react.default.memo(props => {
154
164
  options: {
155
165
  customBodyRenderLite: (_, index) => {
156
166
  const invoice = data.list[index];
157
- const link = getInvoiceLink(invoice, action);
158
- return /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
159
- href: link.url,
160
- target: link.external ? "_blank" : target,
161
- rel: "noreferrer",
167
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
168
+ onClick: e => handleLinkClick(e, invoice),
169
+ sx: linkStyle,
162
170
  children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
163
171
  label: (0, _util.getInvoiceDescriptionAndReason)(invoice, locale)?.type
164
172
  })
@@ -171,11 +179,9 @@ const InvoiceTable = _react.default.memo(props => {
171
179
  options: {
172
180
  customBodyRenderLite: (_, index) => {
173
181
  const invoice = data?.list[index];
174
- const link = getInvoiceLink(invoice, action);
175
- return /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
176
- href: link.url,
177
- target: link.external ? "_blank" : target,
178
- rel: "noreferrer",
182
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
183
+ onClick: e => handleLinkClick(e, invoice),
184
+ sx: linkStyle,
179
185
  children: invoice?.number
180
186
  });
181
187
  }
@@ -186,11 +192,9 @@ const InvoiceTable = _react.default.memo(props => {
186
192
  options: {
187
193
  customBodyRenderLite: (val, index) => {
188
194
  const invoice = data?.list[index];
189
- const link = getInvoiceLink(invoice, action);
190
- return /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
191
- href: link.url,
192
- target: link.external ? "_blank" : target,
193
- rel: "noreferrer",
195
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
196
+ onClick: e => handleLinkClick(e, invoice),
197
+ sx: linkStyle,
194
198
  children: (0, _util.formatToDate)(invoice.created_at, locale, "YYYY-MM-DD HH:mm:ss")
195
199
  });
196
200
  }
@@ -202,11 +206,9 @@ const InvoiceTable = _react.default.memo(props => {
202
206
  sort: false,
203
207
  customBodyRenderLite: (val, index) => {
204
208
  const invoice = data?.list[index];
205
- const link = getInvoiceLink(invoice, action);
206
- return /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
207
- href: link.url,
208
- target: link.external ? "_blank" : target,
209
- rel: "noreferrer",
209
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
210
+ onClick: e => handleLinkClick(e, invoice),
211
+ sx: linkStyle,
210
212
  children: (0, _util.getInvoiceDescriptionAndReason)(invoice, locale)?.description || invoice.id
211
213
  });
212
214
  }
@@ -217,10 +219,12 @@ const InvoiceTable = _react.default.memo(props => {
217
219
  options: {
218
220
  customBodyRenderLite: (val, index) => {
219
221
  const invoice = data?.list[index];
220
- const link = getInvoiceLink(invoice, action);
221
222
  const hidePay = invoice.billing_reason === "overdraft-protection";
223
+ const {
224
+ connect
225
+ } = getInvoiceLink(invoice, action);
222
226
  if (action && !hidePay) {
223
- return link.connect ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
227
+ return connect ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
224
228
  variant: "text",
225
229
  size: "small",
226
230
  onClick: () => onPay(invoice.id),
@@ -232,8 +236,7 @@ const InvoiceTable = _react.default.memo(props => {
232
236
  component: "a",
233
237
  variant: "text",
234
238
  size: "small",
235
- href: link.url,
236
- target: link.external ? "_blank" : target,
239
+ onClick: e => handleLinkClick(e, invoice),
237
240
  sx: {
238
241
  color: "var(--foregrounds-fg-interactive, #0086FF) !important"
239
242
  },
@@ -241,10 +244,9 @@ const InvoiceTable = _react.default.memo(props => {
241
244
  children: t("payment.customer.invoice.pay")
242
245
  });
243
246
  }
244
- return /* @__PURE__ */(0, _jsxRuntime.jsx)("a", {
245
- href: link.url,
246
- target: link.external ? "_blank" : target,
247
- rel: "noreferrer",
247
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
248
+ onClick: e => handleLinkClick(e, invoice),
249
+ sx: linkStyle,
248
250
  children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
249
251
  label: invoice.status,
250
252
  color: (0, _util.getInvoiceStatusColor)(invoice.status)
@@ -330,6 +332,7 @@ const InvoiceList = _react.default.memo(props => {
330
332
  t,
331
333
  locale
332
334
  } = (0, _context.useLocaleContext)();
335
+ const navigate = (0, _reactRouterDom.useNavigate)();
333
336
  const {
334
337
  data,
335
338
  loadMore,
@@ -404,6 +407,11 @@ const InvoiceList = _react.default.memo(props => {
404
407
  }
405
408
  const hasMore = data && data.list.length < data.count;
406
409
  const grouped = groupByDate(data.list);
410
+ const handleLinkClick = (e, link) => {
411
+ (0, _navigation.handleNavigation)(e, link, navigate, {
412
+ target: link.external ? "_blank" : target
413
+ });
414
+ };
407
415
  return /* @__PURE__ */(0, _jsxRuntime.jsxs)(Root, {
408
416
  direction: "column",
409
417
  gap: 1,
@@ -420,7 +428,10 @@ const InvoiceList = _react.default.memo(props => {
420
428
  },
421
429
  children: date
422
430
  }), invoices.map(invoice => {
423
- const link = getInvoiceLink(invoice, action);
431
+ const {
432
+ link,
433
+ connect
434
+ } = getInvoiceLink(invoice, action);
424
435
  return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
425
436
  direction: "row",
426
437
  sx: {
@@ -439,6 +450,7 @@ const InvoiceList = _react.default.memo(props => {
439
450
  href: link.url,
440
451
  target: link.external ? "_blank" : target,
441
452
  rel: "noreferrer",
453
+ onClick: e => handleLinkClick(e, link),
442
454
  children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
443
455
  direction: "row",
444
456
  alignItems: "center",
@@ -482,7 +494,7 @@ const InvoiceList = _react.default.memo(props => {
482
494
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
483
495
  flex: 1,
484
496
  textAlign: "right",
485
- children: action ? link.connect ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
497
+ children: action ? connect ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
486
498
  variant: "contained",
487
499
  color: "primary",
488
500
  size: "small",
@@ -495,8 +507,7 @@ const InvoiceList = _react.default.memo(props => {
495
507
  component: "a",
496
508
  variant: "contained",
497
509
  size: "small",
498
- href: link.url,
499
- target: link.external ? "_blank" : target,
510
+ onClick: e => handleLinkClick(e, link),
500
511
  sx: {
501
512
  whiteSpace: "nowrap"
502
513
  },
@@ -0,0 +1,33 @@
1
+ import { NavigateFunction } from 'react-router-dom';
2
+ export interface LinkInfo {
3
+ url: string;
4
+ external: boolean;
5
+ path: string;
6
+ }
7
+ /**
8
+ * Check if currently running inside Payment Kit
9
+ */
10
+ export declare function isInPaymentKit(): boolean;
11
+ /**
12
+ * Create link information object
13
+ * @param path Path or URL
14
+ * @param external Force external link behavior
15
+ */
16
+ export declare function createLink(path: string, external?: boolean): LinkInfo;
17
+ /**
18
+ * Get HTML attributes for a link
19
+ * @param link Link information
20
+ * @param target Link target (default: _self)
21
+ */
22
+ export declare function getLinkProps(link: LinkInfo, target?: string): Record<string, any>;
23
+ /**
24
+ * Handle link navigation
25
+ * @param e Click event
26
+ * @param link Link information
27
+ * @param navigate React Router navigate function
28
+ * @param options Navigation options
29
+ */
30
+ export declare function handleNavigation(e: React.MouseEvent, link: LinkInfo, navigate?: NavigateFunction, options?: {
31
+ replace?: boolean;
32
+ target?: string;
33
+ }): void;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createLink = createLink;
7
+ exports.getLinkProps = getLinkProps;
8
+ exports.handleNavigation = handleNavigation;
9
+ exports.isInPaymentKit = isInPaymentKit;
10
+ var _ufo = require("ufo");
11
+ var _util = require("./util");
12
+ function isInPaymentKit() {
13
+ const componentId = (window.blocklet?.componentId || "").split("/").pop();
14
+ return componentId === _util.PAYMENT_KIT_DID;
15
+ }
16
+ function createLink(path, external = false) {
17
+ const isAbsoluteUrl = /^https?:\/\//.test(path);
18
+ const isExternal = external || isAbsoluteUrl;
19
+ const url = isExternal ? path : (0, _ufo.joinURL)((0, _util.getPrefix)(), path);
20
+ return {
21
+ url,
22
+ external: isExternal,
23
+ path
24
+ };
25
+ }
26
+ function getLinkProps(link, target = "_self") {
27
+ const props = {
28
+ href: link.url
29
+ };
30
+ if (link.external) {
31
+ props.target = target;
32
+ props.rel = target === "_blank" ? "noopener noreferrer" : void 0;
33
+ }
34
+ return props;
35
+ }
36
+ function handleNavigation(e, link, navigate, options = {}) {
37
+ if (e) {
38
+ e.preventDefault();
39
+ }
40
+ const {
41
+ replace = false,
42
+ target = "_self"
43
+ } = options;
44
+ if (link.external || target === "_blank") {
45
+ window.open(link.url, target);
46
+ return;
47
+ }
48
+ if (isInPaymentKit() && navigate) {
49
+ navigate(link.path, {
50
+ replace
51
+ });
52
+ return;
53
+ }
54
+ if (replace) {
55
+ window.location.replace(link.url);
56
+ } else {
57
+ window.location.href = link.url;
58
+ }
59
+ }
package/lib/locales/en.js CHANGED
@@ -252,8 +252,8 @@ module.exports = (0, _flat.default)({
252
252
  }
253
253
  },
254
254
  recover: {
255
- button: "Renew",
256
- title: "Renew your subscription",
255
+ button: "Resume Subscription",
256
+ title: "Resume Your Subscription",
257
257
  description: "Your subscription will not be canceled and will be automatically renewed on {date}, please confirm to continue"
258
258
  },
259
259
  changePlan: {
package/lib/locales/zh.js CHANGED
@@ -252,8 +252,8 @@ module.exports = (0, _flat.default)({
252
252
  }
253
253
  },
254
254
  recover: {
255
- button: "\u7EED\u8BA2",
256
- title: "\u7EED\u8BA2\u60A8\u7684\u8BA2\u9605",
255
+ button: "\u6062\u590D\u8BA2\u9605",
256
+ title: "\u6062\u590D\u60A8\u7684\u8BA2\u9605",
257
257
  description: "\u60A8\u7684\u8BA2\u9605\u5C06\u4E0D\u4F1A\u88AB\u53D6\u6D88\uFF0C\u5E76\u5C06\u5728{date}\u81EA\u52A8\u7EED\u8BA2\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u7EE7\u7EED"
258
258
  },
259
259
  changePlan: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.18.12",
3
+ "version": "1.18.13",
4
4
  "description": "Reusable react components for payment kit v2",
5
5
  "keywords": [
6
6
  "react",
@@ -92,7 +92,7 @@
92
92
  "@babel/core": "^7.25.2",
93
93
  "@babel/preset-env": "^7.25.2",
94
94
  "@babel/preset-react": "^7.24.7",
95
- "@blocklet/payment-types": "1.18.12",
95
+ "@blocklet/payment-types": "1.18.13",
96
96
  "@storybook/addon-essentials": "^7.6.20",
97
97
  "@storybook/addon-interactions": "^7.6.20",
98
98
  "@storybook/addon-links": "^7.6.20",
@@ -123,5 +123,5 @@
123
123
  "vite-plugin-babel": "^1.2.0",
124
124
  "vite-plugin-node-polyfills": "^0.21.0"
125
125
  },
126
- "gitHead": "7efd2af5c272e4928da6864edfcbd3f9054c722b"
126
+ "gitHead": "b26aa859ba5961c7a8dedfc238a31476cf16036c"
127
127
  }
@@ -22,6 +22,7 @@ export default function Link({ to, children, onClick, replace, target, outLink =
22
22
  e.preventDefault();
23
23
  window.location.href = to;
24
24
  }
25
+ e.preventDefault();
25
26
  onClick?.(e);
26
27
  };
27
28
 
@@ -22,6 +22,7 @@ type Props = {
22
22
  mode?: 'default' | 'custom';
23
23
  onPaid?: (subscriptionId: string, currencyId: string) => void;
24
24
  dialogProps?: DialogProps;
25
+ inSubscriptionDetail?: boolean;
25
26
  children?: (
26
27
  handlePay: (item: SummaryItem) => void,
27
28
  data: {
@@ -56,6 +57,7 @@ function OverdueInvoicePayment({
56
57
  dialogProps = {},
57
58
  children,
58
59
  onPaid = () => {},
60
+ inSubscriptionDetail = false,
59
61
  }: Props) {
60
62
  const { t } = useLocaleContext();
61
63
  const { connect } = usePaymentContext();
@@ -141,8 +143,15 @@ function OverdueInvoicePayment({
141
143
  dialogProps.onClose?.();
142
144
  };
143
145
 
146
+ const handleViewDetailClick = (e: React.MouseEvent) => {
147
+ if (inSubscriptionDetail) {
148
+ e.preventDefault();
149
+ handleClose();
150
+ }
151
+ };
152
+
144
153
  if (loading) {
145
- return <CircularProgress />;
154
+ return null;
146
155
  }
147
156
 
148
157
  const renderPayButton = (item: SummaryItem, props: any) => {
@@ -184,6 +193,7 @@ function OverdueInvoicePayment({
184
193
  {...(dialogProps || {})}
185
194
  open={dialogOpen}
186
195
  title={dialogProps?.title || t('payment.subscription.overdue.pastDue')}
196
+ sx={{ '& .MuiDialogContent-root': { pt: 0 } }}
187
197
  onClose={handleClose}>
188
198
  {error ? (
189
199
  <Alert severity="error">{error.message}</Alert>
@@ -220,6 +230,7 @@ function OverdueInvoicePayment({
220
230
  <a
221
231
  href={subscriptionUrl}
222
232
  target="_blank"
233
+ onClick={handleViewDetailClick}
223
234
  rel="noreferrer"
224
235
  style={{ color: 'var(--foregrounds-fg-interactive, 0086FF)' }}>
225
236
  {t('payment.subscription.overdue.view')}
@@ -250,6 +261,7 @@ function OverdueInvoicePayment({
250
261
  href={subscriptionUrl}
251
262
  target="_blank"
252
263
  rel="noreferrer"
264
+ onClick={handleViewDetailClick}
253
265
  style={{ color: 'var(--foregrounds-fg-interactive, 0086FF)' }}>
254
266
  {t('payment.subscription.overdue.view')}
255
267
  </a>
@@ -303,6 +315,7 @@ OverdueInvoicePayment.defaultProps = {
303
315
  open: true,
304
316
  },
305
317
  children: null,
318
+ inSubscriptionDetail: false,
306
319
  };
307
320
 
308
321
  export default OverdueInvoicePayment;
@@ -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: {