payment-kit 1.13.25 → 1.13.27

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 (73) hide show
  1. package/api/src/index.ts +8 -1
  2. package/api/src/integrations/blockchain/nft.ts +125 -0
  3. package/api/src/integrations/blockchain/stake.ts +55 -0
  4. package/api/src/integrations/blocklet/notification.ts +101 -0
  5. package/api/src/integrations/blocklet/passport.ts +139 -0
  6. package/api/src/integrations/stripe/handlers/invoice.ts +1 -1
  7. package/api/src/integrations/stripe/resource.ts +7 -7
  8. package/api/src/integrations/stripe/setup.ts +1 -1
  9. package/api/src/jobs/checkout-session.ts +23 -0
  10. package/api/src/jobs/payment.ts +1 -2
  11. package/api/src/libs/audit.ts +44 -2
  12. package/api/src/libs/payment.ts +3 -4
  13. package/api/src/locales/en.ts +9 -1
  14. package/api/src/locales/zh.ts +9 -1
  15. package/api/src/routes/checkout-sessions.ts +44 -14
  16. package/api/src/routes/connect/collect.ts +1 -2
  17. package/api/src/routes/connect/pay.ts +1 -2
  18. package/api/src/routes/connect/setup.ts +1 -2
  19. package/api/src/routes/connect/shared.ts +7 -3
  20. package/api/src/routes/connect/subscribe.ts +2 -3
  21. package/api/src/routes/index.ts +4 -0
  22. package/api/src/routes/integrations/stripe.ts +1 -1
  23. package/api/src/routes/passports.ts +74 -0
  24. package/api/src/routes/payment-links.ts +12 -2
  25. package/api/src/routes/pricing-table.ts +17 -3
  26. package/api/src/routes/products.ts +3 -3
  27. package/api/src/routes/redirect.ts +18 -0
  28. package/api/src/routes/subscriptions.ts +2 -5
  29. package/api/src/store/migrations/20231021-nft.ts +22 -0
  30. package/api/src/store/models/checkout-session.ts +76 -20
  31. package/api/src/store/models/invoice.ts +2 -0
  32. package/api/src/store/models/payment-intent.ts +2 -0
  33. package/api/src/store/models/payment-link.ts +26 -15
  34. package/api/src/store/models/payment-method.ts +22 -1
  35. package/api/src/store/models/price.ts +2 -0
  36. package/api/src/store/models/subscription.ts +26 -4
  37. package/api/src/store/models/types.ts +32 -1
  38. package/api/third.d.ts +2 -0
  39. package/blocklet.yml +1 -1
  40. package/package.json +7 -5
  41. package/src/components/customer/actions.tsx +15 -17
  42. package/src/components/customer/form.tsx +1 -1
  43. package/src/components/invoice/list.tsx +2 -1
  44. package/src/components/passport/actions.tsx +62 -0
  45. package/src/components/passport/assign.tsx +82 -0
  46. package/src/components/payment-intent/list.tsx +5 -1
  47. package/src/components/payment-link/actions.tsx +14 -1
  48. package/src/components/payment-link/after-pay.tsx +33 -1
  49. package/src/components/payment-link/preview.tsx +3 -6
  50. package/src/components/price/form.tsx +22 -23
  51. package/src/components/pricing-table/actions.tsx +14 -1
  52. package/src/components/pricing-table/payment-settings.tsx +33 -1
  53. package/src/components/pricing-table/preview.tsx +3 -7
  54. package/src/components/pricing-table/product-settings.tsx +4 -0
  55. package/src/components/pricing-table/product-skeleton.tsx +39 -0
  56. package/src/components/product/actions.tsx +14 -1
  57. package/src/components/status.tsx +1 -1
  58. package/src/components/subscription/status.tsx +3 -3
  59. package/src/components/table.tsx +14 -4
  60. package/src/global.css +7 -5
  61. package/src/libs/util.ts +6 -0
  62. package/src/locales/en.tsx +53 -2
  63. package/src/locales/zh.tsx +272 -116
  64. package/src/pages/admin/payments/links/create.tsx +4 -0
  65. package/src/pages/admin/payments/links/detail.tsx +9 -4
  66. package/src/pages/admin/products/index.tsx +2 -0
  67. package/src/pages/admin/products/passports/index.tsx +154 -0
  68. package/src/pages/admin/products/pricing-tables/create.tsx +1 -1
  69. package/src/pages/admin/settings/index.tsx +1 -1
  70. package/src/pages/admin/settings/payment-methods/index.tsx +17 -7
  71. package/src/pages/checkout/pay.tsx +15 -13
  72. package/src/pages/checkout/pricing-table.tsx +127 -91
  73. package/api/src/libs/chain/arcblock.ts +0 -13
@@ -0,0 +1,82 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import Toast from '@arcblock/ux/lib/Toast';
3
+ import { Alert, Box, CircularProgress, MenuItem, Select, Typography } from '@mui/material';
4
+ import { useRequest } from 'ahooks';
5
+ import { useEffect, useState } from 'react';
6
+
7
+ import api from '../../libs/api';
8
+ import ConfirmDialog from '../confirm';
9
+
10
+ const fetchData = (): Promise<any[]> => {
11
+ return api.get('/api/passports').then((res) => res.data);
12
+ };
13
+
14
+ export default function AssignPassportDialog(props: { id: string; onCancel: any }) {
15
+ const { t } = useLocaleContext();
16
+ const { loading, error, data } = useRequest(fetchData);
17
+ const [selected, setSelected] = useState('');
18
+
19
+ useEffect(() => {
20
+ if (data && data.length && !selected) {
21
+ setSelected(data[0].name);
22
+ }
23
+ // eslint-disable-next-line react-hooks/exhaustive-deps
24
+ }, [data]);
25
+
26
+ const onConfirm = async () => {
27
+ if (!selected) {
28
+ Toast.error(t('admin.passport.assignError'));
29
+ return;
30
+ }
31
+
32
+ await api.put('/api/passports/assign', { name: selected, id: props.id });
33
+ Toast.success(t('common.saved'));
34
+ props.onCancel();
35
+ };
36
+
37
+ if (error) {
38
+ return (
39
+ <ConfirmDialog
40
+ title={t('admin.passport.assign')}
41
+ message={<Alert severity="error">{error.message}</Alert>}
42
+ onConfirm={onConfirm}
43
+ onCancel={props.onCancel}
44
+ />
45
+ );
46
+ }
47
+
48
+ if (loading || !data) {
49
+ return (
50
+ <ConfirmDialog
51
+ title={t('admin.passport.assign')}
52
+ message={<CircularProgress />}
53
+ onConfirm={onConfirm}
54
+ onCancel={props.onCancel}
55
+ />
56
+ );
57
+ }
58
+
59
+ const onRoleChange = (e: any) => {
60
+ setSelected(e.target.value);
61
+ };
62
+
63
+ return (
64
+ <ConfirmDialog
65
+ title={t('admin.passport.assign')}
66
+ message={
67
+ <Box>
68
+ <Typography>{t('admin.passport.assignTip')}</Typography>
69
+ <Select value={selected} onChange={onRoleChange} size="small">
70
+ {data.map((x) => (
71
+ <MenuItem key={x.name} value={x.name}>
72
+ <Typography color={selected ? 'text.primary' : 'text.secondary'}>{x.title}</Typography>
73
+ </MenuItem>
74
+ ))}
75
+ </Select>
76
+ </Box>
77
+ }
78
+ onConfirm={onConfirm}
79
+ onCancel={props.onCancel}
80
+ />
81
+ );
82
+ }
@@ -92,11 +92,13 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
92
92
  {
93
93
  label: t('common.amount'),
94
94
  name: 'id',
95
+ align: 'right',
96
+ width: 60,
95
97
  options: {
96
98
  customBodyRenderLite: (_: string, index: number) => {
97
99
  const item = data.list[index] as TPaymentIntentExpanded;
98
100
  return (
99
- <Typography component="span">
101
+ <Typography component="strong" fontWeight={600}>
100
102
  {fromUnitToToken(item?.amount, item?.paymentCurrency.decimal)}&nbsp;
101
103
  {item?.paymentCurrency.symbol}
102
104
  </Typography>
@@ -107,7 +109,9 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
107
109
  {
108
110
  label: t('common.status'),
109
111
  name: 'status',
112
+ width: 60,
110
113
  options: {
114
+ filter: true,
111
115
  customBodyRenderLite: (_: string, index: number) => {
112
116
  const item = data.list[index] as TPaymentIntentExpanded;
113
117
  return <Status label={item.status} color={getPaymentIntentStatusColor(item.status)} />;
@@ -11,6 +11,7 @@ import { formatError } from '../../libs/util';
11
11
  import Actions from '../actions';
12
12
  import ClickBoundary from '../click-boundary';
13
13
  import ConfirmDialog from '../confirm';
14
+ import AssignPassportDialog from '../passport/assign';
14
15
  import RenamePaymentLink from './rename';
15
16
 
16
17
  type Props = {
@@ -92,7 +93,18 @@ export default function PaymentLinkActions({ data, variant, onChange }: Props) {
92
93
  },
93
94
  { label: t('admin.paymentLink.rename'), handler: () => setState({ action: 'rename' }), color: 'primary' },
94
95
  { label: t('admin.paymentLink.archive'), handler: () => setState({ action: 'archive' }), color: 'primary' },
95
- { label: t('admin.paymentLink.remove'), handler: () => setState({ action: 'remove' }), color: 'error' },
96
+ {
97
+ label: t('admin.paymentLink.remove'),
98
+ handler: () => setState({ action: 'remove' }),
99
+ color: 'error',
100
+ divider: true,
101
+ },
102
+ {
103
+ label: t('admin.passport.assign'),
104
+ handler: () => setState({ action: 'assign' }),
105
+ color: 'primary',
106
+ disabled: !data.active,
107
+ },
96
108
  ]}
97
109
  />
98
110
  {state.action === 'rename' && (
@@ -103,6 +115,7 @@ export default function PaymentLinkActions({ data, variant, onChange }: Props) {
103
115
  onCancel={() => setState({ action: '' })}
104
116
  />
105
117
  )}
118
+ {state.action === 'assign' && <AssignPassportDialog id={data.id} onCancel={() => setState({ action: '' })} />}
106
119
  {state.action === 'archive' && (
107
120
  <ConfirmDialog
108
121
  onConfirm={onArchive}
@@ -7,6 +7,7 @@ export default function AfterPay() {
7
7
  const { t } = useLocaleContext();
8
8
  const { control, setValue, getValues } = useFormContext();
9
9
  const type = useWatch({ control, name: 'after_completion.type' });
10
+ const nftMintEnabled = useWatch({ control, name: 'nft_mint_settings.enabled' });
10
11
 
11
12
  return (
12
13
  <Stack spacing={2} sx={{ width: '100%' }}>
@@ -34,7 +35,13 @@ export default function AfterPay() {
34
35
  name="after_completion.hosted_confirmation.custom_message"
35
36
  control={control}
36
37
  render={({ field }) => (
37
- <TextField {...field} placeholder="Replace default success message" fullWidth size="small" />
38
+ <TextField
39
+ {...field}
40
+ placeholder={t('admin.paymentLink.customMessage')}
41
+ helperText={t('admin.paymentLink.customMessageTip')}
42
+ fullWidth
43
+ size="small"
44
+ />
38
45
  )}
39
46
  />
40
47
  )}
@@ -82,6 +89,31 @@ export default function AfterPay() {
82
89
  />
83
90
  )}
84
91
  />
92
+ <Controller
93
+ name="nft_mint_settings.enabled"
94
+ control={control}
95
+ render={({ field }) => (
96
+ <FormControlLabel
97
+ control={
98
+ <Checkbox
99
+ checked={getValues().nft_mint_settings.enabled}
100
+ {...field}
101
+ onChange={(_, checked) => setValue(field.name, checked)}
102
+ />
103
+ }
104
+ label={t('admin.paymentLink.mintNft')}
105
+ />
106
+ )}
107
+ />
108
+ {nftMintEnabled && (
109
+ <Controller
110
+ name="nft_mint_settings.factory"
111
+ control={control}
112
+ render={({ field }) => (
113
+ <TextField {...field} placeholder={t('admin.paymentLink.mintNftFrom')} fullWidth size="small" />
114
+ )}
115
+ />
116
+ )}
85
117
  </Stack>
86
118
  );
87
119
  }
@@ -14,14 +14,11 @@ export default function PaymentLinkPreview({ id, version }: { id: string; versio
14
14
  const ref = useRef(null);
15
15
  const size = useSize(ref);
16
16
  return (
17
- <Chrome>
18
- <Box ref={ref} sx={{ width: '100%' }}>
19
- &nbsp;
20
- </Box>
17
+ <Chrome sx={{ width: '100%', maxWidth: 840 }}>
18
+ <Box ref={ref}>&nbsp;</Box>
21
19
  <IframeResizer
22
20
  style={{
23
- // @ts-ignore
24
- minWidth: size?.width / 0.8,
21
+ minWidth: size ? size.width / 0.8 : 1050,
25
22
  minHeight: '64vh',
26
23
  transform: 'scale(0.8)',
27
24
  transformOrigin: 'top left',
@@ -78,7 +78,6 @@ const hasMoreCurrency = (methods: TPaymentMethodExpanded[]) => {
78
78
  return methods.every((method) => method.payment_currencies.length > 1) || methods.length > 1;
79
79
  };
80
80
 
81
- // FIXME: @wangshijun i18n
82
81
  export default function PriceForm({ prefix, simple }: PriceFormProps) {
83
82
  const getFieldName = (name: string) => (prefix ? `${prefix}.${name}` : name);
84
83
 
@@ -106,16 +105,16 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
106
105
  <Box sx={{ width: INPUT_WIDTH }}>
107
106
  <FormLabel>{t('admin.price.model')}</FormLabel>
108
107
  <Select {...field} fullWidth size="small">
109
- <MenuItem value="standard">Standard Pricing</MenuItem>
110
- <MenuItem value="package">Package Pricing</MenuItem>
108
+ <MenuItem value="standard">{t('admin.price.models.standard')}</MenuItem>
109
+ <MenuItem value="package">{t('admin.price.models.package')}</MenuItem>
111
110
  <MenuItem value="graduated" disabled>
112
- Graduated Pricing
111
+ {t('admin.price.models.graduated')}
113
112
  </MenuItem>
114
113
  <MenuItem value="volume" disabled>
115
- Volume Pricing
114
+ {t('admin.price.models.volume')}
116
115
  </MenuItem>
117
116
  <MenuItem value="custom" disabled>
118
- Customer choose price
117
+ {t('admin.price.models.custom')}
119
118
  </MenuItem>
120
119
  </Select>
121
120
  </Box>
@@ -235,8 +234,8 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
235
234
  disabled={isLocked}
236
235
  render={({ field }) => (
237
236
  <ToggleButtonGroup {...field} onChange={(_, value: string) => setValue(field.name, value)} exclusive>
238
- <ToggleButton value="recurring">Recurring</ToggleButton>
239
- <ToggleButton value="one_time">One Time</ToggleButton>
237
+ <ToggleButton value="recurring">{t('admin.price.types.recurring')}</ToggleButton>
238
+ <ToggleButton value="one_time">{t('admin.price.types.onetime')}</ToggleButton>
240
239
  </ToggleButtonGroup>
241
240
  )}
242
241
  />
@@ -259,14 +258,14 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
259
258
  }}
260
259
  sx={{ width: INPUT_WIDTH }}
261
260
  size="small">
262
- {!livemode && <MenuItem value="hour_1">Hourly</MenuItem>}
263
- <MenuItem value="day_1">Daily</MenuItem>
264
- <MenuItem value="week_1">Weekly</MenuItem>
265
- <MenuItem value="month_1">Monthly</MenuItem>
266
- <MenuItem value="month_3">Every 3 months</MenuItem>
267
- <MenuItem value="month_6">Every 6 months</MenuItem>
268
- <MenuItem value="year_1">Yearly</MenuItem>
269
- <MenuItem value="month_2">Custom</MenuItem>
261
+ {!livemode && <MenuItem value="hour_1">{t('common.hourly')}</MenuItem>}
262
+ <MenuItem value="day_1">{t('common.daily')}</MenuItem>
263
+ <MenuItem value="week_1">{t('common.weekly')}</MenuItem>
264
+ <MenuItem value="month_1">{t('common.monthly')}</MenuItem>
265
+ <MenuItem value="month_3">{t('common.month3')}</MenuItem>
266
+ <MenuItem value="month_6">{t('common.month6')}</MenuItem>
267
+ <MenuItem value="year_1">{t('common.yearly')}</MenuItem>
268
+ <MenuItem value="month_2">{t('common.custom')}</MenuItem>
270
269
  </Select>
271
270
  </Box>
272
271
  )}
@@ -289,9 +288,9 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
289
288
  endAdornment: (
290
289
  <InputAdornment position="end">
291
290
  <select onChange={(e) => setValue(getFieldName('recurring.interval'), e.target.value)}>
292
- <option value="day">days</option>
293
- <option value="week">weeks</option>
294
- <option value="month">months</option>
291
+ <option value="day">{t('common.days')}</option>
292
+ <option value="week">{t('common.weeks')}</option>
293
+ <option value="month">{t('common.months')}</option>
295
294
  </select>
296
295
  </InputAdornment>
297
296
  ),
@@ -339,10 +338,10 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
339
338
  <Box>
340
339
  <FormLabel>{t('admin.price.recurring.aggregate')}</FormLabel>
341
340
  <Select {...field} sx={{ width: INPUT_WIDTH }} size="small">
342
- <MenuItem value="sum">Sum of usage values during period</MenuItem>
343
- <MenuItem value="max">Maximum usage value during period</MenuItem>
344
- <MenuItem value="last_ever">Most recent usage value</MenuItem>
345
- <MenuItem value="last_during_period">Most recent usage value during period</MenuItem>
341
+ <MenuItem value="sum">{t('admin.price.aggregate.sum')}</MenuItem>
342
+ <MenuItem value="max">{t('admin.price.aggregate.max')}</MenuItem>
343
+ <MenuItem value="last_ever">{t('admin.price.aggregate.last_ever')}</MenuItem>
344
+ <MenuItem value="last_during_period">{t('admin.price.aggregate.last_during_period')}</MenuItem>
346
345
  </Select>
347
346
  </Box>
348
347
  )}
@@ -11,6 +11,7 @@ import { formatError } from '../../libs/util';
11
11
  import Actions from '../actions';
12
12
  import ClickBoundary from '../click-boundary';
13
13
  import ConfirmDialog from '../confirm';
14
+ import AssignPassportDialog from '../passport/assign';
14
15
  import RenamePricingTable from './rename';
15
16
 
16
17
  type Props = {
@@ -92,7 +93,18 @@ export default function PricingTableActions({ data, variant, onChange }: Props)
92
93
  },
93
94
  { label: t('admin.pricingTable.rename'), handler: () => setState({ action: 'rename' }), color: 'primary' },
94
95
  { label: t('admin.pricingTable.archive'), handler: () => setState({ action: 'archive' }), color: 'primary' },
95
- { label: t('admin.pricingTable.remove'), handler: () => setState({ action: 'remove' }), color: 'error' },
96
+ {
97
+ label: t('admin.pricingTable.remove'),
98
+ handler: () => setState({ action: 'remove' }),
99
+ color: 'error',
100
+ divider: true,
101
+ },
102
+ {
103
+ label: t('admin.passport.assign'),
104
+ handler: () => setState({ action: 'assign' }),
105
+ color: 'primary',
106
+ disabled: !data.active,
107
+ },
96
108
  ]}
97
109
  />
98
110
  {state.action === 'rename' && (
@@ -103,6 +115,7 @@ export default function PricingTableActions({ data, variant, onChange }: Props)
103
115
  onCancel={() => setState({ action: '' })}
104
116
  />
105
117
  )}
118
+ {state.action === 'assign' && <AssignPassportDialog id={data.id} onCancel={() => setState({ action: '' })} />}
106
119
  {state.action === 'archive' && (
107
120
  <ConfirmDialog
108
121
  onConfirm={onArchive}
@@ -17,6 +17,7 @@ export function PricePaymentSettings({ index }: { index: number }) {
17
17
  const { t } = useLocaleContext();
18
18
  const { control, setValue, getValues } = useFormContext();
19
19
  const type = useWatch({ control, name: getFieldName('after_completion.type') });
20
+ const nftMintEnabled = useWatch({ control, name: 'nft_mint_settings.enabled' });
20
21
 
21
22
  const values = getValues();
22
23
 
@@ -91,7 +92,13 @@ export function PricePaymentSettings({ index }: { index: number }) {
91
92
  name={getFieldName('after_completion.hosted_confirmation.custom_message')}
92
93
  control={control}
93
94
  render={({ field }) => (
94
- <TextField {...field} placeholder="Replace default success message" fullWidth size="small" />
95
+ <TextField
96
+ {...field}
97
+ placeholder={t('admin.paymentLink.customMessage')}
98
+ helperText={t('admin.paymentLink.customMessageTip')}
99
+ fullWidth
100
+ size="small"
101
+ />
95
102
  )}
96
103
  />
97
104
  )}
@@ -120,6 +127,31 @@ export function PricePaymentSettings({ index }: { index: number }) {
120
127
  )}
121
128
  />
122
129
  )}
130
+ <Controller
131
+ name={getFieldName('nft_mint_settings.enabled')}
132
+ control={control}
133
+ render={({ field }) => (
134
+ <FormControlLabel
135
+ control={
136
+ <Checkbox
137
+ checked={get(values, 'nft_mint_settings.enabled')}
138
+ {...field}
139
+ onChange={(_, checked) => setValue(field.name, checked)}
140
+ />
141
+ }
142
+ label={t('admin.paymentLink.mintNft')}
143
+ />
144
+ )}
145
+ />
146
+ {nftMintEnabled && (
147
+ <Controller
148
+ name={getFieldName('nft_mint_settings.factory')}
149
+ control={control}
150
+ render={({ field }) => (
151
+ <TextField {...field} placeholder={t('admin.paymentLink.mintNftFrom')} fullWidth size="small" />
152
+ )}
153
+ />
154
+ )}
123
155
  </Stack>
124
156
  );
125
157
  }
@@ -13,15 +13,11 @@ export default function PricingTablePreview({ id, version }: { id: string; versi
13
13
  const ref = useRef(null);
14
14
  const size = useSize(ref);
15
15
  return (
16
- <Chrome>
17
- <Box ref={ref} sx={{ width: '100%' }}>
18
- &nbsp;
19
- </Box>
16
+ <Chrome sx={{ width: '100%', maxWidth: 840 }}>
17
+ <Box ref={ref}>&nbsp;</Box>
20
18
  <IframeResizer
21
19
  style={{
22
- // @ts-ignore
23
- // eslint-disable-next-line no-unsafe-optional-chaining
24
- minWidth: size?.width / 0.8,
20
+ minWidth: size ? size.width / 0.8 : 1050,
25
21
  minHeight: '64vh',
26
22
  transform: 'scale(0.8)',
27
23
  transformOrigin: 'top left',
@@ -68,6 +68,10 @@ export default function PricingTableProductSettings() {
68
68
  description: '',
69
69
  trial_period_days: 0,
70
70
  },
71
+ nft_mint_settings: {
72
+ enabled: false,
73
+ factory: '',
74
+ },
71
75
  custom_fields: [],
72
76
  submit_type: 'auto',
73
77
  });
@@ -0,0 +1,39 @@
1
+ import { Fade, Skeleton, Stack, Typography } from '@mui/material';
2
+
3
+ export default function ProductSkeleton({ count }: { count: number }) {
4
+ return (
5
+ <Fade in>
6
+ <Stack
7
+ direction="column"
8
+ alignItems="center"
9
+ padding={4}
10
+ spacing={1}
11
+ sx={{
12
+ width: 320,
13
+ border: '1px solid #eee',
14
+ borderRadius: 1,
15
+ transition: 'border-color 0.3s ease 0s, box-shadow 0.3s ease 0s',
16
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 20%)',
17
+ '&:hover': {
18
+ borderColor: '#ddd',
19
+ boxShadow: '0 8px 16px rgba(0, 0, 0, 20%)',
20
+ },
21
+ }}>
22
+ <Typography component="div" variant="h4" sx={{ width: '50%' }}>
23
+ <Skeleton />
24
+ </Typography>
25
+ <Skeleton variant="text" sx={{ fontSize: '1rem', width: '60%' }} />
26
+ <Typography component="div" variant="h3" sx={{ width: '60%' }}>
27
+ <Skeleton />
28
+ </Typography>
29
+ <Typography component="div" variant="h3" sx={{ width: '100%' }}>
30
+ <Skeleton />
31
+ </Typography>
32
+ {Array.from({ length: count }).map((_, i) => (
33
+ // eslint-disable-next-line react/no-array-index-key
34
+ <Skeleton key={i} variant="text" sx={{ fontSize: '1rem', width: '60%' }} />
35
+ ))}
36
+ </Stack>
37
+ </Fade>
38
+ );
39
+ }
@@ -9,6 +9,7 @@ import { formatError } from '../../libs/util';
9
9
  import Actions from '../actions';
10
10
  import ClickBoundary from '../click-boundary';
11
11
  import ConfirmDialog from '../confirm';
12
+ import AssignPassportDialog from '../passport/assign';
12
13
  import EditProduct from './edit';
13
14
 
14
15
  type ProductActionProps = {
@@ -83,7 +84,18 @@ export default function ProductActions({ data, variant, onChange }: ProductActio
83
84
  data.active
84
85
  ? { label: t('admin.product.archive'), handler: () => setState({ action: 'archive' }), color: 'primary' }
85
86
  : { label: t('admin.product.unarchive'), handler: () => setState({ action: 'unarchive' }), color: 'primary' }, // prettier-ignore
86
- { label: t('admin.product.remove'), handler: () => setState({ action: 'remove' }), color: 'error' },
87
+ {
88
+ label: t('admin.product.remove'),
89
+ handler: () => setState({ action: 'remove' }),
90
+ color: 'error',
91
+ divider: true,
92
+ },
93
+ {
94
+ label: t('admin.passport.assign'),
95
+ handler: () => setState({ action: 'assign' }),
96
+ color: 'primary',
97
+ disabled: !data.active,
98
+ },
87
99
  ].filter(Boolean)}
88
100
  />
89
101
  {state.action === 'edit' && (
@@ -121,6 +133,7 @@ export default function ProductActions({ data, variant, onChange }: ProductActio
121
133
  loading={state.loading}
122
134
  />
123
135
  )}
136
+ {state.action === 'assign' && <AssignPassportDialog id={data.id} onCancel={() => setState({ action: '' })} />}
124
137
  </ClickBoundary>
125
138
  );
126
139
  }
@@ -6,7 +6,7 @@ export default function Status(props: ChipProps) {
6
6
  {...props}
7
7
  size="small"
8
8
  variant="outlined"
9
- sx={{ ...(props.sx || {}), borderRadius: '4px', textTransform: 'capitalize' }}
9
+ sx={{ ...(props.sx || {}), borderRadius: '4px', height: 20, lineHeight: 20, textTransform: 'capitalize' }}
10
10
  />
11
11
  );
12
12
  }
@@ -13,7 +13,7 @@ export default function SubscriptionStatus({
13
13
  [key: string]: any;
14
14
  }) {
15
15
  const { t } = useLocaleContext();
16
- if (subscription.cancel_at_period_end) {
16
+ if (subscription.cancel_at_period_end && subscription.current_period_end > Date.now() / 1000) {
17
17
  return (
18
18
  <Status
19
19
  icon={<AccessTimeOutlined />}
@@ -24,7 +24,7 @@ export default function SubscriptionStatus({
24
24
  );
25
25
  }
26
26
 
27
- if (subscription.cancel_at) {
27
+ if (subscription.cancel_at && subscription.cancel_at >= Date.now() / 1000) {
28
28
  return (
29
29
  <Status
30
30
  icon={<AccessTimeOutlined />}
@@ -36,7 +36,7 @@ export default function SubscriptionStatus({
36
36
  }
37
37
 
38
38
  if (subscription.pause_collection) {
39
- if (subscription.pause_collection.resumes_at) {
39
+ if (subscription.pause_collection.resumes_at && subscription.pause_collection.resumes_at > Date.now() / 1000) {
40
40
  return (
41
41
  <Status
42
42
  icon={<AccessTimeOutlined />}
@@ -8,16 +8,16 @@ function EmptyStub() {
8
8
  return null;
9
9
  }
10
10
 
11
- export default function Table({ options, toolbar = true, footer = true, ...rest }: any) {
11
+ export default function Table({ options, columns, toolbar = true, footer = true, ...rest }: any) {
12
12
  const { isMobile } = useMobile();
13
13
  const { locale } = useLocaleContext();
14
14
  const defaultOptions = {
15
15
  print: false,
16
16
  download: false,
17
- filter: false,
17
+ filter: true,
18
18
  selectableRows: 'none',
19
- rowsPerPage: 20,
20
- rowsPerPageOptions: [20, 50, 100],
19
+ rowsPerPage: 10,
20
+ rowsPerPageOptions: [10, 20, 50, 100],
21
21
  searchDebounceTime: 300,
22
22
  tableBodyHeight: '100%',
23
23
  };
@@ -34,6 +34,12 @@ export default function Table({ options, toolbar = true, footer = true, ...rest
34
34
  <Wrapped
35
35
  locale={locale}
36
36
  options={{ ...defaultOptions, ...options }}
37
+ columns={columns.map((x: any) => {
38
+ x.options = x.options || {};
39
+ x.options.filter = x.options.filter || false;
40
+ x.options.sort = x.options.sort || false;
41
+ return x;
42
+ })}
37
43
  {...rest}
38
44
  components={components}
39
45
  isMobile={isMobile}
@@ -54,6 +60,10 @@ const Wrapped = styled(Datatable)`
54
60
  background: #f5f5f5;
55
61
  }
56
62
 
63
+ > div {
64
+ overflow: visible;
65
+ }
66
+
57
67
  ${(props) => {
58
68
  if (props.isMobile) {
59
69
  return '';
package/src/global.css CHANGED
@@ -62,11 +62,17 @@ a:visited {
62
62
 
63
63
  .MuiTableCell-root {
64
64
  font-size: 1rem !important;
65
+ padding-top: 8px;
66
+ padding-bottom: 8px;
65
67
  padding-right: 24px;
66
68
  }
67
69
 
68
70
  th.MuiTableCell-head {
69
- padding: 8px 0;
71
+ padding: 8px 24px 8px 0;
72
+ }
73
+
74
+ .MuiTableRow-hover {
75
+ cursor: pointer;
70
76
  }
71
77
 
72
78
  .MuiRadio-root {
@@ -91,7 +97,3 @@ th.MuiTableCell-head {
91
97
  max-height: 100% !important;
92
98
  overflow: auto !important;
93
99
  }
94
-
95
- .MuiTableRow-hover {
96
- cursor: pointer;
97
- }
package/src/libs/util.ts CHANGED
@@ -631,3 +631,9 @@ export function groupPricingTableItems(items: any[]) {
631
631
 
632
632
  return Object.entries(grouped);
633
633
  }
634
+
635
+ export function sleep(ms: number) {
636
+ return new Promise((resolve) => {
637
+ setTimeout(resolve, ms);
638
+ });
639
+ }