payment-kit 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/api/src/libs/subscription.ts +116 -0
- package/api/src/routes/checkout-sessions.ts +28 -1
- package/api/src/routes/customers.ts +5 -1
- package/api/src/store/migrations/20250318-donate-invoice.ts +45 -0
- package/api/tests/libs/subscription.spec.ts +311 -0
- package/blocklet.yml +1 -1
- package/package.json +8 -8
- package/src/components/currency.tsx +11 -4
- package/src/components/customer/link.tsx +54 -14
- package/src/components/customer/overdraft-protection.tsx +36 -2
- package/src/components/info-card.tsx +55 -7
- package/src/components/info-row-group.tsx +122 -0
- package/src/components/info-row.tsx +14 -1
- package/src/components/payouts/portal/list.tsx +7 -2
- package/src/components/subscription/items/index.tsx +1 -1
- package/src/components/subscription/metrics.tsx +14 -6
- package/src/contexts/info-row.tsx +4 -0
- package/src/libs/dayjs.ts +4 -3
- package/src/locales/en.tsx +1 -0
- package/src/locales/zh.tsx +1 -0
- package/src/pages/admin/billing/invoices/detail.tsx +54 -76
- package/src/pages/admin/billing/subscriptions/detail.tsx +34 -71
- package/src/pages/admin/customers/customers/detail.tsx +41 -64
- package/src/pages/admin/payments/intents/detail.tsx +28 -42
- package/src/pages/admin/payments/payouts/detail.tsx +27 -36
- package/src/pages/admin/payments/refunds/detail.tsx +27 -41
- package/src/pages/admin/products/links/detail.tsx +30 -55
- package/src/pages/admin/products/prices/detail.tsx +43 -50
- package/src/pages/admin/products/pricing-tables/detail.tsx +23 -25
- package/src/pages/admin/products/products/detail.tsx +52 -81
- package/src/pages/customer/index.tsx +189 -107
- package/src/pages/customer/invoice/detail.tsx +49 -50
- package/src/pages/customer/payout/detail.tsx +16 -22
- package/src/pages/customer/recharge/account.tsx +119 -34
- package/src/pages/customer/recharge/subscription.tsx +11 -1
- package/src/pages/customer/subscription/detail.tsx +176 -94
|
@@ -2,16 +2,22 @@ import type { TCustomer } from '@blocklet/payment-types';
|
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
3
|
|
|
4
4
|
import { getCustomerAvatar } from '@blocklet/payment-react';
|
|
5
|
+
import DID from '@arcblock/ux/lib/DID';
|
|
6
|
+
import { Box, Typography } from '@mui/material';
|
|
5
7
|
import InfoCard from '../info-card';
|
|
6
8
|
|
|
7
9
|
export default function CustomerLink({
|
|
8
10
|
customer,
|
|
9
11
|
linked,
|
|
10
12
|
linkTo,
|
|
13
|
+
size,
|
|
14
|
+
tooltip,
|
|
11
15
|
}: {
|
|
12
16
|
customer: TCustomer;
|
|
13
17
|
linked?: boolean;
|
|
14
18
|
linkTo?: string;
|
|
19
|
+
size?: 'default' | 'small';
|
|
20
|
+
tooltip?: boolean;
|
|
15
21
|
}) {
|
|
16
22
|
if (!customer) {
|
|
17
23
|
return null;
|
|
@@ -19,29 +25,63 @@ export default function CustomerLink({
|
|
|
19
25
|
if (linked) {
|
|
20
26
|
return (
|
|
21
27
|
<Link to={linkTo || `/admin/customers/${customer.id}`}>
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
<Box sx={{ '.info-card-wrapper': { cursor: 'pointer' }, '.info-card': { minWidth: 0 } }}>
|
|
29
|
+
{/* @ts-ignore */}
|
|
30
|
+
<InfoCard
|
|
31
|
+
logo={getCustomerAvatar(
|
|
32
|
+
customer?.did,
|
|
33
|
+
customer?.updated_at ? new Date(customer.updated_at).toISOString() : '',
|
|
34
|
+
size === 'small' ? 24 : 48
|
|
35
|
+
)}
|
|
36
|
+
name={
|
|
37
|
+
<Typography
|
|
38
|
+
sx={{ maxWidth: 208, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
|
39
|
+
className="customer-link-name">
|
|
40
|
+
{customer.name || customer.email}
|
|
41
|
+
</Typography>
|
|
42
|
+
}
|
|
43
|
+
{...(size === 'small'
|
|
44
|
+
? { tooltip: tooltip ? <DID did={customer?.did} /> : false, size: 24 }
|
|
45
|
+
: {
|
|
46
|
+
description: <DID did={customer?.did} compact responsive={false} sx={{ whiteSpace: 'nowrap' }} />,
|
|
47
|
+
size: 48,
|
|
48
|
+
})}
|
|
49
|
+
/>
|
|
50
|
+
</Box>
|
|
31
51
|
</Link>
|
|
32
52
|
);
|
|
33
53
|
}
|
|
34
54
|
|
|
35
55
|
return (
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
<Box sx={{ '.info-card': { minWidth: 0 } }}>
|
|
57
|
+
{/* @ts-ignore */}
|
|
58
|
+
<InfoCard
|
|
59
|
+
logo={getCustomerAvatar(
|
|
60
|
+
customer.did,
|
|
61
|
+
customer.updated_at ? new Date(customer.updated_at).toISOString() : '',
|
|
62
|
+
size === 'small' ? 24 : 48
|
|
63
|
+
)}
|
|
64
|
+
name={
|
|
65
|
+
<Typography
|
|
66
|
+
sx={{ maxWidth: 320, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
|
|
67
|
+
className="customer-link-name">
|
|
68
|
+
{customer.name || customer.email}
|
|
69
|
+
</Typography>
|
|
70
|
+
}
|
|
71
|
+
{...(size === 'small'
|
|
72
|
+
? { tooltip: tooltip ? <DID did={customer?.did} /> : false, size: 24 }
|
|
73
|
+
: {
|
|
74
|
+
description: <DID did={customer?.did} compact responsive={false} sx={{ whiteSpace: 'nowrap' }} />,
|
|
75
|
+
size: 48,
|
|
76
|
+
})}
|
|
77
|
+
/>
|
|
78
|
+
</Box>
|
|
41
79
|
);
|
|
42
80
|
}
|
|
43
81
|
|
|
44
82
|
CustomerLink.defaultProps = {
|
|
45
83
|
linked: true,
|
|
46
84
|
linkTo: '',
|
|
85
|
+
size: 'default',
|
|
86
|
+
tooltip: true,
|
|
47
87
|
};
|
|
@@ -26,7 +26,7 @@ import { useRequest } from 'ahooks';
|
|
|
26
26
|
import { BN, fromTokenToUnit, fromUnitToToken } from '@ocap/util';
|
|
27
27
|
import type { TPaymentCurrency, TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
28
28
|
import { joinURL } from 'ufo';
|
|
29
|
-
import { OpenInNewOutlined } from '@mui/icons-material';
|
|
29
|
+
import { HelpOutline, OpenInNewOutlined } from '@mui/icons-material';
|
|
30
30
|
import Currency from '../currency';
|
|
31
31
|
import { formatSmartDuration, TimeUnit } from '../../libs/dayjs';
|
|
32
32
|
|
|
@@ -228,7 +228,41 @@ export default function OverdraftProtectionDialog({
|
|
|
228
228
|
maxWidth="sm"
|
|
229
229
|
fullWidth
|
|
230
230
|
className="base-dialog"
|
|
231
|
-
title={
|
|
231
|
+
title={
|
|
232
|
+
<Stack direction="row" alignItems="center" spacing={1}>
|
|
233
|
+
<Typography variant="h6" component="span">
|
|
234
|
+
{t('customer.overdraftProtection.setting')}
|
|
235
|
+
</Typography>
|
|
236
|
+
<Link
|
|
237
|
+
href="https://www.arcblock.io/content/blog/en/payment-kit-v117-sub-guard#listen-to-the-audio-overview"
|
|
238
|
+
target="_blank"
|
|
239
|
+
rel="noopener noreferrer"
|
|
240
|
+
underline="hover"
|
|
241
|
+
sx={{
|
|
242
|
+
display: 'flex',
|
|
243
|
+
alignItems: 'center',
|
|
244
|
+
gap: 0.5,
|
|
245
|
+
fontSize: '14px',
|
|
246
|
+
fontWeight: 'normal',
|
|
247
|
+
ml: 1,
|
|
248
|
+
color: 'text.link',
|
|
249
|
+
'&:hover': { color: 'primary.main' },
|
|
250
|
+
...(isMobile ? { fontSize: '12px' } : {}),
|
|
251
|
+
}}>
|
|
252
|
+
<Tooltip title={t('customer.overdraftProtection.learnMore')} placement="top">
|
|
253
|
+
<HelpOutline
|
|
254
|
+
fontSize="small"
|
|
255
|
+
sx={{
|
|
256
|
+
fontSize: '16px',
|
|
257
|
+
color: 'text.secondary',
|
|
258
|
+
cursor: 'pointer',
|
|
259
|
+
'&:hover': { color: 'primary.main' },
|
|
260
|
+
}}
|
|
261
|
+
/>
|
|
262
|
+
</Tooltip>
|
|
263
|
+
</Link>
|
|
264
|
+
</Stack>
|
|
265
|
+
}
|
|
232
266
|
actions={
|
|
233
267
|
<Stack direction="row" spacing={2}>
|
|
234
268
|
<Button variant="outlined" onClick={onCancel}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getWordBreakStyle } from '@blocklet/payment-react';
|
|
2
|
-
import { Avatar, Stack, SxProps, Typography } from '@mui/material';
|
|
2
|
+
import { Avatar, Stack, SxProps, Typography, Tooltip } from '@mui/material';
|
|
3
3
|
import type { LiteralUnion } from 'type-fest';
|
|
4
4
|
|
|
5
5
|
type Props = {
|
|
@@ -11,13 +11,20 @@ type Props = {
|
|
|
11
11
|
sx?: SxProps;
|
|
12
12
|
className?: string;
|
|
13
13
|
logoName?: string;
|
|
14
|
+
tooltip?: string | React.ReactNode;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export default function InfoCard(props: Props) {
|
|
17
18
|
const dimensions = { width: props.size, height: props.size, ...props.sx };
|
|
18
19
|
const avatarName = typeof props.name === 'string' ? props.name : props.logo;
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
const cardContent = (
|
|
22
|
+
<Stack
|
|
23
|
+
direction="row"
|
|
24
|
+
alignItems="center"
|
|
25
|
+
spacing={1}
|
|
26
|
+
className={`info-card-wrapper ${props.className}`}
|
|
27
|
+
sx={{ cursor: props.tooltip ? 'pointer' : 'default' }}>
|
|
21
28
|
{props.logo ? (
|
|
22
29
|
<Avatar src={props.logo} alt={props.logoName ?? avatarName} variant={props.variant as any} sx={dimensions} />
|
|
23
30
|
) : (
|
|
@@ -30,16 +37,56 @@ export default function InfoCard(props: Props) {
|
|
|
30
37
|
alignItems="flex-start"
|
|
31
38
|
justifyContent="space-around"
|
|
32
39
|
className="info-card"
|
|
33
|
-
sx={{
|
|
40
|
+
sx={{
|
|
41
|
+
wordBreak: getWordBreakStyle(props.name),
|
|
42
|
+
minWidth: 140,
|
|
43
|
+
}}>
|
|
34
44
|
<Typography variant="body1" color="text.primary" component="div">
|
|
35
45
|
{props.name}
|
|
36
46
|
</Typography>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
{props.description && (
|
|
48
|
+
<Typography variant="subtitle1" color="text.secondary">
|
|
49
|
+
{props.description}
|
|
50
|
+
</Typography>
|
|
51
|
+
)}
|
|
40
52
|
</Stack>
|
|
41
53
|
</Stack>
|
|
42
54
|
);
|
|
55
|
+
|
|
56
|
+
if (props.tooltip) {
|
|
57
|
+
return (
|
|
58
|
+
<Tooltip
|
|
59
|
+
title={props.tooltip}
|
|
60
|
+
placement="top-start"
|
|
61
|
+
componentsProps={{
|
|
62
|
+
tooltip: {
|
|
63
|
+
sx: {
|
|
64
|
+
bgcolor: (theme) => (theme.palette.mode === 'dark' ? '#2a2e37' : '#ffffff'),
|
|
65
|
+
color: (theme) => (theme.palette.mode === 'dark' ? '#ffffff' : theme.palette.text.primary),
|
|
66
|
+
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.15)',
|
|
67
|
+
borderRadius: 2,
|
|
68
|
+
padding: '10px 16px',
|
|
69
|
+
fontSize: 14,
|
|
70
|
+
maxWidth: 400,
|
|
71
|
+
minWidth: 240,
|
|
72
|
+
wordBreak: 'break-word',
|
|
73
|
+
border: (theme) => (theme.palette.mode === 'dark' ? '1px solid #3a3f48' : '1px solid #e0e0e0'),
|
|
74
|
+
'& .MuiTooltip-arrow': {
|
|
75
|
+
color: (theme) => (theme.palette.mode === 'dark' ? '#2a2e37' : '#ffffff'),
|
|
76
|
+
'&::before': {
|
|
77
|
+
border: (theme) => (theme.palette.mode === 'dark' ? '1px solid #3a3f48' : '1px solid #e0e0e0'),
|
|
78
|
+
backgroundColor: (theme) => (theme.palette.mode === 'dark' ? '#2a2e37' : '#ffffff'),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}}>
|
|
84
|
+
{cardContent}
|
|
85
|
+
</Tooltip>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return cardContent;
|
|
43
90
|
}
|
|
44
91
|
|
|
45
92
|
InfoCard.defaultProps = {
|
|
@@ -49,4 +96,5 @@ InfoCard.defaultProps = {
|
|
|
49
96
|
sx: {},
|
|
50
97
|
className: '',
|
|
51
98
|
logoName: '',
|
|
99
|
+
tooltip: false,
|
|
52
100
|
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Box, Skeleton, SxProps } from '@mui/material';
|
|
2
|
+
import React, {
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
Children,
|
|
7
|
+
cloneElement,
|
|
8
|
+
isValidElement,
|
|
9
|
+
ReactElement,
|
|
10
|
+
useLayoutEffect,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
13
|
+
import { InfoRowGroupContext } from '../contexts/info-row';
|
|
14
|
+
|
|
15
|
+
type InfoRowGroupProps = {
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
loading?: boolean;
|
|
18
|
+
skeletonHeight?: number;
|
|
19
|
+
sx?: SxProps;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function InfoRowGroup({ children, loading = false, skeletonHeight = 24, sx = {} }: InfoRowGroupProps) {
|
|
23
|
+
const [maxLabelWidth, setMaxLabelWidth] = useState<number>(0);
|
|
24
|
+
const [measurementStage, setMeasurementStage] = useState<'measuring' | 'complete'>('measuring');
|
|
25
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
26
|
+
const { locale } = useLocaleContext();
|
|
27
|
+
|
|
28
|
+
const resetMeasurement = () => {
|
|
29
|
+
setMeasurementStage('measuring');
|
|
30
|
+
setMaxLabelWidth(0);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
if (loading || !containerRef.current) return;
|
|
35
|
+
|
|
36
|
+
if (measurementStage === 'measuring' || maxLabelWidth === 0) {
|
|
37
|
+
const labelElements = containerRef.current.querySelectorAll('.info-row-label');
|
|
38
|
+
|
|
39
|
+
if (labelElements.length > 0) {
|
|
40
|
+
let maxWidth = 0;
|
|
41
|
+
labelElements.forEach((label) => {
|
|
42
|
+
const width = (label as HTMLElement).offsetWidth;
|
|
43
|
+
if (width > maxWidth) {
|
|
44
|
+
maxWidth = width;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
maxWidth += 16;
|
|
49
|
+
|
|
50
|
+
if (maxWidth > 0) {
|
|
51
|
+
setMaxLabelWidth(maxWidth);
|
|
52
|
+
requestAnimationFrame(() => {
|
|
53
|
+
setMeasurementStage('complete');
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, [children, loading, measurementStage, locale]); // 添加locale作为依赖项
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
resetMeasurement();
|
|
62
|
+
}, [locale]);
|
|
63
|
+
|
|
64
|
+
// Create skeleton rows for loading state
|
|
65
|
+
const renderSkeletons = () => {
|
|
66
|
+
// Count the number of InfoRow children
|
|
67
|
+
let count = 0;
|
|
68
|
+
Children.forEach(children, (child) => {
|
|
69
|
+
if (isValidElement(child) && child.type && (child.type as any).displayName === 'InfoRow') {
|
|
70
|
+
count++;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return Array(count)
|
|
75
|
+
.fill(0)
|
|
76
|
+
.map((_, index) => (
|
|
77
|
+
<Box key={`skeleton-row-${count - index}`} sx={{ display: 'flex', gap: 2 }}>
|
|
78
|
+
<Skeleton width="30%" height={skeletonHeight} />
|
|
79
|
+
<Skeleton width="65%" height={skeletonHeight} />
|
|
80
|
+
</Box>
|
|
81
|
+
));
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Clone children with calculated width
|
|
85
|
+
const renderChildren = () => {
|
|
86
|
+
return Children.map(children, (child) => {
|
|
87
|
+
if (isValidElement(child) && child.type && (child.type as any).displayName === 'InfoRow') {
|
|
88
|
+
const childProps = {
|
|
89
|
+
...child.props,
|
|
90
|
+
sx: {
|
|
91
|
+
...child.props.sx,
|
|
92
|
+
visibility: measurementStage === 'measuring' ? 'hidden' : 'visible',
|
|
93
|
+
'.info-row-label': {
|
|
94
|
+
width: maxLabelWidth > 0 ? `${maxLabelWidth}px` : 'auto',
|
|
95
|
+
minWidth: maxLabelWidth > 0 ? `${maxLabelWidth}px` : 'auto',
|
|
96
|
+
whiteSpace: 'nowrap',
|
|
97
|
+
transition: 'width 0.3s ease-out, min-width 0.3s ease-out',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
return cloneElement(child as ReactElement, childProps);
|
|
102
|
+
}
|
|
103
|
+
return child;
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<InfoRowGroupContext.Provider value>
|
|
109
|
+
<Box ref={containerRef} sx={{ ...sx }}>
|
|
110
|
+
{loading ? renderSkeletons() : renderChildren()}
|
|
111
|
+
</Box>
|
|
112
|
+
</InfoRowGroupContext.Provider>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
InfoRowGroup.defaultProps = {
|
|
117
|
+
loading: false,
|
|
118
|
+
skeletonHeight: 24,
|
|
119
|
+
sx: {},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export default InfoRowGroup;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
import { Box, Stack, SxProps } from '@mui/material';
|
|
3
|
+
import { useContext } from 'react';
|
|
3
4
|
import type { ReactNode } from 'react';
|
|
4
5
|
import { getWordBreakStyle } from '@blocklet/payment-react';
|
|
5
6
|
import { isEmptyExceptNumber } from '../libs/util';
|
|
7
|
+
import { InfoRowGroupContext } from '../contexts/info-row';
|
|
6
8
|
|
|
7
9
|
type Props = {
|
|
8
10
|
label: string | ReactNode;
|
|
@@ -27,6 +29,7 @@ export default function InfoRow(props: Props) {
|
|
|
27
29
|
const { t } = useLocaleContext();
|
|
28
30
|
const isNone = isEmptyExceptNumber(props.value);
|
|
29
31
|
const sizes = props.sizes || [1, 3];
|
|
32
|
+
const inGroup = useContext(InfoRowGroupContext);
|
|
30
33
|
return (
|
|
31
34
|
<Stack
|
|
32
35
|
direction={props.direction || 'row'}
|
|
@@ -47,11 +50,18 @@ export default function InfoRow(props: Props) {
|
|
|
47
50
|
flex={sizes[0]}
|
|
48
51
|
className="info-row-label"
|
|
49
52
|
color="text.primary"
|
|
50
|
-
fontWeight={500}
|
|
51
53
|
fontSize={14}
|
|
52
54
|
sx={{
|
|
53
55
|
flex: props.direction === 'column' ? 'none' : sizes[0],
|
|
54
56
|
mb: props.direction === 'column' ? 0.5 : 0,
|
|
57
|
+
...(inGroup && {
|
|
58
|
+
flex: 'none',
|
|
59
|
+
whiteSpace: 'nowrap',
|
|
60
|
+
}),
|
|
61
|
+
fontWeight: {
|
|
62
|
+
xs: 500,
|
|
63
|
+
md: 400,
|
|
64
|
+
},
|
|
55
65
|
}}>
|
|
56
66
|
{props.label}
|
|
57
67
|
</Box>
|
|
@@ -68,3 +78,6 @@ export default function InfoRow(props: Props) {
|
|
|
68
78
|
</Stack>
|
|
69
79
|
);
|
|
70
80
|
}
|
|
81
|
+
|
|
82
|
+
// Add display name for identification
|
|
83
|
+
InfoRow.displayName = 'InfoRow';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import { api, formatBNStr, formatTime, Table, useDefaultPageSize } from '@blocklet/payment-react';
|
|
3
|
+
import { api, formatBNStr, formatTime, Table, useDefaultPageSize, useMobile } from '@blocklet/payment-react';
|
|
4
4
|
import type { TCustomer, TPaymentIntentExpanded, TPayoutExpanded } from '@blocklet/payment-types';
|
|
5
5
|
import { Box, Typography } from '@mui/material';
|
|
6
6
|
import { useLocalStorageState } from 'ahooks';
|
|
@@ -64,6 +64,7 @@ CustomerRevenueList.defaultProps = {
|
|
|
64
64
|
|
|
65
65
|
export default function CustomerRevenueList({ currency_id, status, customer_id }: ListProps) {
|
|
66
66
|
const { t } = useLocaleContext();
|
|
67
|
+
const { isMobile } = useMobile('sm');
|
|
67
68
|
|
|
68
69
|
const listKey = getListKey({ customer_id });
|
|
69
70
|
const defaultPageSize = useDefaultPageSize(10);
|
|
@@ -121,7 +122,11 @@ export default function CustomerRevenueList({ currency_id, status, customer_id }
|
|
|
121
122
|
};
|
|
122
123
|
// @ts-ignore
|
|
123
124
|
return item?.paymentIntent?.customer ? (
|
|
124
|
-
<CustomerLink
|
|
125
|
+
<CustomerLink
|
|
126
|
+
customer={item.paymentIntent.customer}
|
|
127
|
+
linkTo={`/customer/payout/${item.id}`}
|
|
128
|
+
size={isMobile ? 'small' : 'default'}
|
|
129
|
+
/>
|
|
125
130
|
) : (
|
|
126
131
|
t('common.none')
|
|
127
132
|
);
|
|
@@ -6,7 +6,7 @@ import { useRequest } from 'ahooks';
|
|
|
6
6
|
import { Button, Stack, Typography, Tooltip, Avatar, Box, CircularProgress, Skeleton } from '@mui/material';
|
|
7
7
|
import { BN } from '@ocap/util';
|
|
8
8
|
import { useNavigate } from 'react-router-dom';
|
|
9
|
-
import {
|
|
9
|
+
import { AccountBalanceWalletOutlined, ArrowForward } from '@mui/icons-material';
|
|
10
10
|
import InfoMetric from '../info-metric';
|
|
11
11
|
import SubscriptionStatus from './status';
|
|
12
12
|
|
|
@@ -59,7 +59,12 @@ export default function SubscriptionMetrics({ subscription, showBalance = true }
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
|
-
<Stack
|
|
62
|
+
<Stack
|
|
63
|
+
flexDirection="row"
|
|
64
|
+
alignItems="center"
|
|
65
|
+
gap={0.5}
|
|
66
|
+
sx={{ fontSize: '16px', fontWeight: 500, cursor: 'pointer', '&:hover': { color: 'primary.main' } }}
|
|
67
|
+
onClick={() => navigate(`/customer/subscription/${subscription.id}/recharge`)}>
|
|
63
68
|
<Avatar
|
|
64
69
|
src={subscription.paymentCurrency?.logo}
|
|
65
70
|
sx={{ width: 16, height: 16 }}
|
|
@@ -67,9 +72,7 @@ export default function SubscriptionMetrics({ subscription, showBalance = true }
|
|
|
67
72
|
/>
|
|
68
73
|
<Box display="flex" alignItems="baseline">
|
|
69
74
|
{formatBNStr(payerValue?.token, subscription.paymentCurrency.decimal)}
|
|
70
|
-
<Typography sx={{ fontSize: '14px',
|
|
71
|
-
{subscription.paymentCurrency.symbol}
|
|
72
|
-
</Typography>
|
|
75
|
+
<Typography sx={{ fontSize: '14px', ml: 0.5 }}>{subscription.paymentCurrency.symbol}</Typography>
|
|
73
76
|
</Box>
|
|
74
77
|
</Stack>
|
|
75
78
|
);
|
|
@@ -105,13 +108,18 @@ export default function SubscriptionMetrics({ subscription, showBalance = true }
|
|
|
105
108
|
{payerLoading ? <Skeleton width={120} /> : payerValue?.paymentAddress}
|
|
106
109
|
</Typography>
|
|
107
110
|
}
|
|
111
|
+
placement="top"
|
|
108
112
|
arrow>
|
|
109
|
-
<
|
|
113
|
+
<AccountBalanceWalletOutlined
|
|
110
114
|
sx={{
|
|
111
115
|
fontSize: '16px',
|
|
112
116
|
color: 'text.secondary',
|
|
113
117
|
cursor: 'pointer',
|
|
114
118
|
'&:hover': { color: 'primary.main' },
|
|
119
|
+
display: {
|
|
120
|
+
xs: 'none',
|
|
121
|
+
md: 'block',
|
|
122
|
+
},
|
|
115
123
|
}}
|
|
116
124
|
/>
|
|
117
125
|
</Tooltip>
|
package/src/libs/dayjs.ts
CHANGED
|
@@ -54,8 +54,9 @@ export const formatSmartDuration = (
|
|
|
54
54
|
{ t, separator = ' ' }: FormatDurationOptions
|
|
55
55
|
): string => {
|
|
56
56
|
// Format single unit
|
|
57
|
-
const formatUnit = (val: number, unitType: TimeUnit): string =>
|
|
58
|
-
`${val} ${t(`common.${unitType}${val > 1 ? 's' : ''}`).toLowerCase()}`;
|
|
57
|
+
const formatUnit = (val: number, unitType: TimeUnit): string => {
|
|
58
|
+
return `${val || 0} ${t(`common.${unitType}${val > 1 ? 's' : ''}`).toLowerCase()}`;
|
|
59
|
+
};
|
|
59
60
|
|
|
60
61
|
// Convert to largest possible unit
|
|
61
62
|
const convertToLargest = (val: number, fromUnit: TimeUnit): [TimeUnit, number][] => {
|
|
@@ -129,7 +130,7 @@ export const formatSmartDuration = (
|
|
|
129
130
|
|
|
130
131
|
// Get units and filter out zero values
|
|
131
132
|
const units = convertToLargest(value, unit).filter(([, val]) => val > 0);
|
|
132
|
-
|
|
133
|
+
if (units.length === 0) return formatUnit(0, unit);
|
|
133
134
|
// Format all units
|
|
134
135
|
return units.map(([u, val]) => formatUnit(val, u)).join(separator);
|
|
135
136
|
};
|
package/src/locales/en.tsx
CHANGED
|
@@ -809,6 +809,7 @@ export default flat({
|
|
|
809
809
|
overdraftProtection: {
|
|
810
810
|
title: 'SubGuard™',
|
|
811
811
|
setting: 'Set SubGuard™',
|
|
812
|
+
learnMore: 'Click to learn more about SubGuard™',
|
|
812
813
|
tip: 'To avoid service interruption due to unpaid invoices, you can enable SubGuard™ by staking. Timely payment will not incur additional fees. Please settle your invoices promptly. If your available stake is insufficient or payment is overdue, we will deduct the amount from your stake and charge a service fee.',
|
|
813
814
|
enabled: 'Enabled',
|
|
814
815
|
disabled: 'Disabled',
|
package/src/locales/zh.tsx
CHANGED