payment-kit 1.14.29 → 1.14.31
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/index.ts +4 -0
- package/api/src/libs/api.ts +23 -0
- package/api/src/libs/subscription.ts +32 -0
- package/api/src/queues/refund.ts +38 -1
- package/api/src/queues/subscription.ts +218 -21
- package/api/src/routes/checkout-sessions.ts +5 -0
- package/api/src/routes/customers.ts +27 -1
- package/api/src/routes/invoices.ts +5 -1
- package/api/src/routes/payment-intents.ts +17 -2
- package/api/src/routes/payment-links.ts +105 -3
- package/api/src/routes/payouts.ts +5 -1
- package/api/src/routes/prices.ts +19 -3
- package/api/src/routes/pricing-table.ts +79 -2
- package/api/src/routes/products.ts +24 -8
- package/api/src/routes/refunds.ts +7 -4
- package/api/src/routes/subscription-items.ts +5 -1
- package/api/src/routes/subscriptions.ts +38 -6
- package/api/src/routes/webhook-endpoints.ts +5 -1
- package/api/src/store/models/subscription.ts +1 -0
- package/api/tests/libs/api.spec.ts +72 -1
- package/api/third.d.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +19 -18
- package/src/components/customer/form.tsx +53 -0
- package/src/components/filter-toolbar.tsx +1 -1
- package/src/components/invoice/list.tsx +8 -8
- package/src/components/invoice/table.tsx +42 -36
- package/src/components/metadata/form.tsx +24 -3
- package/src/components/payment-intent/actions.tsx +17 -5
- package/src/components/payment-link/after-pay.tsx +46 -4
- package/src/components/payouts/list.tsx +1 -1
- package/src/components/price/form.tsx +14 -2
- package/src/components/pricing-table/payment-settings.tsx +45 -4
- package/src/components/product/features.tsx +16 -2
- package/src/components/product/form.tsx +28 -4
- package/src/components/subscription/actions/cancel.tsx +10 -0
- package/src/components/subscription/description.tsx +2 -2
- package/src/components/subscription/items/index.tsx +3 -2
- package/src/components/subscription/portal/cancel.tsx +12 -1
- package/src/components/subscription/portal/list.tsx +169 -145
- package/src/hooks/loading.ts +28 -0
- package/src/locales/en.tsx +6 -1
- package/src/locales/zh.tsx +6 -1
- package/src/pages/admin/billing/invoices/detail.tsx +17 -2
- package/src/pages/admin/billing/subscriptions/detail.tsx +4 -0
- package/src/pages/admin/customers/customers/detail.tsx +4 -0
- package/src/pages/admin/customers/customers/index.tsx +1 -1
- package/src/pages/admin/payments/intents/detail.tsx +4 -0
- package/src/pages/admin/payments/payouts/detail.tsx +4 -0
- package/src/pages/admin/payments/refunds/detail.tsx +4 -0
- package/src/pages/admin/products/links/detail.tsx +4 -0
- package/src/pages/admin/products/prices/detail.tsx +4 -0
- package/src/pages/admin/products/pricing-tables/detail.tsx +4 -0
- package/src/pages/admin/products/products/detail.tsx +4 -0
- package/src/pages/checkout/pricing-table.tsx +9 -3
- package/src/pages/customer/index.tsx +28 -17
- package/src/pages/customer/invoice/detail.tsx +27 -16
- package/src/pages/customer/invoice/past-due.tsx +3 -2
- package/src/pages/customer/subscription/detail.tsx +4 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
import { AddOutlined, DeleteOutlineOutlined } from '@mui/icons-material';
|
|
3
3
|
import { Box, Button, IconButton, TextField, Typography } from '@mui/material';
|
|
4
|
+
import { get } from 'lodash';
|
|
4
5
|
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
|
|
5
6
|
|
|
6
7
|
export default function MetadataForm() {
|
|
7
8
|
const { t } = useLocaleContext();
|
|
8
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
control,
|
|
11
|
+
formState: { errors },
|
|
12
|
+
} = useFormContext();
|
|
9
13
|
const features = useFieldArray({ control, name: 'features' });
|
|
10
14
|
return (
|
|
11
15
|
<Box sx={{ width: 1 }}>
|
|
@@ -13,7 +17,17 @@ export default function MetadataForm() {
|
|
|
13
17
|
{features.fields.map((feature, index) => (
|
|
14
18
|
<Box key={feature.id} mt={2} sx={{ width: 1 }}>
|
|
15
19
|
<Controller
|
|
16
|
-
render={({ field }) =>
|
|
20
|
+
render={({ field }) => (
|
|
21
|
+
<TextField
|
|
22
|
+
{...field}
|
|
23
|
+
sx={{ width: '80%' }}
|
|
24
|
+
size="small"
|
|
25
|
+
inputProps={{ maxLength: 64 }}
|
|
26
|
+
error={!!get(errors, field.name)}
|
|
27
|
+
helperText={get(errors, field.name)?.message as string}
|
|
28
|
+
/>
|
|
29
|
+
)}
|
|
30
|
+
rules={{ maxLength: { value: 64, message: t('common.maxLength', { len: 64 }) } }}
|
|
17
31
|
name={`features.${index}.name`}
|
|
18
32
|
control={control}
|
|
19
33
|
/>
|
|
@@ -38,16 +38,29 @@ export default function ProductForm(props: Props) {
|
|
|
38
38
|
<Stack spacing={2} flex={2} alignItems="flex-start">
|
|
39
39
|
<FormInput
|
|
40
40
|
name="name"
|
|
41
|
-
rules={{
|
|
41
|
+
rules={{
|
|
42
|
+
required: t('admin.product.name.required'),
|
|
43
|
+
maxLength: {
|
|
44
|
+
value: 64,
|
|
45
|
+
message: t('common.maxLength', { len: 64 }),
|
|
46
|
+
},
|
|
47
|
+
}}
|
|
42
48
|
label={t('admin.product.name.label')}
|
|
43
49
|
placeholder={t('admin.product.name.placeholder')}
|
|
44
50
|
error={!!formState.errors.name}
|
|
45
51
|
helperText={formState.errors.name?.message as string}
|
|
46
52
|
autoFocus
|
|
53
|
+
inputProps={{ maxLength: 64 }}
|
|
47
54
|
/>
|
|
48
55
|
<FormInput
|
|
49
56
|
name="description"
|
|
50
|
-
rules={{
|
|
57
|
+
rules={{
|
|
58
|
+
required: t('admin.product.description.required'),
|
|
59
|
+
maxLength: {
|
|
60
|
+
value: 256,
|
|
61
|
+
message: t('common.maxLength', { len: 256 }),
|
|
62
|
+
},
|
|
63
|
+
}}
|
|
51
64
|
label={t('admin.product.description.label')}
|
|
52
65
|
placeholder={t('admin.product.description.placeholder')}
|
|
53
66
|
error={!!formState.errors.description}
|
|
@@ -55,11 +68,22 @@ export default function ProductForm(props: Props) {
|
|
|
55
68
|
multiline
|
|
56
69
|
minRows={2}
|
|
57
70
|
maxRows={4}
|
|
71
|
+
inputProps={{ maxLength: 256 }}
|
|
58
72
|
/>
|
|
59
73
|
<Collapse trigger={t('admin.product.additional')}>
|
|
60
74
|
<Stack spacing={2} alignItems="flex-start">
|
|
61
|
-
<FormInput
|
|
62
|
-
|
|
75
|
+
<FormInput
|
|
76
|
+
name="statement_descriptor"
|
|
77
|
+
label={t('admin.product.statement_descriptor.label')}
|
|
78
|
+
rules={{ maxLength: { value: 32, message: t('common.maxLength', { len: 32 }) } }}
|
|
79
|
+
inputProps={{ maxLength: 32 }}
|
|
80
|
+
/>
|
|
81
|
+
<FormInput
|
|
82
|
+
name="unit_label"
|
|
83
|
+
label={t('admin.product.unit_label.label')}
|
|
84
|
+
rules={{ maxLength: { value: 32, message: t('common.maxLength', { len: 32 }) } }}
|
|
85
|
+
inputProps={{ maxLength: 32 }}
|
|
86
|
+
/>
|
|
63
87
|
{!props.simple && <ProductFeatures />}
|
|
64
88
|
{!props.simple && <MetadataForm title={t('common.metadata.label')} />}
|
|
65
89
|
</Stack>
|
|
@@ -147,6 +147,16 @@ export default function SubscriptionCancelForm({ data }: { data: TSubscriptionEx
|
|
|
147
147
|
symbol,
|
|
148
148
|
})}
|
|
149
149
|
/>
|
|
150
|
+
<FormControlLabel
|
|
151
|
+
value="slash"
|
|
152
|
+
disabled={loading || !staking}
|
|
153
|
+
onClick={() => !(loading || !staking) && setValue('cancel.staking', 'slash')}
|
|
154
|
+
control={<Radio checked={stakingType === 'slash'} />}
|
|
155
|
+
label={t('admin.subscription.cancel.staking.slash', {
|
|
156
|
+
unused: formatAmount(staking?.return_amount || '0', decimal),
|
|
157
|
+
symbol,
|
|
158
|
+
})}
|
|
159
|
+
/>
|
|
150
160
|
</RadioGroup>
|
|
151
161
|
</Stack>
|
|
152
162
|
</>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatSubscriptionProduct, useMobile } from '@blocklet/payment-react';
|
|
1
|
+
import { formatSubscriptionProduct, TruncatedText, useMobile } from '@blocklet/payment-react';
|
|
2
2
|
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
3
3
|
import { InfoOutlined } from '@mui/icons-material';
|
|
4
4
|
import { Stack, Tooltip, Typography } from '@mui/material';
|
|
@@ -15,7 +15,7 @@ export default function SubscriptionDescription({ subscription, variant, hideSub
|
|
|
15
15
|
return (
|
|
16
16
|
<Stack direction="row" alignItems="center" spacing={1}>
|
|
17
17
|
<Typography variant={variant} fontWeight={600} className="subscription-description">
|
|
18
|
-
{subscription.description}
|
|
18
|
+
<TruncatedText text={subscription.description} maxLength={80} useWidth />
|
|
19
19
|
</Typography>
|
|
20
20
|
{!hideSubscription && !isMobile && (
|
|
21
21
|
<Tooltip title={formatSubscriptionProduct(subscription.items)}>
|
|
@@ -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 { formatPrice, Table } from '@blocklet/payment-react';
|
|
3
|
+
import { formatPrice, Table, TruncatedText, useMobile } from '@blocklet/payment-react';
|
|
4
4
|
import type { TPaymentCurrency, TSubscriptionItemExpanded } from '@blocklet/payment-types';
|
|
5
5
|
import { Avatar, Stack, Typography } from '@mui/material';
|
|
6
6
|
|
|
@@ -22,6 +22,7 @@ const size = { width: 48, height: 48 };
|
|
|
22
22
|
|
|
23
23
|
export default function SubscriptionItemList({ data, currency, mode }: ListProps) {
|
|
24
24
|
const { t } = useLocaleContext();
|
|
25
|
+
const { isMobile } = useMobile();
|
|
25
26
|
const columns = [
|
|
26
27
|
{
|
|
27
28
|
label: t('admin.subscription.product'),
|
|
@@ -53,7 +54,7 @@ export default function SubscriptionItemList({ data, currency, mode }: ListProps
|
|
|
53
54
|
</Avatar>
|
|
54
55
|
)}
|
|
55
56
|
<Typography color="text.primary" fontWeight={600}>
|
|
56
|
-
{item?.price.product.name}
|
|
57
|
+
<TruncatedText text={item?.price.product.name} maxLength={isMobile ? 20 : 50} useWidth />
|
|
57
58
|
{mode === 'customer' ? '' : ` - ${item?.price_id}`}
|
|
58
59
|
</Typography>
|
|
59
60
|
<Typography color="text.secondary" whiteSpace="nowrap">
|
|
@@ -11,7 +11,9 @@ export default function CustomerCancelForm({ data }: { data: TSubscriptionExpand
|
|
|
11
11
|
return (
|
|
12
12
|
<Stack direction="column" spacing={1} alignItems="flex-start">
|
|
13
13
|
<Typography>
|
|
14
|
-
{
|
|
14
|
+
{data.status === 'trialing'
|
|
15
|
+
? t('payment.customer.cancel.trialDescription')
|
|
16
|
+
: t('payment.customer.cancel.description', { date: formatTime(data.current_period_end * 1000) })}
|
|
15
17
|
</Typography>
|
|
16
18
|
<Controller
|
|
17
19
|
name="cancel.feedback"
|
|
@@ -66,6 +68,12 @@ export default function CustomerCancelForm({ data }: { data: TSubscriptionExpand
|
|
|
66
68
|
<Controller
|
|
67
69
|
name="cancel.comment"
|
|
68
70
|
control={control}
|
|
71
|
+
rules={{
|
|
72
|
+
maxLength: {
|
|
73
|
+
value: 200,
|
|
74
|
+
message: t('common.maxLength', { len: 200 }),
|
|
75
|
+
},
|
|
76
|
+
}}
|
|
69
77
|
render={({ field }) => (
|
|
70
78
|
<TextField
|
|
71
79
|
variant="outlined"
|
|
@@ -76,6 +84,9 @@ export default function CustomerCancelForm({ data }: { data: TSubscriptionExpand
|
|
|
76
84
|
maxRows={4}
|
|
77
85
|
placeholder={t('payment.customer.cancel.comment')}
|
|
78
86
|
{...field}
|
|
87
|
+
inputProps={{
|
|
88
|
+
maxLength: 200,
|
|
89
|
+
}}
|
|
79
90
|
/>
|
|
80
91
|
)}
|
|
81
92
|
/>
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
3
|
import Empty from '@arcblock/ux/lib/Empty';
|
|
4
|
-
import {
|
|
5
|
-
Status,
|
|
6
|
-
api,
|
|
7
|
-
formatPrice,
|
|
8
|
-
getSubscriptionStatusColor,
|
|
9
|
-
getSubscriptionTimeSummary,
|
|
10
|
-
useMobile,
|
|
11
|
-
formatSubscriptionStatus,
|
|
12
|
-
} from '@blocklet/payment-react';
|
|
4
|
+
import { api, formatPrice, getSubscriptionTimeSummary, useMobile } from '@blocklet/payment-react';
|
|
13
5
|
import type { Paginated, TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
14
6
|
import { Avatar, AvatarGroup, Box, Button, CircularProgress, Stack, StackProps, Typography } from '@mui/material';
|
|
15
7
|
import { useInfiniteScroll } from 'ahooks';
|
|
16
8
|
|
|
9
|
+
import { useRef } from 'react';
|
|
17
10
|
import SubscriptionDescription from '../description';
|
|
18
11
|
import SubscriptionActions from './actions';
|
|
12
|
+
import SubscriptionStatus from '../status';
|
|
13
|
+
import useDelayedLoading from '../../../hooks/loading';
|
|
19
14
|
|
|
20
15
|
const fetchData = (params: Record<string, any> = {}): Promise<Paginated<TSubscriptionExpanded>> => {
|
|
21
16
|
const search = new URLSearchParams();
|
|
@@ -47,168 +42,197 @@ export default function CurrentSubscriptions({
|
|
|
47
42
|
}: Props) {
|
|
48
43
|
const { t } = useLocaleContext();
|
|
49
44
|
const { isMobile } = useMobile();
|
|
45
|
+
const listRef = useRef<HTMLDivElement | null>(null);
|
|
50
46
|
|
|
51
|
-
const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TSubscriptionExpanded>>(
|
|
47
|
+
const { data, loadMore, loadingMore, loading, reload } = useInfiniteScroll<Paginated<TSubscriptionExpanded>>(
|
|
52
48
|
(d) => {
|
|
53
49
|
const page = d ? Math.ceil(d.list.length / pageSize) + 1 : 1;
|
|
54
|
-
return fetchData({ page, pageSize, status, customer_id: id });
|
|
50
|
+
return fetchData({ page, pageSize, status, customer_id: id, activeFirst: true });
|
|
55
51
|
},
|
|
56
52
|
{
|
|
57
53
|
reloadDeps: [id, status],
|
|
54
|
+
...(isMobile
|
|
55
|
+
? {}
|
|
56
|
+
: {
|
|
57
|
+
target: listRef,
|
|
58
|
+
isNoMore: (d) => {
|
|
59
|
+
return d?.list?.length === 0 || (d?.list?.length ?? 0) >= (d?.count ?? 0);
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
58
62
|
}
|
|
59
63
|
);
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
return <CircularProgress />;
|
|
63
|
-
}
|
|
65
|
+
const showLoadingMore = useDelayedLoading(loadingMore);
|
|
64
66
|
|
|
65
|
-
if (data
|
|
66
|
-
return <
|
|
67
|
+
if (!data || loading) {
|
|
68
|
+
return <CircularProgress />;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
const hasMore = data && data.list
|
|
71
|
+
const hasMore = data && data.list?.length < data.count;
|
|
70
72
|
const size = { width: 48, height: 48 };
|
|
71
73
|
|
|
72
74
|
return (
|
|
73
75
|
<Stack direction="column" spacing={2} sx={{ mt: 2 }}>
|
|
74
76
|
{data.list?.length > 0 ? (
|
|
75
77
|
<>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
78
|
+
<Stack
|
|
79
|
+
ref={listRef}
|
|
80
|
+
spacing={2}
|
|
81
|
+
sx={{
|
|
82
|
+
maxHeight: {
|
|
83
|
+
xs: '100%',
|
|
84
|
+
md: '500px',
|
|
85
|
+
},
|
|
86
|
+
overflowY: 'auto',
|
|
87
|
+
}}>
|
|
88
|
+
{data.list.map((subscription) => {
|
|
89
|
+
return (
|
|
90
|
+
<Stack
|
|
91
|
+
key={subscription.id}
|
|
92
|
+
direction="row"
|
|
93
|
+
justifyContent="space-between"
|
|
94
|
+
gap={{
|
|
95
|
+
xs: 1,
|
|
96
|
+
sm: 2,
|
|
97
|
+
}}
|
|
98
|
+
sx={{
|
|
99
|
+
padding: 1.5,
|
|
100
|
+
background: 'var(--backgrounds-bg-subtle, #F9FAFB)',
|
|
101
|
+
'&:hover': {
|
|
102
|
+
backgroundColor: 'var(--backgrounds-bg-highlight, #eff6ff)',
|
|
103
|
+
transition: 'background-color 200ms linear',
|
|
104
|
+
cursor: 'pointer',
|
|
105
|
+
},
|
|
106
|
+
}}
|
|
107
|
+
flexWrap="wrap">
|
|
108
|
+
<Stack direction="column" flex={1} spacing={0.5} {...rest}>
|
|
109
|
+
<Stack
|
|
110
|
+
direction={isMobile ? 'column' : 'row'}
|
|
111
|
+
spacing={1}
|
|
112
|
+
alignItems={isMobile ? 'flex-start' : 'center'}
|
|
113
|
+
flexWrap="wrap"
|
|
114
|
+
justifyContent="space-between"
|
|
115
|
+
onClick={() => onClickSubscription(subscription)}>
|
|
116
|
+
<Stack direction="row" spacing={1.5}>
|
|
117
|
+
<AvatarGroup max={3}>
|
|
118
|
+
{subscription.items.map((item) =>
|
|
119
|
+
item.price.product.images.length > 0 ? (
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
<Avatar
|
|
122
|
+
key={item.price.product_id}
|
|
123
|
+
src={item.price.product.images[0]}
|
|
124
|
+
alt={item.price.product.name}
|
|
125
|
+
variant="rounded"
|
|
126
|
+
sx={size}
|
|
127
|
+
/>
|
|
128
|
+
) : (
|
|
129
|
+
<Avatar key={item.price.product_id} variant="rounded" sx={size}>
|
|
130
|
+
{item.price.product.name.slice(0, 1)}
|
|
131
|
+
</Avatar>
|
|
132
|
+
)
|
|
133
|
+
)}
|
|
134
|
+
</AvatarGroup>
|
|
135
|
+
<Stack
|
|
136
|
+
direction="column"
|
|
137
|
+
spacing={0.5}
|
|
138
|
+
sx={{
|
|
139
|
+
'.MuiTypography-body1': {
|
|
140
|
+
fontSize: '16px',
|
|
141
|
+
},
|
|
142
|
+
}}>
|
|
143
|
+
<SubscriptionDescription subscription={subscription} hideSubscription variant="body1" />
|
|
144
|
+
<SubscriptionStatus
|
|
145
|
+
subscription={subscription}
|
|
146
|
+
sx={{ height: 18, width: 'fit-content' }}
|
|
147
|
+
size="small"
|
|
148
|
+
/>
|
|
149
|
+
</Stack>
|
|
150
|
+
</Stack>
|
|
151
|
+
<Stack>
|
|
152
|
+
<Typography variant="subtitle1" fontWeight={500} fontSize={16}>
|
|
153
|
+
{
|
|
108
154
|
// @ts-ignore
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
alt={item.price.product.name}
|
|
113
|
-
variant="rounded"
|
|
114
|
-
sx={size}
|
|
115
|
-
/>
|
|
116
|
-
) : (
|
|
117
|
-
<Avatar key={item.price.product_id} variant="rounded" sx={size}>
|
|
118
|
-
{item.price.product.name.slice(0, 1)}
|
|
119
|
-
</Avatar>
|
|
120
|
-
)
|
|
121
|
-
)}
|
|
122
|
-
</AvatarGroup>
|
|
123
|
-
<Stack
|
|
124
|
-
direction="column"
|
|
125
|
-
spacing={0.5}
|
|
126
|
-
sx={{
|
|
127
|
-
'.MuiTypography-body1': {
|
|
128
|
-
fontSize: '16px',
|
|
129
|
-
},
|
|
130
|
-
}}>
|
|
131
|
-
<SubscriptionDescription subscription={subscription} hideSubscription variant="body1" />
|
|
132
|
-
<Status
|
|
133
|
-
size="small"
|
|
134
|
-
sx={{ height: 18, width: 'fit-content' }}
|
|
135
|
-
label={formatSubscriptionStatus(subscription.status)}
|
|
136
|
-
color={getSubscriptionStatusColor(subscription.status)}
|
|
137
|
-
/>
|
|
155
|
+
formatPrice(subscription.items[0].price, subscription.paymentCurrency)
|
|
156
|
+
}
|
|
157
|
+
</Typography>
|
|
138
158
|
</Stack>
|
|
139
159
|
</Stack>
|
|
140
|
-
<Stack
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</Stack>
|
|
149
|
-
<Stack
|
|
150
|
-
gap={1}
|
|
151
|
-
justifyContent="space-between"
|
|
152
|
-
flexWrap="wrap"
|
|
153
|
-
sx={{
|
|
154
|
-
flexDirection: {
|
|
155
|
-
xs: 'column',
|
|
156
|
-
lg: 'row',
|
|
157
|
-
},
|
|
158
|
-
alignItems: {
|
|
159
|
-
xs: 'flex-start',
|
|
160
|
-
lg: 'center',
|
|
161
|
-
},
|
|
162
|
-
}}>
|
|
163
|
-
<Box
|
|
164
|
-
component="div"
|
|
165
|
-
onClick={() => onClickSubscription(subscription)}
|
|
166
|
-
sx={{ display: 'flex', gap: 0.5, flexDirection: isMobile ? 'column' : 'row' }}>
|
|
167
|
-
{getSubscriptionTimeSummary(subscription)
|
|
168
|
-
.split(',')
|
|
169
|
-
.map((x) => (
|
|
170
|
-
<Typography key={x} variant="body1" color="text.secondary">
|
|
171
|
-
{x}
|
|
172
|
-
</Typography>
|
|
173
|
-
))}
|
|
174
|
-
</Box>
|
|
175
|
-
<SubscriptionActions
|
|
176
|
-
subscription={subscription}
|
|
177
|
-
onChange={onChange}
|
|
178
|
-
actionProps={{
|
|
179
|
-
cancel: {
|
|
180
|
-
variant: 'outlined',
|
|
181
|
-
color: 'primary',
|
|
160
|
+
<Stack
|
|
161
|
+
gap={1}
|
|
162
|
+
justifyContent="space-between"
|
|
163
|
+
flexWrap="wrap"
|
|
164
|
+
sx={{
|
|
165
|
+
flexDirection: {
|
|
166
|
+
xs: 'column',
|
|
167
|
+
lg: 'row',
|
|
182
168
|
},
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
169
|
+
alignItems: {
|
|
170
|
+
xs: 'flex-start',
|
|
171
|
+
lg: 'center',
|
|
186
172
|
},
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
173
|
+
}}>
|
|
174
|
+
<Box
|
|
175
|
+
component="div"
|
|
176
|
+
onClick={() => onClickSubscription(subscription)}
|
|
177
|
+
sx={{ display: 'flex', gap: 0.5, flexDirection: isMobile ? 'column' : 'row' }}>
|
|
178
|
+
{getSubscriptionTimeSummary(subscription)
|
|
179
|
+
.split(',')
|
|
180
|
+
.map((x) => (
|
|
181
|
+
<Typography key={x} variant="body1" color="text.secondary">
|
|
182
|
+
{x}
|
|
183
|
+
</Typography>
|
|
184
|
+
))}
|
|
185
|
+
</Box>
|
|
186
|
+
<SubscriptionActions
|
|
187
|
+
subscription={subscription}
|
|
188
|
+
onChange={(v) => {
|
|
189
|
+
reload();
|
|
190
|
+
if (onChange) {
|
|
191
|
+
onChange(v);
|
|
192
|
+
}
|
|
193
|
+
}}
|
|
194
|
+
actionProps={{
|
|
195
|
+
cancel: {
|
|
196
|
+
variant: 'outlined',
|
|
197
|
+
color: 'primary',
|
|
198
|
+
},
|
|
199
|
+
recover: {
|
|
200
|
+
variant: 'outlined',
|
|
201
|
+
color: 'info',
|
|
202
|
+
},
|
|
203
|
+
pastDue: {
|
|
204
|
+
variant: 'outlined',
|
|
205
|
+
color: 'primary',
|
|
206
|
+
},
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</Stack>
|
|
193
210
|
</Stack>
|
|
194
211
|
</Stack>
|
|
195
|
-
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
{loadingMore
|
|
202
|
-
? t('common.loadingMore', { resource: t('admin.subscriptions') })
|
|
203
|
-
: t('common.loadMore', { resource: t('admin.subscriptions') })}
|
|
204
|
-
</Button>
|
|
212
|
+
);
|
|
213
|
+
})}
|
|
214
|
+
{hasMore && !isMobile && showLoadingMore && (
|
|
215
|
+
<Box alignItems="center" gap={0.5} display="flex" mt={0.5}>
|
|
216
|
+
{t('common.loadingMore', { resource: t('admin.subscriptions') })}
|
|
217
|
+
</Box>
|
|
205
218
|
)}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
219
|
+
</Stack>
|
|
220
|
+
{isMobile && (
|
|
221
|
+
<Box>
|
|
222
|
+
{hasMore && (
|
|
223
|
+
<Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
|
|
224
|
+
{loadingMore
|
|
225
|
+
? t('common.loadingMore', { resource: t('admin.subscriptions') })
|
|
226
|
+
: t('common.loadMore', { resource: t('admin.subscriptions') })}
|
|
227
|
+
</Button>
|
|
228
|
+
)}
|
|
229
|
+
{!hasMore && data.count > pageSize && (
|
|
230
|
+
<Typography color="text.secondary">
|
|
231
|
+
{t('common.noMore', { resource: t('admin.subscriptions') })}
|
|
232
|
+
</Typography>
|
|
233
|
+
)}
|
|
234
|
+
</Box>
|
|
235
|
+
)}
|
|
212
236
|
</>
|
|
213
237
|
) : (
|
|
214
238
|
<Empty>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
function useDelayedLoading(loading: boolean, delay: number = 300) {
|
|
4
|
+
const [showLoading, setShowLoading] = useState(false);
|
|
5
|
+
const delayTimeout = useRef<NodeJS.Timeout | null>(null);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (delayTimeout.current) {
|
|
9
|
+
clearTimeout(delayTimeout.current);
|
|
10
|
+
}
|
|
11
|
+
if (loading) {
|
|
12
|
+
delayTimeout.current = setTimeout(() => {
|
|
13
|
+
setShowLoading(true);
|
|
14
|
+
}, delay);
|
|
15
|
+
} else {
|
|
16
|
+
setShowLoading(false);
|
|
17
|
+
}
|
|
18
|
+
return () => {
|
|
19
|
+
if (delayTimeout.current) {
|
|
20
|
+
clearTimeout(delayTimeout.current);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}, [loading, delay]);
|
|
24
|
+
|
|
25
|
+
return showLoading;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default useDelayedLoading;
|
package/src/locales/en.tsx
CHANGED
|
@@ -16,6 +16,9 @@ export default flat({
|
|
|
16
16
|
add: 'Add',
|
|
17
17
|
fullscreen: 'Fullscreen',
|
|
18
18
|
exit: 'Exit',
|
|
19
|
+
maxLength: 'Max {len} characters',
|
|
20
|
+
minLength: 'Min {len} characters',
|
|
21
|
+
loading: 'Loading...',
|
|
19
22
|
},
|
|
20
23
|
admin: {
|
|
21
24
|
balances: 'Balances',
|
|
@@ -459,8 +462,9 @@ export default flat({
|
|
|
459
462
|
},
|
|
460
463
|
staking: {
|
|
461
464
|
title: 'Stake',
|
|
462
|
-
none: 'No return',
|
|
465
|
+
none: 'No return or slash',
|
|
463
466
|
proration: 'Return Remaining Stake {unused}{symbol}',
|
|
467
|
+
slash: 'Slash Remaining Stake {unused}{symbol}',
|
|
464
468
|
},
|
|
465
469
|
},
|
|
466
470
|
pause: {
|
|
@@ -592,6 +596,7 @@ export default flat({
|
|
|
592
596
|
payments: 'No Payments',
|
|
593
597
|
prices: 'No Prices',
|
|
594
598
|
pricing: 'You haven’t added any prices you can add it',
|
|
599
|
+
summary: 'No Summary',
|
|
595
600
|
},
|
|
596
601
|
customer: {
|
|
597
602
|
subscription: {
|
package/src/locales/zh.tsx
CHANGED
|
@@ -16,6 +16,9 @@ export default flat({
|
|
|
16
16
|
add: '添加',
|
|
17
17
|
fullscreen: '全屏',
|
|
18
18
|
exit: '退出',
|
|
19
|
+
maxLength: '最多输入{len}个字符',
|
|
20
|
+
minLength: '最少输入{len}个字符',
|
|
21
|
+
loading: '加载中...',
|
|
19
22
|
},
|
|
20
23
|
admin: {
|
|
21
24
|
balances: '余额',
|
|
@@ -450,8 +453,9 @@ export default flat({
|
|
|
450
453
|
},
|
|
451
454
|
staking: {
|
|
452
455
|
title: '质押',
|
|
453
|
-
none: '
|
|
456
|
+
none: '不退还 / 罚没质押',
|
|
454
457
|
proration: '退还剩余部分 {unused}{symbol}',
|
|
458
|
+
slash: '罚没剩余部分 {unused}{symbol}',
|
|
455
459
|
},
|
|
456
460
|
},
|
|
457
461
|
pause: {
|
|
@@ -582,6 +586,7 @@ export default flat({
|
|
|
582
586
|
payments: '没有付款记录',
|
|
583
587
|
prices: '没有价格',
|
|
584
588
|
pricing: '您还没有设置定价,您可以添加它',
|
|
589
|
+
summary: '没有摘要',
|
|
585
590
|
},
|
|
586
591
|
customer: {
|
|
587
592
|
subscription: {
|