payment-kit 1.18.13 → 1.18.14
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 +2 -0
- package/api/src/integrations/stripe/resource.ts +53 -11
- package/api/src/libs/auth.ts +14 -0
- package/api/src/libs/payment.ts +77 -2
- package/api/src/libs/util.ts +8 -0
- package/api/src/queues/payment.ts +50 -1
- package/api/src/queues/payout.ts +297 -0
- package/api/src/routes/checkout-sessions.ts +2 -7
- package/api/src/routes/payment-currencies.ts +117 -1
- package/api/src/routes/payment-methods.ts +19 -9
- package/api/src/routes/subscriptions.ts +2 -8
- package/api/src/store/migrations/20250305-vault-config.ts +21 -0
- package/api/src/store/models/payment-currency.ts +14 -0
- package/api/src/store/models/payout.ts +21 -0
- package/api/src/store/models/types.ts +6 -0
- package/blocklet.yml +1 -1
- package/package.json +18 -18
- package/src/app.tsx +116 -120
- package/src/components/customer/overdraft-protection.tsx +1 -0
- package/src/components/layout/admin.tsx +6 -0
- package/src/components/layout/user.tsx +1 -0
- package/src/components/metadata/editor.tsx +7 -1
- package/src/components/metadata/list.tsx +3 -0
- package/src/components/passport/assign.tsx +3 -0
- package/src/components/payment-link/rename.tsx +1 -0
- package/src/components/pricing-table/rename.tsx +1 -0
- package/src/components/product/add-price.tsx +1 -0
- package/src/components/product/edit-price.tsx +1 -0
- package/src/components/product/edit.tsx +1 -0
- package/src/components/subscription/actions/index.tsx +1 -0
- package/src/components/subscription/portal/actions.tsx +1 -0
- package/src/locales/en.tsx +42 -0
- package/src/locales/zh.tsx +37 -0
- package/src/pages/admin/payments/payouts/detail.tsx +47 -43
- package/src/pages/admin/settings/index.tsx +3 -3
- package/src/pages/admin/settings/payment-methods/index.tsx +33 -1
- package/src/pages/admin/settings/vault-config/edit-form.tsx +253 -0
- package/src/pages/admin/settings/vault-config/index.tsx +352 -0
- package/src/pages/integrations/donations/edit-form.tsx +0 -1
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
ListItem,
|
|
26
26
|
ListItemAvatar,
|
|
27
27
|
ListItemText,
|
|
28
|
+
Skeleton,
|
|
28
29
|
Stack,
|
|
29
30
|
TextField,
|
|
30
31
|
Tooltip,
|
|
@@ -296,6 +297,37 @@ function Balance({
|
|
|
296
297
|
);
|
|
297
298
|
}
|
|
298
299
|
|
|
300
|
+
function PaymentMethodSkeleton() {
|
|
301
|
+
return (
|
|
302
|
+
<>
|
|
303
|
+
{[1].map((group) => (
|
|
304
|
+
<Box key={group} mt={3}>
|
|
305
|
+
<Stack direction="row" alignItems="center" mb={1} flexWrap="wrap" gap={1}>
|
|
306
|
+
<Skeleton variant="text" width={120} height={32} />
|
|
307
|
+
</Stack>
|
|
308
|
+
<Box
|
|
309
|
+
sx={{
|
|
310
|
+
py: 1,
|
|
311
|
+
borderTop: '1px solid #eee',
|
|
312
|
+
borderBottom: '1px solid #eee',
|
|
313
|
+
mb: 1,
|
|
314
|
+
}}>
|
|
315
|
+
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
|
316
|
+
<Stack direction="row" spacing={2} alignItems="center" sx={{ flex: 1 }}>
|
|
317
|
+
<Skeleton variant="rectangular" width={40} height={40} />
|
|
318
|
+
<Box sx={{ flex: 1 }}>
|
|
319
|
+
<Skeleton variant="text" width="20%" height={24} />
|
|
320
|
+
<Skeleton variant="text" width="40%" height={20} />
|
|
321
|
+
</Box>
|
|
322
|
+
</Stack>
|
|
323
|
+
</Stack>
|
|
324
|
+
</Box>
|
|
325
|
+
</Box>
|
|
326
|
+
))}
|
|
327
|
+
</>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
299
331
|
export default function PaymentMethods() {
|
|
300
332
|
const { t } = useLocaleContext();
|
|
301
333
|
const [expandedId, setExpandedId] = useSessionStorageState('payment-method-expanded-id', {
|
|
@@ -362,7 +394,7 @@ export default function PaymentMethods() {
|
|
|
362
394
|
}
|
|
363
395
|
|
|
364
396
|
if (loading || !data || methods?.length === 0) {
|
|
365
|
-
return <
|
|
397
|
+
return <PaymentMethodSkeleton />;
|
|
366
398
|
}
|
|
367
399
|
|
|
368
400
|
const groups = groupByType(methods);
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { useForm, Controller } from 'react-hook-form';
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
|
+
import Dialog from '@arcblock/ux/lib/Dialog';
|
|
5
|
+
import { api, formatBNStr, Switch, formatAmountPrecisionLimit, LoadingButton } from '@blocklet/payment-react';
|
|
6
|
+
import { Box, Button, Stack, TextField, Typography, Alert, Tooltip, InputAdornment, Divider } from '@mui/material';
|
|
7
|
+
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
|
8
|
+
import { useRequest } from 'ahooks';
|
|
9
|
+
import type { TPaymentCurrencyExpanded } from '@blocklet/payment-types';
|
|
10
|
+
import Currency from '../../../../components/currency';
|
|
11
|
+
|
|
12
|
+
type EditFormProps = {
|
|
13
|
+
item: TPaymentCurrencyExpanded | null;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
onSuccess: () => void;
|
|
16
|
+
isOwner: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type VaultConfigFormData = {
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
deposit_threshold: string;
|
|
22
|
+
withdraw_threshold: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const DEFAULT_DEPOSIT_THRESHOLD = '100';
|
|
26
|
+
|
|
27
|
+
const updateVaultConfig = async (currencyId: string, data: VaultConfigFormData) => {
|
|
28
|
+
const res = await api.put(`/api/payment-currencies/${currencyId}/vault-config`, data);
|
|
29
|
+
return res.data;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function EditForm({ item, onClose, onSuccess, isOwner }: EditFormProps) {
|
|
33
|
+
const { t, locale } = useLocaleContext();
|
|
34
|
+
const {
|
|
35
|
+
control,
|
|
36
|
+
handleSubmit,
|
|
37
|
+
watch,
|
|
38
|
+
formState: { errors },
|
|
39
|
+
} = useForm<VaultConfigFormData>({
|
|
40
|
+
defaultValues: {
|
|
41
|
+
enabled: true,
|
|
42
|
+
deposit_threshold:
|
|
43
|
+
item?.vault_config?.deposit_threshold && item?.vault_config?.deposit_threshold !== '0'
|
|
44
|
+
? formatBNStr(item.vault_config.deposit_threshold, item?.decimal || 18)
|
|
45
|
+
: DEFAULT_DEPOSIT_THRESHOLD,
|
|
46
|
+
withdraw_threshold: item?.vault_config?.withdraw_threshold
|
|
47
|
+
? formatBNStr(item.vault_config.withdraw_threshold, item?.decimal || 18)
|
|
48
|
+
: '0',
|
|
49
|
+
},
|
|
50
|
+
mode: 'onChange',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const enabled = watch('enabled');
|
|
54
|
+
// const withdrawThreshold = watch('withdraw_threshold');
|
|
55
|
+
|
|
56
|
+
const getDepositHelperText = (error?: { message?: string }) => {
|
|
57
|
+
if (error) return error.message;
|
|
58
|
+
return t('admin.vaultConfig.depositThresholdHelp');
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// const getWithdrawHelperText = (error?: { message?: string }, threshold?: string) => {
|
|
62
|
+
// if (error) return error.message;
|
|
63
|
+
// return Number(threshold) === 0
|
|
64
|
+
// ? t('admin.vaultConfig.withdrawThresholdNoLimit')
|
|
65
|
+
// : t('admin.vaultConfig.withdrawThresholdHelp');
|
|
66
|
+
// };
|
|
67
|
+
|
|
68
|
+
const { runAsync: runUpdateVaultConfig, loading } = useRequest(updateVaultConfig, {
|
|
69
|
+
manual: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const onSubmit = (data: VaultConfigFormData) => {
|
|
73
|
+
if (!item) return;
|
|
74
|
+
|
|
75
|
+
const getSuccessMessage = () => {
|
|
76
|
+
const originalEnabled = item.vault_config?.enabled;
|
|
77
|
+
if (!originalEnabled && data.enabled) {
|
|
78
|
+
return t('admin.vaultConfig.enableSuccess', {
|
|
79
|
+
currency: item.symbol,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (originalEnabled && !data.enabled) {
|
|
83
|
+
return t('admin.vaultConfig.disableSuccess', {
|
|
84
|
+
currency: item.symbol,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return t('admin.vaultConfig.updateSuccess', {
|
|
88
|
+
currency: item.symbol,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
runUpdateVaultConfig(item.id, data).then(() => {
|
|
93
|
+
Toast.success(getSuccessMessage());
|
|
94
|
+
onSuccess();
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (!item) return null;
|
|
99
|
+
|
|
100
|
+
const renderLabelWithTooltip = (label: string, tooltip: string) => (
|
|
101
|
+
<Stack direction="row" spacing={1} alignItems="center">
|
|
102
|
+
<Typography variant="subtitle2">{label}</Typography>
|
|
103
|
+
<Tooltip title={tooltip}>
|
|
104
|
+
<HelpOutlineIcon fontSize="small" sx={{ color: 'text.secondary' }} />
|
|
105
|
+
</Tooltip>
|
|
106
|
+
</Stack>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Dialog
|
|
111
|
+
open
|
|
112
|
+
fullWidth
|
|
113
|
+
onClose={onClose}
|
|
114
|
+
className="base-dialog"
|
|
115
|
+
title={
|
|
116
|
+
item.vault_config?.enabled
|
|
117
|
+
? t('admin.vaultConfig.editTitle', { currency: item.symbol })
|
|
118
|
+
: t('admin.vaultConfig.enableTitle', { currency: item.symbol })
|
|
119
|
+
}
|
|
120
|
+
actions={
|
|
121
|
+
<Stack direction="row" spacing={2}>
|
|
122
|
+
<Button variant="outlined" onClick={onClose}>
|
|
123
|
+
{t('common.cancel')}
|
|
124
|
+
</Button>
|
|
125
|
+
<LoadingButton
|
|
126
|
+
variant="contained"
|
|
127
|
+
color="primary"
|
|
128
|
+
disabled={loading || !isOwner}
|
|
129
|
+
onClick={handleSubmit(onSubmit)}>
|
|
130
|
+
{t('common.save')}
|
|
131
|
+
</LoadingButton>
|
|
132
|
+
</Stack>
|
|
133
|
+
}>
|
|
134
|
+
<Box sx={{ pb: 2 }}>
|
|
135
|
+
<Alert severity="info" sx={{ mb: 3 }}>
|
|
136
|
+
{t('admin.vaultConfig.enableVaultHelp')}
|
|
137
|
+
</Alert>
|
|
138
|
+
|
|
139
|
+
<Stack spacing={3}>
|
|
140
|
+
{/* 启用设置 */}
|
|
141
|
+
<Stack direction="row" alignItems="center" gap={2}>
|
|
142
|
+
<Typography variant="subtitle1">{t('admin.vaultConfig.enableVault')}</Typography>
|
|
143
|
+
<Controller
|
|
144
|
+
name="enabled"
|
|
145
|
+
control={control}
|
|
146
|
+
render={({ field }) => (
|
|
147
|
+
<Switch checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />
|
|
148
|
+
)}
|
|
149
|
+
/>
|
|
150
|
+
</Stack>
|
|
151
|
+
|
|
152
|
+
{enabled && (
|
|
153
|
+
<>
|
|
154
|
+
<Divider sx={{ my: 1 }} />
|
|
155
|
+
|
|
156
|
+
{/* 存入阈值设置 */}
|
|
157
|
+
<Box>
|
|
158
|
+
{renderLabelWithTooltip(
|
|
159
|
+
t('admin.vaultConfig.depositThreshold'),
|
|
160
|
+
t('admin.vaultConfig.depositThresholdHelp')
|
|
161
|
+
)}
|
|
162
|
+
<Controller
|
|
163
|
+
name="deposit_threshold"
|
|
164
|
+
control={control}
|
|
165
|
+
rules={{
|
|
166
|
+
required: true,
|
|
167
|
+
validate: (value) => {
|
|
168
|
+
if (Number(value) <= 0) {
|
|
169
|
+
return t('admin.vaultConfig.depositThresholdRequired');
|
|
170
|
+
}
|
|
171
|
+
const validPrecision = formatAmountPrecisionLimit(value.toString(), locale, item.decimal || 6);
|
|
172
|
+
return validPrecision || true;
|
|
173
|
+
},
|
|
174
|
+
}}
|
|
175
|
+
render={({ field }) => (
|
|
176
|
+
<TextField
|
|
177
|
+
{...field}
|
|
178
|
+
fullWidth
|
|
179
|
+
type="number"
|
|
180
|
+
error={!!errors.deposit_threshold}
|
|
181
|
+
helperText={getDepositHelperText(errors.deposit_threshold)}
|
|
182
|
+
InputProps={{
|
|
183
|
+
endAdornment: (
|
|
184
|
+
<InputAdornment position="end">
|
|
185
|
+
<Box sx={{ display: 'flex', alignItems: 'center', ml: 1 }}>
|
|
186
|
+
<Currency logo={item.logo} name={item.symbol} />
|
|
187
|
+
</Box>
|
|
188
|
+
</InputAdornment>
|
|
189
|
+
),
|
|
190
|
+
inputProps: {
|
|
191
|
+
min: 0,
|
|
192
|
+
},
|
|
193
|
+
}}
|
|
194
|
+
sx={{ mt: 1 }}
|
|
195
|
+
placeholder={DEFAULT_DEPOSIT_THRESHOLD}
|
|
196
|
+
/>
|
|
197
|
+
)}
|
|
198
|
+
/>
|
|
199
|
+
</Box>
|
|
200
|
+
|
|
201
|
+
{/* 提取阈值设置 */}
|
|
202
|
+
{/* <Box>
|
|
203
|
+
{renderLabelWithTooltip(
|
|
204
|
+
t('admin.vaultConfig.withdrawThreshold'),
|
|
205
|
+
t('admin.vaultConfig.withdrawThresholdHelp')
|
|
206
|
+
)}
|
|
207
|
+
<Controller
|
|
208
|
+
name="withdraw_threshold"
|
|
209
|
+
control={control}
|
|
210
|
+
rules={{
|
|
211
|
+
required: true,
|
|
212
|
+
validate: (value) => {
|
|
213
|
+
if (Number(value) < 0) {
|
|
214
|
+
return t('admin.vaultConfig.withdrawThresholdInvalid');
|
|
215
|
+
}
|
|
216
|
+
const validPrecision = formatAmountPrecisionLimit(value.toString(), locale, item.decimal || 6);
|
|
217
|
+
return validPrecision || true;
|
|
218
|
+
},
|
|
219
|
+
}}
|
|
220
|
+
render={({ field }) => (
|
|
221
|
+
<TextField
|
|
222
|
+
{...field}
|
|
223
|
+
fullWidth
|
|
224
|
+
type="number"
|
|
225
|
+
error={!!errors.withdraw_threshold}
|
|
226
|
+
helperText={getWithdrawHelperText(errors.withdraw_threshold, withdrawThreshold)}
|
|
227
|
+
InputProps={{
|
|
228
|
+
endAdornment: (
|
|
229
|
+
<InputAdornment position="end">
|
|
230
|
+
<Box sx={{ display: 'flex', alignItems: 'center', ml: 1 }}>
|
|
231
|
+
<Currency logo={item.logo} name={item.symbol} />
|
|
232
|
+
</Box>
|
|
233
|
+
</InputAdornment>
|
|
234
|
+
),
|
|
235
|
+
inputProps: {
|
|
236
|
+
min: 0,
|
|
237
|
+
},
|
|
238
|
+
}}
|
|
239
|
+
sx={{ mt: 1 }}
|
|
240
|
+
placeholder="0"
|
|
241
|
+
/>
|
|
242
|
+
)}
|
|
243
|
+
/>
|
|
244
|
+
</Box> */}
|
|
245
|
+
</>
|
|
246
|
+
)}
|
|
247
|
+
</Stack>
|
|
248
|
+
</Box>
|
|
249
|
+
</Dialog>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export default EditForm;
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/* eslint-disable react/no-unstable-nested-components */
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
import { api, ConfirmDialog, formatBNStr, Link, parseMarkedText, Table, useMobile } from '@blocklet/payment-react';
|
|
4
|
+
import { Alert, Avatar, Box, Button, CircularProgress, Stack, Tooltip, Typography } from '@mui/material';
|
|
5
|
+
import { useRequest, useSetState } from 'ahooks';
|
|
6
|
+
import Empty from '@arcblock/ux/lib/Empty';
|
|
7
|
+
import { HelpOutline } from '@mui/icons-material';
|
|
8
|
+
import type { TPaymentCurrencyExpanded } from '@blocklet/payment-types';
|
|
9
|
+
import { Toast } from '@arcblock/ux';
|
|
10
|
+
import { styled } from '@mui/system';
|
|
11
|
+
import { useSessionContext } from '../../../../contexts/session';
|
|
12
|
+
|
|
13
|
+
import EditForm from './edit-form';
|
|
14
|
+
|
|
15
|
+
export const getVaultConfigs = async () => {
|
|
16
|
+
const res = await api.get('/api/payment-currencies/vault-config');
|
|
17
|
+
return res.data;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const checkDepositVault = async (currencyId: string) => {
|
|
21
|
+
const res = await api.get(`/api/payment-currencies/${currencyId}/deposit-vault`);
|
|
22
|
+
return res.data;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const depositToVault = async (currencyId: string) => {
|
|
26
|
+
const res = await api.put(`/api/payment-currencies/${currencyId}/deposit-vault`);
|
|
27
|
+
return res.data;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default function VaultConfig() {
|
|
31
|
+
const { t } = useLocaleContext();
|
|
32
|
+
const { session } = useSessionContext();
|
|
33
|
+
const { isMobile } = useMobile();
|
|
34
|
+
const isOwner = session?.user?.role === 'owner';
|
|
35
|
+
|
|
36
|
+
const [state, setState] = useSetState({
|
|
37
|
+
item: null as TPaymentCurrencyExpanded | null,
|
|
38
|
+
edit: false,
|
|
39
|
+
confirm: false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
data = {
|
|
44
|
+
list: [],
|
|
45
|
+
balances: {},
|
|
46
|
+
},
|
|
47
|
+
loading,
|
|
48
|
+
error,
|
|
49
|
+
refresh,
|
|
50
|
+
} = useRequest(getVaultConfigs, {
|
|
51
|
+
onSuccess: (res) => {
|
|
52
|
+
if (res.error) {
|
|
53
|
+
Toast.error(res.error);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const { runAsync: runCheckDepositVault } = useRequest(checkDepositVault, {
|
|
59
|
+
manual: true,
|
|
60
|
+
onSuccess: (result) => {
|
|
61
|
+
if (result.depositAmount && result.depositAmount !== '0') {
|
|
62
|
+
setState({
|
|
63
|
+
confirm: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const handleEditClick = (item: TPaymentCurrencyExpanded) => {
|
|
69
|
+
setState({
|
|
70
|
+
item,
|
|
71
|
+
edit: true,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleFormClose = () => {
|
|
76
|
+
setState({
|
|
77
|
+
edit: false,
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// 确认转入冷钱包
|
|
82
|
+
const handleDepositConfirm = async () => {
|
|
83
|
+
if (!state.item) return;
|
|
84
|
+
try {
|
|
85
|
+
setState({ confirm: false });
|
|
86
|
+
await depositToVault(state.item.id);
|
|
87
|
+
Toast.success(t('admin.vaultConfig.depositQueued'));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
Toast.error(t('admin.vaultConfig.depositFailed'));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleFormSuccess = () => {
|
|
94
|
+
if (state.item) {
|
|
95
|
+
runCheckDepositVault(state.item.id);
|
|
96
|
+
}
|
|
97
|
+
refresh();
|
|
98
|
+
handleFormClose();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const columns = [
|
|
102
|
+
{
|
|
103
|
+
label: t('common.currency'),
|
|
104
|
+
name: 'symbol',
|
|
105
|
+
options: {
|
|
106
|
+
customBodyRenderLite: (dataIndex: number) => {
|
|
107
|
+
const item = data.list[dataIndex];
|
|
108
|
+
return (
|
|
109
|
+
<Stack direction="row" spacing={1} alignItems="center">
|
|
110
|
+
<Avatar src={item.logo} alt={item.symbol} style={{ width: 24, height: 24 }} />
|
|
111
|
+
<Box>
|
|
112
|
+
<Typography variant="body2">{item.symbol}</Typography>
|
|
113
|
+
{!isMobile && (
|
|
114
|
+
<Typography variant="caption" color="text.secondary">
|
|
115
|
+
{item.payment_method?.name}
|
|
116
|
+
</Typography>
|
|
117
|
+
)}
|
|
118
|
+
</Box>
|
|
119
|
+
</Stack>
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
label: t('admin.vaultConfig.appBalance'),
|
|
126
|
+
name: 'id',
|
|
127
|
+
options: {
|
|
128
|
+
customBodyRenderLite: (dataIndex: number) => {
|
|
129
|
+
const item = data.list[dataIndex];
|
|
130
|
+
return (
|
|
131
|
+
<Typography variant="body1">
|
|
132
|
+
{formatBNStr(data.balances?.[item.id] || '0', item.decimal)} {item.symbol}
|
|
133
|
+
</Typography>
|
|
134
|
+
);
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
label: t('admin.vaultConfig.enabled'),
|
|
140
|
+
name: 'vault_enabled',
|
|
141
|
+
options: {
|
|
142
|
+
customBodyRenderLite: (dataIndex: number) => {
|
|
143
|
+
const item = data.list[dataIndex];
|
|
144
|
+
const enabled = item.vault_config?.enabled;
|
|
145
|
+
return (
|
|
146
|
+
<Stack direction="row" spacing={1} alignItems="center">
|
|
147
|
+
<Box
|
|
148
|
+
sx={{
|
|
149
|
+
width: 8,
|
|
150
|
+
height: 8,
|
|
151
|
+
borderRadius: '50%',
|
|
152
|
+
bgcolor: enabled ? 'success.main' : 'text.lighter',
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
155
|
+
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
156
|
+
{enabled ? t('admin.vaultConfig.enabledYes') : t('admin.vaultConfig.enabledNo')}
|
|
157
|
+
</Typography>
|
|
158
|
+
</Stack>
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: t('admin.vaultConfig.depositThreshold'),
|
|
165
|
+
name: 'deposit_threshold',
|
|
166
|
+
options: {
|
|
167
|
+
customBodyRenderLite: (dataIndex: number) => {
|
|
168
|
+
const item = data.list[dataIndex];
|
|
169
|
+
if (!item?.vault_config) {
|
|
170
|
+
return (
|
|
171
|
+
<Typography variant="body2" color="text.secondary">
|
|
172
|
+
{t('admin.vaultConfig.notConfig')}
|
|
173
|
+
</Typography>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return (
|
|
177
|
+
<Typography variant="body1">
|
|
178
|
+
{formatBNStr(item.vault_config?.deposit_threshold || '0', item.decimal)} {item.symbol}
|
|
179
|
+
</Typography>
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
customHeadLabelRender: () => {
|
|
183
|
+
return (
|
|
184
|
+
<Box display="flex" alignItems="center" gap={1}>
|
|
185
|
+
{t('admin.vaultConfig.depositThreshold')}
|
|
186
|
+
<Tooltip title={t('admin.vaultConfig.depositThresholdHelp')}>
|
|
187
|
+
<HelpOutline fontSize="small" sx={{ color: 'text.lighter' }} />
|
|
188
|
+
</Tooltip>
|
|
189
|
+
</Box>
|
|
190
|
+
);
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
// {
|
|
195
|
+
// label: t('admin.vaultConfig.withdrawThreshold'),
|
|
196
|
+
// name: 'withdraw_threshold',
|
|
197
|
+
// options: {
|
|
198
|
+
// customBodyRenderLite: (dataIndex: number) => {
|
|
199
|
+
// const item = data.list[dataIndex];
|
|
200
|
+
// if (!item.vault_config) {
|
|
201
|
+
// return (
|
|
202
|
+
// <Typography variant="body2" color="text.secondary">
|
|
203
|
+
// {t('admin.vaultConfig.notConfig')}
|
|
204
|
+
// </Typography>
|
|
205
|
+
// );
|
|
206
|
+
// }
|
|
207
|
+
// return (
|
|
208
|
+
// <Typography variant="body1">
|
|
209
|
+
// {item.vault_config?.withdraw_threshold === '0'
|
|
210
|
+
// ? t('admin.vaultConfig.noLimit')
|
|
211
|
+
// : `${formatBNStr(item.vault_config?.withdraw_threshold || '0', item.decimal)} ${item.symbol}`}
|
|
212
|
+
// </Typography>
|
|
213
|
+
// );
|
|
214
|
+
// },
|
|
215
|
+
// customHeadLabelRender: () => {
|
|
216
|
+
// return (
|
|
217
|
+
// <Box display="flex" alignItems="center" gap={1}>
|
|
218
|
+
// {t('admin.vaultConfig.withdrawThreshold')}
|
|
219
|
+
// <Tooltip title={t('admin.vaultConfig.withdrawThresholdHelp')}>
|
|
220
|
+
// <HelpOutline fontSize="small" sx={{ color: 'text.lighter' }} />
|
|
221
|
+
// </Tooltip>
|
|
222
|
+
// </Box>
|
|
223
|
+
// );
|
|
224
|
+
// },
|
|
225
|
+
// },
|
|
226
|
+
// },
|
|
227
|
+
...(isOwner
|
|
228
|
+
? [
|
|
229
|
+
{
|
|
230
|
+
label: t('common.actions'),
|
|
231
|
+
name: 'actions',
|
|
232
|
+
options: {
|
|
233
|
+
customBodyRenderLite: (dataIndex: number) => {
|
|
234
|
+
const item = data.list[dataIndex];
|
|
235
|
+
const enabled = item.vault_config?.enabled;
|
|
236
|
+
return (
|
|
237
|
+
<Button
|
|
238
|
+
size="small"
|
|
239
|
+
variant="text"
|
|
240
|
+
onClick={() => handleEditClick(item)}
|
|
241
|
+
sx={{
|
|
242
|
+
color: 'text.link',
|
|
243
|
+
minWidth: 'fit-content',
|
|
244
|
+
}}>
|
|
245
|
+
{enabled ? t('common.edit') : t('admin.vaultConfig.enable')}
|
|
246
|
+
</Button>
|
|
247
|
+
);
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
]
|
|
252
|
+
: []),
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
if (loading) {
|
|
256
|
+
return <CircularProgress />;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (error) {
|
|
260
|
+
return <Alert severity="error">{error.message}</Alert>;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const renderEmpty = () => {
|
|
264
|
+
const configFirst = t('admin.vaultConfig.configureFirst');
|
|
265
|
+
const parsedParts = parseMarkedText(configFirst);
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<Empty>
|
|
269
|
+
<Typography variant="body1">{t('admin.vaultConfig.notConfigured')}</Typography>
|
|
270
|
+
<Typography variant="body2" mt={1} sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
271
|
+
{parsedParts.map((part: { type: 'text' | 'marked'; content: string }) =>
|
|
272
|
+
part.type === 'text' ? (
|
|
273
|
+
part.content
|
|
274
|
+
) : (
|
|
275
|
+
<Link
|
|
276
|
+
key={part.content}
|
|
277
|
+
to={`${window.location.origin}/.well-known/service/admin/configuration`}
|
|
278
|
+
style={{ color: '#3b82f6' }}>
|
|
279
|
+
{part.content}
|
|
280
|
+
</Link>
|
|
281
|
+
)
|
|
282
|
+
)}
|
|
283
|
+
</Typography>
|
|
284
|
+
</Empty>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<Root>
|
|
290
|
+
<Box mb={2}>
|
|
291
|
+
<Typography variant="body2" color="text.secondary" mt={1}>
|
|
292
|
+
{t('admin.vaultConfig.description')}
|
|
293
|
+
<Box
|
|
294
|
+
component="a"
|
|
295
|
+
href="https://www.arcblock.io/docs/arcblock-payment-kit/en/vault-config"
|
|
296
|
+
target="_blank"
|
|
297
|
+
rel="noopener noreferrer"
|
|
298
|
+
sx={{ color: 'text.link', textDecoration: 'none', ml: 0.5 }}>
|
|
299
|
+
{t('admin.vaultConfig.learnMore')}
|
|
300
|
+
</Box>
|
|
301
|
+
</Typography>
|
|
302
|
+
|
|
303
|
+
{!isOwner && (
|
|
304
|
+
<Alert severity="warning" sx={{ mt: 2 }}>
|
|
305
|
+
{t('admin.vaultConfig.ownerOnly')}
|
|
306
|
+
</Alert>
|
|
307
|
+
)}
|
|
308
|
+
</Box>
|
|
309
|
+
<Table
|
|
310
|
+
data={data.list}
|
|
311
|
+
columns={columns}
|
|
312
|
+
options={{
|
|
313
|
+
pagination: false,
|
|
314
|
+
selectableRows: 'none',
|
|
315
|
+
}}
|
|
316
|
+
mobileTDFlexDirection="row"
|
|
317
|
+
toolbar={false}
|
|
318
|
+
footer={false}
|
|
319
|
+
emptyNode={renderEmpty()}
|
|
320
|
+
/>
|
|
321
|
+
{state.edit && state.item && (
|
|
322
|
+
<EditForm item={state.item} onClose={handleFormClose} onSuccess={handleFormSuccess} isOwner={isOwner} />
|
|
323
|
+
)}
|
|
324
|
+
{state.confirm && state.item && (
|
|
325
|
+
<ConfirmDialog
|
|
326
|
+
title={t('admin.vaultConfig.depositConfirmTitle')}
|
|
327
|
+
message={t('admin.vaultConfig.depositConfirmMessage', {
|
|
328
|
+
currency: state.item.symbol,
|
|
329
|
+
})}
|
|
330
|
+
onCancel={() => setState({ confirm: false })}
|
|
331
|
+
onConfirm={handleDepositConfirm}
|
|
332
|
+
color="primary"
|
|
333
|
+
/>
|
|
334
|
+
)}
|
|
335
|
+
</Root>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const Root = styled(Box)`
|
|
340
|
+
@media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
|
|
341
|
+
.MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > td.MuiTableCell-root {
|
|
342
|
+
> div {
|
|
343
|
+
width: fit-content;
|
|
344
|
+
flex: inherit;
|
|
345
|
+
font-size: 14px;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
.invoice-summary {
|
|
349
|
+
padding-right: 20px;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
`;
|