payment-kit 1.18.12 → 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.
Files changed (64) hide show
  1. package/api/src/index.ts +2 -0
  2. package/api/src/integrations/stripe/resource.ts +53 -11
  3. package/api/src/libs/auth.ts +14 -0
  4. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +5 -3
  5. package/api/src/libs/notification/template/subscription-canceled.ts +3 -3
  6. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +4 -3
  7. package/api/src/libs/notification/template/subscription-renew-failed.ts +5 -4
  8. package/api/src/libs/notification/template/subscription-renewed.ts +2 -1
  9. package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +3 -4
  10. package/api/src/libs/notification/template/subscription-succeeded.ts +2 -1
  11. package/api/src/libs/notification/template/subscription-upgraded.ts +6 -4
  12. package/api/src/libs/notification/template/subscription-will-canceled.ts +6 -3
  13. package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
  14. package/api/src/libs/payment.ts +77 -2
  15. package/api/src/libs/util.ts +8 -0
  16. package/api/src/queues/payment.ts +50 -1
  17. package/api/src/queues/payout.ts +297 -0
  18. package/api/src/routes/checkout-sessions.ts +2 -7
  19. package/api/src/routes/customers.ts +79 -5
  20. package/api/src/routes/payment-currencies.ts +117 -1
  21. package/api/src/routes/payment-methods.ts +19 -9
  22. package/api/src/routes/subscriptions.ts +15 -9
  23. package/api/src/store/migrations/20250305-vault-config.ts +21 -0
  24. package/api/src/store/models/invoice.ts +4 -2
  25. package/api/src/store/models/payment-currency.ts +14 -0
  26. package/api/src/store/models/payout.ts +21 -0
  27. package/api/src/store/models/types.ts +6 -0
  28. package/blocklet.yml +2 -2
  29. package/package.json +18 -18
  30. package/src/app.tsx +117 -121
  31. package/src/components/actions.tsx +32 -9
  32. package/src/components/copyable.tsx +2 -2
  33. package/src/components/customer/overdraft-protection.tsx +1 -0
  34. package/src/components/layout/admin.tsx +6 -0
  35. package/src/components/layout/user.tsx +38 -0
  36. package/src/components/metadata/editor.tsx +7 -1
  37. package/src/components/metadata/list.tsx +3 -0
  38. package/src/components/passport/assign.tsx +3 -0
  39. package/src/components/payment-link/rename.tsx +1 -0
  40. package/src/components/pricing-table/rename.tsx +1 -0
  41. package/src/components/product/add-price.tsx +1 -0
  42. package/src/components/product/edit-price.tsx +1 -0
  43. package/src/components/product/edit.tsx +1 -0
  44. package/src/components/subscription/actions/index.tsx +1 -0
  45. package/src/components/subscription/portal/actions.tsx +27 -5
  46. package/src/components/subscription/portal/list.tsx +24 -6
  47. package/src/components/subscription/status.tsx +2 -2
  48. package/src/libs/util.ts +15 -0
  49. package/src/locales/en.tsx +42 -0
  50. package/src/locales/zh.tsx +37 -0
  51. package/src/pages/admin/payments/payouts/detail.tsx +47 -38
  52. package/src/pages/admin/settings/index.tsx +3 -3
  53. package/src/pages/admin/settings/payment-methods/index.tsx +33 -1
  54. package/src/pages/admin/settings/vault-config/edit-form.tsx +253 -0
  55. package/src/pages/admin/settings/vault-config/index.tsx +352 -0
  56. package/src/pages/customer/index.tsx +247 -154
  57. package/src/pages/customer/invoice/detail.tsx +1 -1
  58. package/src/pages/customer/payout/detail.tsx +9 -2
  59. package/src/pages/customer/recharge.tsx +6 -2
  60. package/src/pages/customer/subscription/change-payment.tsx +1 -1
  61. package/src/pages/customer/subscription/change-plan.tsx +1 -1
  62. package/src/pages/customer/subscription/detail.tsx +8 -3
  63. package/src/pages/customer/subscription/embed.tsx +142 -84
  64. package/src/pages/integrations/donations/edit-form.tsx +0 -1
@@ -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
+ `;