payment-kit 1.14.21 → 1.14.23

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 (79) hide show
  1. package/blocklet.yml +1 -1
  2. package/package.json +19 -19
  3. package/src/app.tsx +13 -12
  4. package/src/components/balance-list.tsx +12 -2
  5. package/src/components/copyable.tsx +3 -2
  6. package/src/components/customer/edit.tsx +25 -21
  7. package/src/components/customer/form.tsx +18 -28
  8. package/src/components/customer/link.tsx +1 -2
  9. package/src/components/date-range-picker.tsx +21 -0
  10. package/src/components/drawer-form.tsx +27 -4
  11. package/src/components/event/list.tsx +3 -2
  12. package/src/components/filter-toolbar.tsx +11 -4
  13. package/src/components/info-card.tsx +4 -2
  14. package/src/components/info-metric.tsx +2 -2
  15. package/src/components/info-row.tsx +33 -26
  16. package/src/components/invoice/list.tsx +2 -2
  17. package/src/components/invoice/table.tsx +148 -85
  18. package/src/components/invoice-pdf/pdf.tsx +5 -1
  19. package/src/components/layout/admin.tsx +8 -2
  20. package/src/components/metadata/editor.tsx +25 -18
  21. package/src/components/metadata/form.tsx +83 -25
  22. package/src/components/metadata/list.tsx +22 -6
  23. package/src/components/payment-intent/list.tsx +3 -3
  24. package/src/components/payment-link/preview.tsx +42 -24
  25. package/src/components/payouts/list.tsx +2 -3
  26. package/src/components/price/form.tsx +28 -15
  27. package/src/components/price/upsell.tsx +1 -4
  28. package/src/components/pricing-table/preview.tsx +42 -23
  29. package/src/components/product/cross-sell-select.tsx +1 -1
  30. package/src/components/product/cross-sell.tsx +3 -4
  31. package/src/components/product/edit-price.tsx +0 -1
  32. package/src/components/refund/list.tsx +9 -4
  33. package/src/components/section/header.tsx +11 -4
  34. package/src/components/subscription/description.tsx +10 -6
  35. package/src/components/subscription/items/index.tsx +28 -6
  36. package/src/components/subscription/list.tsx +2 -2
  37. package/src/components/subscription/metrics.tsx +10 -8
  38. package/src/components/subscription/portal/actions.tsx +37 -11
  39. package/src/components/subscription/portal/list.tsx +131 -70
  40. package/src/components/subscription/status.tsx +9 -3
  41. package/src/global.css +1 -1
  42. package/src/hooks/mobile.ts +3 -3
  43. package/src/libs/util.ts +6 -2
  44. package/src/locales/en.tsx +37 -1
  45. package/src/locales/zh.tsx +37 -1
  46. package/src/pages/admin/billing/index.tsx +24 -4
  47. package/src/pages/admin/billing/invoices/detail.tsx +302 -147
  48. package/src/pages/admin/billing/subscriptions/detail.tsx +259 -134
  49. package/src/pages/admin/customers/customers/detail.tsx +358 -175
  50. package/src/pages/admin/customers/customers/index.tsx +8 -5
  51. package/src/pages/admin/customers/index.tsx +22 -5
  52. package/src/pages/admin/developers/webhooks/index.tsx +2 -2
  53. package/src/pages/admin/index.tsx +24 -10
  54. package/src/pages/admin/overview.tsx +1 -1
  55. package/src/pages/admin/payments/index.tsx +22 -7
  56. package/src/pages/admin/payments/intents/detail.tsx +263 -121
  57. package/src/pages/admin/payments/payouts/detail.tsx +235 -102
  58. package/src/pages/admin/payments/refunds/detail.tsx +286 -133
  59. package/src/pages/admin/products/index.tsx +28 -12
  60. package/src/pages/admin/products/links/create.tsx +16 -6
  61. package/src/pages/admin/products/links/detail.tsx +280 -176
  62. package/src/pages/admin/products/links/index.tsx +4 -7
  63. package/src/pages/admin/products/passports/index.tsx +6 -3
  64. package/src/pages/admin/products/prices/detail.tsx +260 -139
  65. package/src/pages/admin/products/prices/list.tsx +7 -3
  66. package/src/pages/admin/products/pricing-tables/create.tsx +17 -5
  67. package/src/pages/admin/products/pricing-tables/detail.tsx +221 -121
  68. package/src/pages/admin/products/pricing-tables/index.tsx +1 -2
  69. package/src/pages/admin/products/products/detail.tsx +262 -119
  70. package/src/pages/admin/products/products/index.tsx +1 -2
  71. package/src/pages/admin/settings/index.tsx +27 -7
  72. package/src/pages/customer/index.tsx +431 -143
  73. package/src/pages/customer/invoice/detail.tsx +138 -26
  74. package/src/pages/customer/refund/list.tsx +193 -4
  75. package/src/pages/customer/subscription/change-payment.tsx +20 -20
  76. package/src/pages/customer/subscription/detail.tsx +168 -34
  77. package/src/pages/customer/subscription/embed.tsx +22 -19
  78. package/src/pages/home.tsx +7 -1
  79. package/src/components/table.tsx +0 -93
@@ -4,17 +4,15 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
4
  import Toast from '@arcblock/ux/lib/Toast';
5
5
  import { api, formatBNStr, formatError, formatTime, usePaymentContext } from '@blocklet/payment-react';
6
6
  import type { GroupedBN, TCustomerExpanded, TPaymentMethodExpanded } from '@blocklet/payment-types';
7
- import { ArrowBackOutlined, Edit } from '@mui/icons-material';
8
- import { Alert, Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
7
+ import { ArrowBackOutlined } from '@mui/icons-material';
8
+ import { Alert, Avatar, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
9
9
  import { styled } from '@mui/system';
10
10
  import { useRequest, useSetState } from 'ahooks';
11
- import { isEmpty } from 'lodash';
12
- import { FlagEmoji } from 'react-international-phone';
13
- import { useNavigate } from 'react-router-dom';
11
+ import { defaultCountries, FlagEmoji, parseCountry } from 'react-international-phone';
14
12
 
13
+ import { useMemo } from 'react';
15
14
  import BalanceList from '../../../../components/balance-list';
16
15
  import Copyable from '../../../../components/copyable';
17
- import CustomerActions from '../../../../components/customer/actions';
18
16
  import EditCustomer from '../../../../components/customer/edit';
19
17
  import EventList from '../../../../components/event/list';
20
18
  import InfoMetric from '../../../../components/info-metric';
@@ -27,6 +25,7 @@ import RefundList from '../../../../components/refund/list';
27
25
  import SectionHeader from '../../../../components/section/header';
28
26
  import SubscriptionList from '../../../../components/subscription/list';
29
27
  import { goBackOrFallback } from '../../../../libs/util';
28
+ import MetadataList from '../../../../components/metadata/list';
30
29
 
31
30
  const fetchData = async (
32
31
  id: string
@@ -42,6 +41,9 @@ const fetchData = async (
42
41
  };
43
42
  };
44
43
 
44
+ const InfoDirection = 'column';
45
+ const InfoAlignItems = 'flex-start';
46
+
45
47
  function getTokenBalances(customer: TCustomerExpanded, paymentMethods: TPaymentMethodExpanded[]) {
46
48
  const tokens: { currency: string; balance: string }[] = [];
47
49
  // @ts-ignore
@@ -74,7 +76,6 @@ function getTokenBalances(customer: TCustomerExpanded, paymentMethods: TPaymentM
74
76
 
75
77
  export default function CustomerDetail(props: { id: string }) {
76
78
  const { t } = useLocaleContext();
77
- const navigate = useNavigate();
78
79
  const { settings } = usePaymentContext();
79
80
  const [state, setState] = useSetState({
80
81
  adding: {
@@ -91,7 +92,10 @@ export default function CustomerDetail(props: { id: string }) {
91
92
  });
92
93
 
93
94
  const { loading, error, data, runAsync } = useRequest(() => fetchData(props.id));
94
-
95
+ const countryDetail = useMemo(() => {
96
+ const item = defaultCountries.find((v) => v[1] === data?.customer?.address?.country);
97
+ return item ? parseCountry(item) : { name: '' };
98
+ }, [data]);
95
99
  if (error) {
96
100
  return <Alert severity="error">{error.message}</Alert>;
97
101
  }
@@ -128,15 +132,18 @@ export default function CustomerDetail(props: { id: string }) {
128
132
  }
129
133
  };
130
134
 
131
- const onChange = (action: string) => {
132
- if (action === 'remove') {
133
- navigate('/admin/customers');
134
- } else {
135
- runAsync();
136
- }
137
- };
135
+ // const onChange = (action: string) => {
136
+ // if (action === 'remove') {
137
+ // navigate('/admin/customers');
138
+ // } else {
139
+ // runAsync();
140
+ // }
141
+ // };
138
142
 
139
143
  const tokenBalances = getTokenBalances(data.customer, settings.paymentMethods);
144
+ const handleEditMetadata = () => {
145
+ setState((prev) => ({ editing: { ...prev.editing, metadata: true } }));
146
+ };
140
147
 
141
148
  return (
142
149
  <Root direction="column" spacing={4} sx={{ mb: 4 }}>
@@ -152,181 +159,357 @@ export default function CustomerDetail(props: { id: string }) {
152
159
  {t('admin.customers')}
153
160
  </Typography>
154
161
  </Stack>
155
- <Copyable text={props.id} style={{ marginLeft: 4 }} />
162
+ <Button
163
+ variant="outlined"
164
+ color="primary"
165
+ size="small"
166
+ disabled={state.editing.customer}
167
+ onClick={() => setState((prev) => ({ editing: { ...prev.editing, customer: true } }))}>
168
+ {t('common.edit')}
169
+ </Button>
170
+ {/* <CustomerActions data={data.customer} onChange={onChange} variant="normal" /> */}
156
171
  </Stack>
157
- <Box mt={2}>
172
+ <Box
173
+ mt={4}
174
+ mb={3}
175
+ sx={{
176
+ display: 'flex',
177
+ gap: {
178
+ xs: 2,
179
+ sm: 2,
180
+ md: 5,
181
+ },
182
+ flexWrap: 'wrap',
183
+ flexDirection: {
184
+ xs: 'column',
185
+ sm: 'column',
186
+ md: 'row',
187
+ },
188
+ alignItems: {
189
+ xs: 'flex-start',
190
+ sm: 'flex-start',
191
+ md: 'center',
192
+ },
193
+ }}>
158
194
  <Stack direction="row" justifyContent="space-between" alignItems="center">
159
- <Typography variant="h5" sx={{ fontWeight: 600 }}>
160
- {data.customer.name}
161
- </Typography>
162
- <CustomerActions data={data.customer} onChange={onChange} variant="normal" />
195
+ <Stack direction="row" alignItems="center" spacing={1}>
196
+ <Avatar
197
+ title={data.customer.name}
198
+ src={`/.well-known/service/user/avatar/${data?.customer?.did}`}
199
+ variant="square"
200
+ sx={{ width: 52, height: 52, borderRadius: 'var(--radius-s, 4px)' }}
201
+ />
202
+ <Typography variant="h2" sx={{ fontWeight: 600 }}>
203
+ {data.customer.name}
204
+ </Typography>
205
+ </Stack>
163
206
  </Stack>
164
207
  <Stack
165
208
  className="section-body"
166
- direction="row"
167
- spacing={3}
168
209
  justifyContent="flex-start"
169
210
  flexWrap="wrap"
170
- sx={{ pt: 2, mt: 2, borderTop: '1px solid #eee' }}>
171
- <InfoMetric label={t('common.createdAt')} value={formatTime(data.customer.created_at)} divider />
172
- <InfoMetric label={t('common.updatedAt')} value={formatTime(data.customer.updated_at)} divider />
173
- <InfoMetric label={t('admin.customer.spent')} value={<BalanceList data={data.summary?.paid} />} divider />
174
- <InfoMetric label={t('admin.customer.stake')} value={<BalanceList data={data.summary?.stake} />} divider />
175
- <InfoMetric label={t('admin.customer.token')} value={<BalanceList data={data.summary?.token} />} divider />
176
- <InfoMetric label={t('admin.customer.due')} value={<BalanceList data={data.summary?.due} />} divider />
177
- <InfoMetric
178
- label={t('admin.customer.refund')}
179
- value={<BalanceList data={data.summary?.refunded} />}
180
- divider
181
- />
182
- {tokenBalances.map((x) => (
183
- <InfoMetric
184
- key={x.currency}
185
- label={t('admin.customer.balance', { currency: x.currency })}
186
- value={x.balance}
187
- divider
188
- />
189
- ))}
211
+ sx={{
212
+ 'hr.MuiDivider-root:last-child': {
213
+ display: 'none',
214
+ },
215
+ flexDirection: {
216
+ xs: 'column',
217
+ sm: 'column',
218
+ md: 'row',
219
+ },
220
+ alignItems: {
221
+ xs: 'flex-start',
222
+ sm: 'flex-start',
223
+ md: 'center',
224
+ },
225
+ gap: {
226
+ xs: 1,
227
+ sm: 1,
228
+ md: 3,
229
+ },
230
+ }}>
231
+ <InfoMetric label={t('admin.customer.email')} value={data.customer.email} divider />
232
+ <InfoMetric label={t('admin.customer.phone')} value={data.customer.phone} divider />
233
+ <InfoMetric label={t('common.id')} value={<Copyable text={props.id} style={{ marginLeft: 4 }} />} divider />
190
234
  </Stack>
191
235
  </Box>
236
+ <Divider />
192
237
  </Box>
238
+ <Stack
239
+ sx={{
240
+ flexDirection: {
241
+ xs: 'column',
242
+ lg: 'row',
243
+ },
244
+ gap: 4,
245
+ '.payment-link-column-1': {
246
+ minWidth: {
247
+ xs: '100%',
248
+ lg: '600px',
249
+ },
250
+ },
251
+ '.payment-link-column-2': {
252
+ width: {
253
+ xs: '100%',
254
+ md: '100%',
255
+ lg: '580px',
256
+ },
257
+ maxWidth: {
258
+ xs: '100%',
259
+ md: '33%',
260
+ },
261
+ },
262
+ }}>
263
+ <Box flex={1} className="payment-link-column-1" sx={{ gap: 4, display: 'flex', flexDirection: 'column' }}>
264
+ <Box className="section">
265
+ <SectionHeader title={t('admin.details')} />
266
+ <Stack>
267
+ <InfoRow
268
+ label={t('common.did')}
269
+ value={<DidAddress did={data.customer.did} showQrcode />}
270
+ direction={InfoDirection}
271
+ alignItems={InfoAlignItems}
272
+ />
273
+ <Box
274
+ sx={{
275
+ display: 'grid',
276
+ gridTemplateColumns: {
277
+ xs: 'repeat(1, 1fr)',
278
+ sm: 'repeat(1, 1fr)',
279
+ md: 'repeat(2, 1fr)',
280
+ lg: 'repeat(3, 1fr)',
281
+ },
282
+ }}>
283
+ <InfoRow
284
+ label={t('admin.customer.name')}
285
+ value={data.customer.name}
286
+ direction={InfoDirection}
287
+ alignItems={InfoAlignItems}
288
+ />
289
+ <InfoRow
290
+ label={t('admin.customer.phone')}
291
+ value={data.customer.phone}
292
+ direction={InfoDirection}
293
+ alignItems={InfoAlignItems}
294
+ />
295
+ <InfoRow
296
+ label={t('admin.customer.email')}
297
+ value={data.customer.email}
298
+ direction={InfoDirection}
299
+ alignItems={InfoAlignItems}
300
+ />
301
+ <InfoRow
302
+ label={t('admin.customer.invoicePrefix')}
303
+ value={data.customer.invoice_prefix}
304
+ direction={InfoDirection}
305
+ alignItems={InfoAlignItems}
306
+ />
307
+ <InfoRow
308
+ label={t('common.createdAt')}
309
+ value={formatTime(data.customer.created_at)}
310
+ direction={InfoDirection}
311
+ alignItems={InfoAlignItems}
312
+ />
313
+ <InfoRow
314
+ label={t('common.updatedAt')}
315
+ value={formatTime(data.customer.updated_at)}
316
+ direction={InfoDirection}
317
+ alignItems={InfoAlignItems}
318
+ />
193
319
 
194
- <Box className="section">
195
- <SectionHeader title={t('admin.details')}>
196
- <Button
197
- variant="outlined"
198
- color="inherit"
199
- size="small"
200
- disabled={state.editing.customer}
201
- onClick={() => setState((prev) => ({ editing: { ...prev.editing, customer: true } }))}>
202
- <Edit fontSize="small" sx={{ mr: 0.5 }} />
203
- {t('common.edit')}
204
- </Button>
205
- </SectionHeader>
206
- <Stack>
207
- <InfoRow label={t('common.did')} value={<DidAddress did={data.customer.did} showQrcode />} />
208
- <InfoRow label={t('admin.customer.name')} value={data.customer.name} />
209
- <InfoRow label={t('admin.customer.phone')} value={data.customer.phone} />
210
- <InfoRow label={t('admin.customer.email')} value={data.customer.email} />
211
- <InfoRow label={t('admin.customer.invoicePrefix')} value={data.customer.invoice_prefix} />
212
- <InfoRow label={t('common.createdAt')} value={formatTime(data.customer.created_at)} />
213
- <InfoRow label={t('common.updatedAt')} value={formatTime(data.customer.updated_at)} />
214
- <InfoRow
215
- alignItems="flex-start"
216
- label={t('admin.customer.address.label')}
217
- value={
218
- <Stack direction="column">
320
+ {state.editing.customer && (
321
+ <EditCustomer
322
+ data={data.customer}
323
+ loading={state.loading.customer}
324
+ onSave={onUpdateInfo}
325
+ onCancel={() => setState((prev) => ({ editing: { ...prev.editing, customer: false } }))}
326
+ />
327
+ )}
328
+ </Box>
329
+ </Stack>
330
+ </Box>
331
+ <Divider />
332
+ <Box className="section">
333
+ <SectionHeader title={t('admin.subscriptions')} mb={0} />
334
+ <Box className="section-body">
335
+ <SubscriptionList
336
+ features={{ customer: false, toolbar: false }}
337
+ customer_id={data.customer.id}
338
+ status={['active', 'trialing', 'paused', 'past_due', 'canceled'].join(',')}
339
+ />
340
+ </Box>
341
+ </Box>
342
+ <Divider />
343
+ <Box className="section">
344
+ <SectionHeader title={t('admin.invoices')} mb={0} />
345
+ <Box className="section-body">
346
+ <InvoiceList
347
+ features={{ customer: false, toolbar: false }}
348
+ customer_id={data.customer.id}
349
+ status={['open', 'paid', 'uncollectible', 'draft', 'void'].join(',')}
350
+ />
351
+ </Box>
352
+ </Box>
353
+ <Divider />
354
+ <Box className="section">
355
+ <SectionHeader title={t('admin.payments')} mb={0} />
356
+ <Box className="section-body">
357
+ <PaymentList features={{ customer: false, toolbar: false }} customer_id={data.customer.id} />
358
+ </Box>
359
+ </Box>
360
+ <Divider />
361
+ <Box className="section">
362
+ <SectionHeader title={t('admin.payouts')} mb={0} />
363
+ <Box className="section-body">
364
+ <PayoutList
365
+ features={{ customer: false, toolbar: false }}
366
+ customer_id={data.customer.id}
367
+ status={['paid', 'pending', 'failed', 'canceled'].join(',')}
368
+ />
369
+ </Box>
370
+ </Box>
371
+ <Divider />
372
+ <Box className="section">
373
+ <SectionHeader title={t('admin.refunds')} mb={0} />
374
+ <Box className="section-body">
375
+ <RefundList
376
+ features={{ customer: false, toolbar: false }}
377
+ customer_id={data.customer.id}
378
+ status={['succeeded', 'canceled', 'failed', 'requires_action', 'pending'].join(',')}
379
+ />
380
+ </Box>
381
+ </Box>
382
+ <Divider />
383
+ <Box className="section">
384
+ <SectionHeader title={t('admin.events')} />
385
+ <Box className="section-body">
386
+ <EventList features={{ toolbar: false }} object_id={data.customer.id} />
387
+ </Box>
388
+ </Box>
389
+ </Box>
390
+ <Box className="payment-link-column-2" sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
391
+ <Box className="section">
392
+ <SectionHeader title={t('admin.metrics')} />
393
+ <Box className="section-body">
394
+ <InfoRow
395
+ label={t('admin.customer.spent')}
396
+ value={<BalanceList data={data.summary?.paid} showLogo />}
397
+ direction={InfoDirection}
398
+ alignItems={InfoAlignItems}
399
+ />
400
+ <InfoRow
401
+ label={t('admin.customer.stake')}
402
+ value={<BalanceList data={data.summary?.stake} showLogo />}
403
+ direction={InfoDirection}
404
+ alignItems={InfoAlignItems}
405
+ />
406
+ <InfoRow
407
+ label={t('admin.customer.token')}
408
+ value={<BalanceList data={data.summary?.token} showLogo />}
409
+ direction={InfoDirection}
410
+ alignItems={InfoAlignItems}
411
+ />
412
+ <InfoRow
413
+ label={t('admin.customer.due')}
414
+ value={<BalanceList data={data.summary?.due} showLogo />}
415
+ direction={InfoDirection}
416
+ alignItems={InfoAlignItems}
417
+ />
418
+ <InfoRow
419
+ label={t('admin.customer.refund')}
420
+ value={<BalanceList data={data.summary?.refunded} showLogo />}
421
+ direction={InfoDirection}
422
+ alignItems={InfoAlignItems}
423
+ />
424
+ {tokenBalances.map((x) => (
219
425
  <InfoRow
220
- label={t('admin.customer.address.country')}
221
- value={
222
- data.customer.address?.country ? (
426
+ key={x.currency}
427
+ label={t('admin.customer.balance', { currency: x.currency })}
428
+ value={x.balance}
429
+ direction={InfoDirection}
430
+ alignItems={InfoAlignItems}
431
+ />
432
+ ))}
433
+ </Box>
434
+ </Box>
435
+ <Divider />
436
+ <Box className="section">
437
+ <SectionHeader title={t('admin.customer.address.label')} />
438
+ <Box className="section-body">
439
+ <InfoRow
440
+ label={t('admin.customer.address.country')}
441
+ value={
442
+ data.customer.address?.country ? (
443
+ <Box display="flex" alignItems="center" flexWrap="nowrap" gap={0.5} sx={{ cursor: 'pointer' }}>
223
444
  <FlagEmoji iso2={data.customer.address?.country} style={{ display: 'flex', width: 24 }} />
224
- ) : (
225
- ''
226
- )
227
- }
445
+ <Typography>{countryDetail?.name}</Typography>
446
+ </Box>
447
+ ) : (
448
+ ''
449
+ )
450
+ }
451
+ direction={InfoDirection}
452
+ alignItems={InfoAlignItems}
453
+ />
454
+ <InfoRow
455
+ label={t('admin.customer.address.state')}
456
+ value={data.customer.address?.state}
457
+ direction={InfoDirection}
458
+ alignItems={InfoAlignItems}
459
+ />
460
+ <InfoRow
461
+ label={t('admin.customer.address.city')}
462
+ value={data.customer.address?.city}
463
+ direction={InfoDirection}
464
+ alignItems={InfoAlignItems}
465
+ />
466
+ <InfoRow
467
+ label={t('admin.customer.address.line1')}
468
+ value={data.customer.address?.line1}
469
+ direction={InfoDirection}
470
+ alignItems={InfoAlignItems}
471
+ />
472
+ <InfoRow
473
+ label={t('admin.customer.address.line2')}
474
+ value={data.customer.address?.line2}
475
+ direction={InfoDirection}
476
+ alignItems={InfoAlignItems}
477
+ />
478
+ <InfoRow
479
+ label={t('admin.customer.address.postal_code')}
480
+ value={data.customer.address?.postal_code}
481
+ direction={InfoDirection}
482
+ alignItems={InfoAlignItems}
483
+ />
484
+ </Box>
485
+ </Box>
486
+ <Divider />
487
+ <Box className="section">
488
+ <SectionHeader title={t('common.metadata.label')}>
489
+ <Button
490
+ variant="text"
491
+ color="inherit"
492
+ size="small"
493
+ sx={{ color: 'text.link' }}
494
+ disabled={state.editing.metadata}
495
+ onClick={handleEditMetadata}>
496
+ {t('common.edit')}
497
+ </Button>
498
+ </SectionHeader>
499
+ <Box className="section-body">
500
+ <MetadataList data={data.customer.metadata} handleEditMetadata={handleEditMetadata} />
501
+ {state.editing.metadata && (
502
+ <MetadataEditor
503
+ data={data.customer}
504
+ loading={state.loading.metadata}
505
+ onSave={onUpdateMetadata}
506
+ onCancel={() => setState((prev) => ({ editing: { ...prev.editing, metadata: false } }))}
228
507
  />
229
- <InfoRow label={t('admin.customer.address.state')} value={data.customer.address?.state} />
230
- <InfoRow label={t('admin.customer.address.city')} value={data.customer.address?.city} />
231
- <InfoRow label={t('admin.customer.address.line1')} value={data.customer.address?.line1} />
232
- <InfoRow label={t('admin.customer.address.line2')} value={data.customer.address?.line2} />
233
- <InfoRow label={t('admin.customer.address.postal_code')} value={data.customer.address?.postal_code} />
234
- </Stack>
235
- }
236
- />
237
- {state.editing.customer && (
238
- <EditCustomer
239
- data={data.customer}
240
- loading={state.loading.customer}
241
- onSave={onUpdateInfo}
242
- onCancel={() => setState((prev) => ({ editing: { ...prev.editing, customer: false } }))}
243
- />
244
- )}
245
- </Stack>
246
- </Box>
247
- <Box className="section">
248
- <SectionHeader title={t('admin.subscriptions')} mb={0} />
249
- <Box className="section-body">
250
- <SubscriptionList
251
- features={{ customer: false, toolbar: false }}
252
- customer_id={data.customer.id}
253
- status={['active', 'trialing', 'paused', 'past_due', 'canceled'].join(',')}
254
- />
255
- </Box>
256
- </Box>
257
- <Box className="section">
258
- <SectionHeader title={t('admin.invoices')} mb={0} />
259
- <Box className="section-body">
260
- <InvoiceList
261
- features={{ customer: false, toolbar: false }}
262
- customer_id={data.customer.id}
263
- status={['open', 'paid', 'uncollectible', 'draft', 'void'].join(',')}
264
- />
265
- </Box>
266
- </Box>
267
- <Box className="section">
268
- <SectionHeader title={t('admin.payments')} mb={0} />
269
- <Box className="section-body">
270
- <PaymentList features={{ customer: false, toolbar: false }} customer_id={data.customer.id} />
271
- </Box>
272
- </Box>
273
- <Box className="section">
274
- <SectionHeader title={t('admin.payouts')} mb={0} />
275
- <Box className="section-body">
276
- <PayoutList
277
- features={{ customer: false, toolbar: false }}
278
- customer_id={data.customer.id}
279
- status={['paid', 'pending', 'failed', 'canceled'].join(',')}
280
- />
281
- </Box>
282
- </Box>
283
- <Box className="section">
284
- <SectionHeader title={t('admin.refunds')} mb={0} />
285
- <Box className="section-body">
286
- <RefundList
287
- features={{ customer: false, toolbar: false }}
288
- customer_id={data.customer.id}
289
- status={['succeeded', 'canceled', 'failed', 'requires_action', 'pending'].join(',')}
290
- />
508
+ )}
509
+ </Box>
510
+ </Box>
291
511
  </Box>
292
- </Box>
293
- <Box className="section">
294
- <SectionHeader title={t('common.metadata.label')}>
295
- <Button
296
- variant="outlined"
297
- color="inherit"
298
- size="small"
299
- disabled={state.editing.metadata}
300
- onClick={() => setState((prev) => ({ editing: { ...prev.editing, metadata: true } }))}>
301
- <Edit fontSize="small" sx={{ mr: 0.5 }} />
302
- {t('common.metadata.edit')}
303
- </Button>
304
- </SectionHeader>
305
- <Box className="section-body">
306
- {!state.editing.metadata &&
307
- (isEmpty(data.customer.metadata) ? (
308
- <Typography color="text.secondary">{t('common.metadata.empty')}</Typography>
309
- ) : (
310
- Object.keys(data.customer.metadata || {}).map((key) => (
311
- <InfoRow key={key} label={key} value={data.customer.metadata[key]} />
312
- ))
313
- ))}
314
- {state.editing.metadata && (
315
- <MetadataEditor
316
- data={data.customer}
317
- loading={state.loading.metadata}
318
- onSave={onUpdateMetadata}
319
- onCancel={() => setState((prev) => ({ editing: { ...prev.editing, metadata: false } }))}
320
- />
321
- )}
322
- </Box>
323
- </Box>
324
- <Box className="section">
325
- <SectionHeader title={t('admin.events')} />
326
- <Box className="section-body">
327
- <EventList features={{ toolbar: false }} object_id={data.customer.id} />
328
- </Box>
329
- </Box>
512
+ </Stack>
330
513
  </Root>
331
514
  );
332
515
  }
@@ -2,14 +2,12 @@
2
2
  import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
3
  import DidAddress from '@arcblock/ux/lib/DID';
4
4
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
5
- import { api, formatTime } from '@blocklet/payment-react';
5
+ import { api, formatTime, Table } from '@blocklet/payment-react';
6
6
  import type { TCustomer } from '@blocklet/payment-types';
7
7
  import { Avatar, CircularProgress, Stack, Typography } from '@mui/material';
8
8
  import { useEffect, useState } from 'react';
9
9
  import { Link } from 'react-router-dom';
10
10
 
11
- import Table from '../../../../components/table';
12
-
13
11
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TCustomer[]; count: number }> => {
14
12
  const search = new URLSearchParams();
15
13
  Object.keys(params).forEach((key) => {
@@ -60,8 +58,12 @@ export default function CustomersList() {
60
58
  const item = data.list[index] as TCustomer;
61
59
  return (
62
60
  <Link to={`/admin/customers/${item.id}`}>
63
- <Stack direction="row" alignItems="center" spacing={0.5}>
64
- <Avatar src={`/.well-known/service/user/avatar/${item.did}?imageFilter=resize&w=48&h=48`} />
61
+ <Stack direction="row" alignItems="center" spacing={1}>
62
+ <Avatar
63
+ src={`/.well-known/service/user/avatar/${item.did}?imageFilter=resize&w=48&h=48`}
64
+ variant="square"
65
+ sx={{ borderRadius: 'var(--radius-m, 8px)' }}
66
+ />
65
67
  <Typography>{item.name}</Typography>
66
68
  </Stack>
67
69
  </Link>
@@ -167,6 +169,7 @@ export default function CustomersList() {
167
169
  }}
168
170
  loading={!data.list}
169
171
  onChange={onTableChange}
172
+ emptyNodeText={t('empty.customers')}
170
173
  />
171
174
  );
172
175
  }