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
@@ -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,12 +148,10 @@ 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
- <MenuItem value="package" disabled>
154
- {t('admin.price.models.package')}
155
- </MenuItem>
154
+ <MenuItem value="package">{t('admin.price.models.package')}</MenuItem>
156
155
  <MenuItem value="graduated" disabled>
157
156
  {t('admin.price.models.graduated')}
158
157
  </MenuItem>
@@ -177,7 +176,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
177
176
  disabled={isLocked}
178
177
  render={({ field }) => (
179
178
  <Box>
180
- <FormLabel>
179
+ <FormLabel sx={{ color: 'text.primary' }}>
181
180
  <Stack direction="row" alignItems="center" spacing={0.5}>
182
181
  <Typography component="span" color="text.primary">
183
182
  {t('admin.price.amount')}
@@ -325,9 +324,10 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
325
324
  disabled={isLocked}
326
325
  render={({ field }) => (
327
326
  <Box>
328
- <FormLabel>{t('admin.price.recurring.interval')}</FormLabel>
327
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.recurring.interval')}</FormLabel>
329
328
  <Select
330
329
  {...field}
330
+ value={field.value || 'month_1'}
331
331
  onChange={(e) => {
332
332
  const [interval, count] = e.target.value.split('_');
333
333
  setValue(getFieldName('recurring.interval'), interval);
@@ -353,6 +353,14 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
353
353
  name={getFieldName('recurring.interval_count')}
354
354
  control={control}
355
355
  disabled={isLocked}
356
+ rules={{
357
+ validate: (v) => {
358
+ if (!intervalCountPositive(v)) {
359
+ return t('admin.price.recurring.intervalCountTip');
360
+ }
361
+ return true;
362
+ },
363
+ }}
356
364
  render={({ field }) => (
357
365
  <Box ml={2}>
358
366
  <FormLabel>&nbsp;</FormLabel>
@@ -361,13 +369,16 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
361
369
  type="number"
362
370
  size="small"
363
371
  sx={{ width: INPUT_WIDTH }}
364
- error={!intervalCountPositive(field.value)}
365
- helperText={!intervalCountPositive(field.value) && t('admin.price.recurring.intervalCountTip')}
372
+ error={!!get(errors, getFieldName('recurring.interval_count'))}
373
+ helperText={get(errors, getFieldName('recurring.interval_count'))?.message as string}
366
374
  InputProps={{
367
375
  startAdornment: <InputAdornment position="start">{t('common.every')}</InputAdornment>,
368
376
  endAdornment: (
369
377
  <InputAdornment position="end">
370
- <select onChange={(e) => setValue(getFieldName('recurring.interval'), e.target.value)}>
378
+ <select
379
+ onChange={(e) => setValue(getFieldName('recurring.interval'), e.target.value)}
380
+ value={intervalSelectValue}
381
+ style={{ background: 'none', outline: 'none' }}>
371
382
  <option value="day">{t('common.days')}</option>
372
383
  <option value="week">{t('common.weeks')}</option>
373
384
  <option value="month">{t('common.months')}</option>
@@ -416,7 +427,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
416
427
  disabled={isLocked}
417
428
  render={({ field }) => (
418
429
  <Box>
419
- <FormLabel>{t('admin.price.recurring.aggregate')}</FormLabel>
430
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.recurring.aggregate')}</FormLabel>
420
431
  <Select {...field} sx={{ width: INPUT_WIDTH }} size="small">
421
432
  <MenuItem value="sum">{t('admin.price.aggregate.sum')}</MenuItem>
422
433
  <MenuItem value="max">{t('admin.price.aggregate.max')}</MenuItem>
@@ -435,7 +446,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
435
446
  control={control}
436
447
  render={({ field }) => (
437
448
  <Box>
438
- <FormLabel>{t('admin.price.quantityAvailable.label')}</FormLabel>
449
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.quantityAvailable.label')}</FormLabel>
439
450
  <TextField
440
451
  {...field}
441
452
  size="small"
@@ -453,7 +464,9 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
453
464
  control={control}
454
465
  render={({ field }) => (
455
466
  <Box>
456
- <FormLabel>{t('admin.price.quantityLimitPerCheckout.label')}</FormLabel>
467
+ <FormLabel sx={{ color: 'text.primary' }}>
468
+ {t('admin.price.quantityLimitPerCheckout.label')}
469
+ </FormLabel>
457
470
  <TextField
458
471
  {...field}
459
472
  size="small"
@@ -471,7 +484,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
471
484
  control={control}
472
485
  render={({ field }) => (
473
486
  <Box>
474
- <FormLabel>{t('admin.price.nickname.label')}</FormLabel>
487
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.nickname.label')}</FormLabel>
475
488
  <TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} />
476
489
  </Box>
477
490
  )}
@@ -481,7 +494,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
481
494
  control={control}
482
495
  render={({ field }) => (
483
496
  <Box>
484
- <FormLabel>{t('admin.price.lookup_key.label')}</FormLabel>
497
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.lookup_key.label')}</FormLabel>
485
498
  <TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} />
486
499
  </Box>
487
500
  )}
@@ -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')}