payment-kit 1.14.21 → 1.14.22

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 +27 -12
  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
@@ -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 { Status, api, formatBNStr, formatTime, getPaymentIntentStatusColor } from '@blocklet/payment-react';
3
+ import { Status, api, formatBNStr, formatTime, getPaymentIntentStatusColor, Table } from '@blocklet/payment-react';
4
4
  import type { TPaymentIntentExpanded } from '@blocklet/payment-types';
5
5
  import { CircularProgress, Typography } from '@mui/material';
6
6
  import { useLocalStorageState } from 'ahooks';
@@ -10,7 +10,6 @@ import { Link } from 'react-router-dom';
10
10
  import { debounce } from '../../libs/util';
11
11
  import CustomerLink from '../customer/link';
12
12
  import FilterToolbar from '../filter-toolbar';
13
- import Table from '../table';
14
13
  import PaymentIntentActions from './actions';
15
14
 
16
15
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TPaymentIntentExpanded[]; count: number }> => {
@@ -105,7 +104,7 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
105
104
  label: t('common.amount'),
106
105
  name: 'id',
107
106
  align: 'right',
108
- width: 60,
107
+ width: 80,
109
108
  options: {
110
109
  customBodyRenderLite: (_: string, index: number) => {
111
110
  const item = data.list[index] as TPaymentIntentExpanded;
@@ -270,6 +269,7 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
270
269
  />
271
270
  )
272
271
  }
272
+ emptyNodeText={t('empty.payments')}
273
273
  />
274
274
  );
275
275
  }
@@ -1,31 +1,49 @@
1
- /* eslint-disable no-unsafe-optional-chaining */
2
- import { Box } from '@mui/material';
3
- import { useSize } from 'ahooks';
1
+ import { forwardRef, useImperativeHandle, useRef } from 'react';
2
+ import { useFullscreen, useSize } from 'ahooks';
4
3
  import IframeResizer from 'iframe-resizer-react';
5
- import { useRef } from 'react';
6
-
7
4
  import Chrome from './chrome';
8
5
 
6
+ const PaymentLinkPreview = forwardRef(({ id, version = 1 }: { id: string; version?: number }, ref) => {
7
+ const innerRef = useRef(null);
8
+ const size = useSize(innerRef);
9
+ const scale = (size?.width || 0) / 1280;
10
+ const [fullscreen, { toggleFullscreen }] = useFullscreen(innerRef);
11
+
12
+ useImperativeHandle(ref, () => ({
13
+ ref: innerRef.current,
14
+ fullscreen,
15
+ toggleFullscreen,
16
+ }));
17
+
18
+ return (
19
+ <div ref={innerRef}>
20
+ {fullscreen ? (
21
+ <div style={{ width: '100%', height: '100%', background: '#fff' }}>
22
+ <IframeResizer
23
+ style={{ width: '100%', height: '100vh', overflow: 'hidden', border: 'none' }}
24
+ src={`${window.blocklet.prefix}checkout/pay/${id}?preview=1&version=${version}`}
25
+ />
26
+ </div>
27
+ ) : (
28
+ <Chrome sx={{ width: '100%', height: 800 * scale }}>
29
+ <IframeResizer
30
+ style={{
31
+ width: '1280px',
32
+ height: '800px',
33
+ transform: `scale(${scale})`,
34
+ transformOrigin: 'top left',
35
+ border: 'none',
36
+ }}
37
+ src={`${window.blocklet.prefix}checkout/pay/${id}?preview=1&version=${version}`}
38
+ />
39
+ </Chrome>
40
+ )}
41
+ </div>
42
+ );
43
+ });
44
+
9
45
  PaymentLinkPreview.defaultProps = {
10
46
  version: 1,
11
47
  };
12
48
 
13
- export default function PaymentLinkPreview({ id, version }: { id: string; version?: number }) {
14
- const ref = useRef(null);
15
- const size = useSize(ref);
16
- return (
17
- <Chrome sx={{ width: '100%', maxWidth: 840 }}>
18
- <Box ref={ref}>&nbsp;</Box>
19
- <IframeResizer
20
- style={{
21
- minWidth: size ? size.width / 0.8 : 1050,
22
- minHeight: '64vh',
23
- transform: 'scale(0.8)',
24
- transformOrigin: 'top left',
25
- border: 'none',
26
- }}
27
- src={`${window.blocklet.prefix}checkout/pay/${id}?preview=1&version=${version}`}
28
- />
29
- </Chrome>
30
- );
31
- }
49
+ export default PaymentLinkPreview;
@@ -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 { Status, api, formatBNStr, formatTime, getPayoutStatusColor } from '@blocklet/payment-react';
3
+ import { Status, api, formatBNStr, formatTime, getPayoutStatusColor, Table } from '@blocklet/payment-react';
4
4
  import type { TPayoutExpanded } from '@blocklet/payment-types';
5
5
  import { CircularProgress, Typography } from '@mui/material';
6
6
  import { useLocalStorageState } from 'ahooks';
@@ -10,7 +10,6 @@ import { Link } from 'react-router-dom';
10
10
  import { debounce } from '../../libs/util';
11
11
  import CustomerLink from '../customer/link';
12
12
  import FilterToolbar from '../filter-toolbar';
13
- import Table from '../table';
14
13
  import PayoutActions from './actions';
15
14
 
16
15
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TPayoutExpanded[]; count: number }> => {
@@ -103,7 +102,7 @@ export default function PayoutList({ customer_id, payment_intent_id, status, fea
103
102
  label: t('common.amount'),
104
103
  name: 'id',
105
104
  align: 'right',
106
- width: 60,
105
+ width: 80,
107
106
  options: {
108
107
  customBodyRenderLite: (_: string, index: number) => {
109
108
  const item = data.list[index] as TPayoutExpanded;
@@ -30,6 +30,7 @@ import { styled } from '@mui/system';
30
30
  import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
31
31
  import type { LiteralUnion } from 'type-fest';
32
32
 
33
+ import { get } from 'lodash';
33
34
  import Collapse from '../collapse';
34
35
  import CurrencySelect from './currency-select';
35
36
 
@@ -109,7 +110,6 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
109
110
  return names.reduce((prev, curr) => prev?.[curr], errors as any);
110
111
  };
111
112
  const { settings, livemode } = usePaymentContext();
112
-
113
113
  const currencies = useFieldArray({ control, name: getFieldName('currency_options') });
114
114
 
115
115
  const priceLocked = useWatch({ control, name: getFieldName('locked') });
@@ -117,6 +117,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
117
117
  const isMetered = useWatch({ control, name: getFieldName('recurring.usage_type') }) === 'metered';
118
118
  const isCustomInterval = useWatch({ control, name: getFieldName('recurring.interval_config') }) === 'month_2';
119
119
  const model = useWatch({ control, name: getFieldName('model') });
120
+ const intervalSelectValue = useWatch({ control, name: getFieldName('recurring.interval') });
120
121
  const quantityPositive = (v: number | undefined) => !v || v.toString().match(/^(0|[1-9]\d*)$/);
121
122
  const intervalCountPositive = (v: number) => Number.isInteger(Number(v)) && v > 0;
122
123
 
@@ -147,7 +148,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
147
148
  disabled={isLocked}
148
149
  render={({ field }) => (
149
150
  <Box sx={{ width: INPUT_WIDTH }}>
150
- <FormLabel>{t('admin.price.model')}</FormLabel>
151
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.model')}</FormLabel>
151
152
  <Select {...field} fullWidth size="small">
152
153
  <MenuItem value="standard">{t('admin.price.models.standard')}</MenuItem>
153
154
  <MenuItem value="package" disabled>
@@ -177,7 +178,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
177
178
  disabled={isLocked}
178
179
  render={({ field }) => (
179
180
  <Box>
180
- <FormLabel>
181
+ <FormLabel sx={{ color: 'text.primary' }}>
181
182
  <Stack direction="row" alignItems="center" spacing={0.5}>
182
183
  <Typography component="span" color="text.primary">
183
184
  {t('admin.price.amount')}
@@ -325,9 +326,10 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
325
326
  disabled={isLocked}
326
327
  render={({ field }) => (
327
328
  <Box>
328
- <FormLabel>{t('admin.price.recurring.interval')}</FormLabel>
329
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.recurring.interval')}</FormLabel>
329
330
  <Select
330
331
  {...field}
332
+ value={field.value || 'month_1'}
331
333
  onChange={(e) => {
332
334
  const [interval, count] = e.target.value.split('_');
333
335
  setValue(getFieldName('recurring.interval'), interval);
@@ -353,6 +355,14 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
353
355
  name={getFieldName('recurring.interval_count')}
354
356
  control={control}
355
357
  disabled={isLocked}
358
+ rules={{
359
+ validate: (v) => {
360
+ if (!intervalCountPositive(v)) {
361
+ return t('admin.price.recurring.intervalCountTip');
362
+ }
363
+ return true;
364
+ },
365
+ }}
356
366
  render={({ field }) => (
357
367
  <Box ml={2}>
358
368
  <FormLabel>&nbsp;</FormLabel>
@@ -361,13 +371,16 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
361
371
  type="number"
362
372
  size="small"
363
373
  sx={{ width: INPUT_WIDTH }}
364
- error={!intervalCountPositive(field.value)}
365
- helperText={!intervalCountPositive(field.value) && t('admin.price.recurring.intervalCountTip')}
374
+ error={!!get(errors, getFieldName('recurring.interval_count'))}
375
+ helperText={get(errors, getFieldName('recurring.interval_count'))?.message as string}
366
376
  InputProps={{
367
377
  startAdornment: <InputAdornment position="start">{t('common.every')}</InputAdornment>,
368
378
  endAdornment: (
369
379
  <InputAdornment position="end">
370
- <select onChange={(e) => setValue(getFieldName('recurring.interval'), e.target.value)}>
380
+ <select
381
+ onChange={(e) => setValue(getFieldName('recurring.interval'), e.target.value)}
382
+ value={intervalSelectValue}
383
+ style={{ background: 'none', outline: 'none' }}>
371
384
  <option value="day">{t('common.days')}</option>
372
385
  <option value="week">{t('common.weeks')}</option>
373
386
  <option value="month">{t('common.months')}</option>
@@ -416,7 +429,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
416
429
  disabled={isLocked}
417
430
  render={({ field }) => (
418
431
  <Box>
419
- <FormLabel>{t('admin.price.recurring.aggregate')}</FormLabel>
432
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.recurring.aggregate')}</FormLabel>
420
433
  <Select {...field} sx={{ width: INPUT_WIDTH }} size="small">
421
434
  <MenuItem value="sum">{t('admin.price.aggregate.sum')}</MenuItem>
422
435
  <MenuItem value="max">{t('admin.price.aggregate.max')}</MenuItem>
@@ -435,7 +448,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
435
448
  control={control}
436
449
  render={({ field }) => (
437
450
  <Box>
438
- <FormLabel>{t('admin.price.quantityAvailable.label')}</FormLabel>
451
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.quantityAvailable.label')}</FormLabel>
439
452
  <TextField
440
453
  {...field}
441
454
  size="small"
@@ -453,7 +466,9 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
453
466
  control={control}
454
467
  render={({ field }) => (
455
468
  <Box>
456
- <FormLabel>{t('admin.price.quantityLimitPerCheckout.label')}</FormLabel>
469
+ <FormLabel sx={{ color: 'text.primary' }}>
470
+ {t('admin.price.quantityLimitPerCheckout.label')}
471
+ </FormLabel>
457
472
  <TextField
458
473
  {...field}
459
474
  size="small"
@@ -471,7 +486,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
471
486
  control={control}
472
487
  render={({ field }) => (
473
488
  <Box>
474
- <FormLabel>{t('admin.price.nickname.label')}</FormLabel>
489
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.nickname.label')}</FormLabel>
475
490
  <TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} />
476
491
  </Box>
477
492
  )}
@@ -481,7 +496,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
481
496
  control={control}
482
497
  render={({ field }) => (
483
498
  <Box>
484
- <FormLabel>{t('admin.price.lookup_key.label')}</FormLabel>
499
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.lookup_key.label')}</FormLabel>
485
500
  <TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} />
486
501
  </Box>
487
502
  )}
@@ -1,4 +1,3 @@
1
- import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
1
  import Toast from '@arcblock/ux/lib/Toast';
3
2
  import { api, formatError, formatPrice, usePaymentContext } from '@blocklet/payment-react';
4
3
  import type { TPriceExpanded } from '@blocklet/payment-types';
@@ -60,12 +59,10 @@ export function UpsellForm({ data, onChange }: { data: TPriceExpanded; onChange:
60
59
  }
61
60
 
62
61
  export default function PriceUpsell({ data, onChange }: { data: TPriceExpanded; onChange: Function }) {
63
- const { t } = useLocaleContext();
64
-
65
62
  return (
66
63
  <Grid container>
67
64
  <Grid item xs={12} md={6}>
68
- <InfoRow label={t('admin.price.upsell.to')} value={<UpsellForm data={data} onChange={onChange} />} />
65
+ <InfoRow label="" value={<UpsellForm data={data} onChange={onChange} />} />
69
66
  </Grid>
70
67
  </Grid>
71
68
  );
@@ -1,30 +1,49 @@
1
- import { Box } from '@mui/material';
2
- import { useSize } from 'ahooks';
1
+ import { forwardRef, useImperativeHandle, useRef } from 'react';
2
+ import { useFullscreen, useSize } from 'ahooks';
3
3
  import IframeResizer from 'iframe-resizer-react';
4
- import { useRef } from 'react';
5
-
6
4
  import Chrome from '../payment-link/chrome';
7
5
 
6
+ const PricingTablePreview = forwardRef(({ id, version = 1 }: { id: string; version?: number }, ref) => {
7
+ const innerRef = useRef(null);
8
+ const size = useSize(innerRef);
9
+ const scale = (size?.width || 0) / 1280;
10
+ const [fullscreen, { toggleFullscreen }] = useFullscreen(innerRef);
11
+
12
+ useImperativeHandle(ref, () => ({
13
+ ref: innerRef.current,
14
+ fullscreen,
15
+ toggleFullscreen,
16
+ }));
17
+
18
+ return (
19
+ <div ref={innerRef}>
20
+ {fullscreen ? (
21
+ <div style={{ width: '100%', height: '100%', background: '#fff', border: 'none' }}>
22
+ <IframeResizer
23
+ style={{ width: '100%', height: '100vh', overflow: 'hidden' }}
24
+ src={`${window.blocklet.prefix}checkout/pricing-table/${id}?preview=1&version=${version}`}
25
+ />
26
+ </div>
27
+ ) : (
28
+ <Chrome sx={{ width: '100%', height: 800 * scale }}>
29
+ <IframeResizer
30
+ style={{
31
+ width: '1280px',
32
+ height: '800px',
33
+ transform: `scale(${scale})`,
34
+ transformOrigin: 'top left',
35
+ border: 'none',
36
+ }}
37
+ src={`${window.blocklet.prefix}checkout/pricing-table/${id}?preview=1&version=${version}`}
38
+ />
39
+ </Chrome>
40
+ )}
41
+ </div>
42
+ );
43
+ });
44
+
8
45
  PricingTablePreview.defaultProps = {
9
46
  version: 1,
10
47
  };
11
48
 
12
- export default function PricingTablePreview({ id, version }: { id: string; version?: number }) {
13
- const ref = useRef(null);
14
- const size = useSize(ref);
15
- return (
16
- <Chrome sx={{ width: '100%', maxWidth: 840 }}>
17
- <Box ref={ref}>&nbsp;</Box>
18
- <IframeResizer
19
- style={{
20
- minWidth: size ? size.width / 0.8 : 1050,
21
- minHeight: '64vh',
22
- transform: 'scale(0.8)',
23
- transformOrigin: 'top left',
24
- border: 'none',
25
- }}
26
- src={`${window.blocklet.prefix}checkout/pricing-table/${id}?preview=1&version=${version}`}
27
- />
28
- </Chrome>
29
- );
30
- }
49
+ export default PricingTablePreview;
@@ -28,7 +28,7 @@ export default function CrossSellSelect({ data, onSelect, valid }: Props) {
28
28
  };
29
29
 
30
30
  return (
31
- <FormControl error={!valid}>
31
+ <FormControl error={!valid} sx={{ width: '100%' }}>
32
32
  <Select
33
33
  value={value}
34
34
  sx={{ minWidth: 300 }}
@@ -9,7 +9,6 @@ import { useState } from 'react';
9
9
  import { ProductsProvider, useProductsContext } from '../../contexts/products';
10
10
  import { formatProductPrice, isProductCurrenciesMatched } from '../../libs/util';
11
11
  import InfoCard from '../info-card';
12
- import InfoRow from '../info-row';
13
12
  import CrossSellSelect from './cross-sell-select';
14
13
 
15
14
  export function CrossSellForm({ data, onChange }: { data: TProductExpanded; onChange: Function }) {
@@ -84,13 +83,13 @@ export function CrossSellForm({ data, onChange }: { data: TProductExpanded; onCh
84
83
  }
85
84
 
86
85
  export default function ProductCrossSell({ data, onChange }: { data: TProductExpanded; onChange: Function }) {
87
- const { t } = useLocaleContext();
86
+ // const { t } = useLocaleContext();
88
87
 
89
88
  return (
90
89
  <ProductsProvider>
91
90
  <Grid container>
92
- <Grid item xs={12} md={6}>
93
- <InfoRow label={t('admin.product.cross_sell.to')} value={<CrossSellForm data={data} onChange={onChange} />} />
91
+ <Grid item xs={12}>
92
+ <CrossSellForm data={data} onChange={onChange} />
94
93
  </Grid>
95
94
  </Grid>
96
95
  </ProductsProvider>
@@ -37,7 +37,6 @@ export default function EditPrice({
37
37
  recurring: price.recurring
38
38
  ? {
39
39
  ...price.recurring,
40
- interval_config: [price.recurring.interval, price.recurring.interval_count].join('_'),
41
40
  }
42
41
  : DEFAULT_PRICE.recurring,
43
42
  currency_options: cloneDeep(price.currency_options).map((x: any) => {
@@ -1,15 +1,15 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
- import { Status, api, formatBNStr, formatTime, getPaymentIntentStatusColor } from '@blocklet/payment-react';
3
+ import { Status, api, formatBNStr, formatTime, getPaymentIntentStatusColor, Table } from '@blocklet/payment-react';
4
4
  import type { TRefundExpanded } from '@blocklet/payment-types';
5
5
  import { CircularProgress, Typography } from '@mui/material';
6
6
  import { useLocalStorageState } from 'ahooks';
7
7
  import { useEffect, useState } from 'react';
8
8
  import { Link } from 'react-router-dom';
9
9
 
10
+ import { capitalize, toLower } from 'lodash';
10
11
  import CustomerLink from '../customer/link';
11
12
  import FilterToolbar from '../filter-toolbar';
12
- import Table from '../table';
13
13
  import RefundActions from './actions';
14
14
 
15
15
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TRefundExpanded[]; count: number }> => {
@@ -125,7 +125,7 @@ export default function RefundList({
125
125
  label: t('common.amount'),
126
126
  name: 'id',
127
127
  align: 'right',
128
- width: 60,
128
+ width: 80,
129
129
  options: {
130
130
  customBodyRenderLite: (_: string, index: number) => {
131
131
  const item = data.list[index] as TRefundExpanded;
@@ -178,7 +178,11 @@ export default function RefundList({
178
178
  options: {
179
179
  customBodyRenderLite: (_: string, index: number) => {
180
180
  const item = data.list[index] as TRefundExpanded;
181
- return <Link to={`/admin/payments/${item.id}`}>{item.description || item.id}</Link>;
181
+ return (
182
+ <Link to={`/admin/payments/${item.id}`}>
183
+ {item.description ? capitalize(toLower(item.description.replace(/_/g, ' '))) : item.id}
184
+ </Link>
185
+ );
182
186
  },
183
187
  },
184
188
  },
@@ -294,6 +298,7 @@ export default function RefundList({
294
298
  />
295
299
  )
296
300
  }
301
+ emptyNodeText={t('empty.refunds')}
297
302
  />
298
303
  );
299
304
  }
@@ -17,8 +17,15 @@ export default function SectionHeader(props: Props) {
17
17
  alignItems="center"
18
18
  flexWrap="wrap"
19
19
  gap={1}
20
- sx={{ mb: props.mb, mt: props.mt, pb: 1, width: 1, borderBottom: '1px solid #eee' }}>
21
- <Typography variant="h6" sx={{ fontWeight: 600 }}>
20
+ sx={{ mb: props.mb, mt: props.mt, pb: 1 }}>
21
+ <Typography
22
+ variant="h3"
23
+ sx={{
24
+ fontSize: {
25
+ xs: '18px',
26
+ md: '1.25rem',
27
+ },
28
+ }}>
22
29
  {props.title}
23
30
  </Typography>
24
31
  {props.children}
@@ -28,6 +35,6 @@ export default function SectionHeader(props: Props) {
28
35
 
29
36
  SectionHeader.defaultProps = {
30
37
  children: null,
31
- mb: 1,
32
- mt: 1,
38
+ mb: 1.5,
39
+ mt: 1.5,
33
40
  };
@@ -5,19 +5,22 @@ import { Stack, Tooltip, Typography } from '@mui/material';
5
5
 
6
6
  type Props = {
7
7
  subscription: TSubscriptionExpanded;
8
- variant?: 'body1' | 'h5';
8
+ variant?: 'body1' | 'h5' | 'h4' | 'h3' | 'h2' | 'h1';
9
+ hideSubscription?: boolean;
9
10
  };
10
11
 
11
- export default function SubscriptionDescription({ subscription, variant }: Props) {
12
+ export default function SubscriptionDescription({ subscription, variant, hideSubscription }: Props) {
12
13
  if (subscription.description) {
13
14
  return (
14
15
  <Stack direction="row" alignItems="center" spacing={1}>
15
- <Typography variant={variant} fontWeight={600}>
16
+ <Typography variant={variant} fontWeight={600} className="subscription-description">
16
17
  {subscription.description}
17
18
  </Typography>
18
- <Tooltip title={formatSubscriptionProduct(subscription.items)}>
19
- <InfoOutlined sx={{ color: 'text.secondary' }} fontSize="small" />
20
- </Tooltip>
19
+ {!hideSubscription && (
20
+ <Tooltip title={formatSubscriptionProduct(subscription.items)}>
21
+ <InfoOutlined sx={{ color: 'text.secondary' }} fontSize="small" />
22
+ </Tooltip>
23
+ )}
21
24
  </Stack>
22
25
  );
23
26
  }
@@ -31,4 +34,5 @@ export default function SubscriptionDescription({ subscription, variant }: Props
31
34
 
32
35
  SubscriptionDescription.defaultProps = {
33
36
  variant: 'body1',
37
+ hideSubscription: false,
34
38
  };
@@ -1,11 +1,10 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
- import { formatPrice } from '@blocklet/payment-react';
3
+ import { formatPrice, Table } from '@blocklet/payment-react';
4
4
  import type { TPaymentCurrency, TSubscriptionItemExpanded } from '@blocklet/payment-types';
5
- import { Stack, Typography } from '@mui/material';
5
+ import { Avatar, Stack, Typography } from '@mui/material';
6
6
 
7
7
  import Copyable from '../../copyable';
8
- import Table from '../../table';
9
8
  import LineItemActions from './actions';
10
9
  import UsageRecords from './usage-records';
11
10
 
@@ -19,9 +18,10 @@ SubscriptionItemList.defaultProps = {
19
18
  mode: 'customer',
20
19
  };
21
20
 
21
+ const size = { width: 48, height: 48 };
22
+
22
23
  export default function SubscriptionItemList({ data, currency, mode }: ListProps) {
23
24
  const { t } = useLocaleContext();
24
-
25
25
  const columns = [
26
26
  {
27
27
  label: t('admin.subscription.product'),
@@ -30,12 +30,33 @@ export default function SubscriptionItemList({ data, currency, mode }: ListProps
30
30
  customBodyRenderLite: (_: string, index: number) => {
31
31
  const item = data[index] as TSubscriptionItemExpanded;
32
32
  return (
33
- <Stack>
33
+ <Stack
34
+ flexDirection="row"
35
+ flexWrap="wrap"
36
+ alignItems="center"
37
+ gap={{
38
+ xs: 1,
39
+ sm: 2,
40
+ }}>
41
+ {item.price.product.images.length > 0 ? (
42
+ // @ts-ignore
43
+ <Avatar
44
+ key={item.price.product_id}
45
+ src={item.price.product.images[0]}
46
+ alt={item.price.product.name}
47
+ variant="rounded"
48
+ sx={size}
49
+ />
50
+ ) : (
51
+ <Avatar key={item.price.product_id} variant="rounded" sx={size}>
52
+ {item.price.product.name.slice(0, 1)}
53
+ </Avatar>
54
+ )}
34
55
  <Typography color="text.primary" fontWeight={600}>
35
56
  {item?.price.product.name}
36
57
  {mode === 'customer' ? '' : ` - ${item?.price_id}`}
37
58
  </Typography>
38
- <Typography color="text.secondary">
59
+ <Typography color="text.secondary" whiteSpace="nowrap">
39
60
  {formatPrice(item.price, currency, item?.price.product.unit_label)}
40
61
  </Typography>
41
62
  </Stack>
@@ -112,6 +133,7 @@ export default function SubscriptionItemList({ data, currency, mode }: ListProps
112
133
  page: 0,
113
134
  rowsPerPage: 100,
114
135
  }}
136
+ emptyNodeText={t('customer.product.empty')}
115
137
  />
116
138
  );
117
139
  }
@@ -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 { Status, api, formatTime } from '@blocklet/payment-react';
3
+ import { Status, api, formatTime, Table } from '@blocklet/payment-react';
4
4
  import type { TSubscriptionExpanded } from '@blocklet/payment-types';
5
5
  import { CircularProgress } from '@mui/material';
6
6
  import { useLocalStorageState } from 'ahooks';
@@ -9,7 +9,6 @@ import { Link } from 'react-router-dom';
9
9
 
10
10
  import CustomerLink from '../customer/link';
11
11
  import FilterToolbar from '../filter-toolbar';
12
- import Table from '../table';
13
12
  import SubscriptionActions from './actions';
14
13
  import SubscriptionDescription from './description';
15
14
  import SubscriptionStatus from './status';
@@ -238,6 +237,7 @@ export default function SubscriptionList({ customer_id, features, status }: List
238
237
  setSearch={setSearch}
239
238
  search={search}
240
239
  status={['active', 'trialing', 'paused', 'past_due', 'canceled', 'incomplete', 'incomplete_expired']}
240
+ formatStatus={(x) => (x === 'canceled' ? 'ended' : x)}
241
241
  />
242
242
  )
243
243
  }
@@ -4,6 +4,7 @@ import type { TSubscriptionExpanded } from '@blocklet/payment-types';
4
4
  import { useRequest } from 'ahooks';
5
5
 
6
6
  import InfoMetric from '../info-metric';
7
+ import SubscriptionStatus from './status';
7
8
 
8
9
  type Props = {
9
10
  subscription: TSubscriptionExpanded;
@@ -17,15 +18,16 @@ export default function SubscriptionMetrics({ subscription }: Props) {
17
18
  const { t } = useLocaleContext();
18
19
  const { data: upcoming } = useRequest(() => fetchUpcoming(subscription.id));
19
20
 
20
- let scheduleToCancelTime = 0;
21
- if (['active', 'trialing', 'past_due'].includes(subscription.status) && subscription.cancel_at) {
22
- scheduleToCancelTime = subscription.cancel_at * 1000;
23
- } else if (subscription.status !== 'canceled' && subscription.cancel_at_period_end) {
24
- scheduleToCancelTime = subscription.current_period_end * 1000;
25
- }
21
+ // let scheduleToCancelTime = 0;
22
+ // if (['active', 'trialing', 'past_due'].includes(subscription.status) && subscription.cancel_at) {
23
+ // scheduleToCancelTime = subscription.cancel_at * 1000;
24
+ // } else if (subscription.status !== 'canceled' && subscription.cancel_at_period_end) {
25
+ // scheduleToCancelTime = subscription.current_period_end * 1000;
26
+ // }
26
27
 
27
28
  return (
28
29
  <>
30
+ <InfoMetric label={t('common.status')} value={<SubscriptionStatus subscription={subscription} />} divider />
29
31
  <InfoMetric
30
32
  label={t('admin.subscription.startedAt')}
31
33
  value={formatTime(subscription.start_date ? subscription.start_date * 1000 : subscription.created_at)}
@@ -47,9 +49,9 @@ export default function SubscriptionMetrics({ subscription }: Props) {
47
49
  divider
48
50
  />
49
51
  )}
50
- {scheduleToCancelTime > 0 && (
52
+ {/* {scheduleToCancelTime > 0 && (
51
53
  <InfoMetric label={t('admin.subscription.cancel.schedule')} value={formatTime(scheduleToCancelTime)} divider />
52
- )}
54
+ )} */}
53
55
  {subscription.status === 'canceled' && subscription.canceled_at && (
54
56
  <InfoMetric
55
57
  label={t('admin.subscription.cancel.done')}