payment-kit 1.13.161 → 1.13.162

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.
@@ -25,49 +25,68 @@ const filterProducts = (products: TProductExpanded[], hasSelected: (price: any)
25
25
  return filtered.filter((x) => x.prices.length);
26
26
  };
27
27
 
28
- export default function ProductSelect({ mode: initialMode, hasSelected, onSelect }: Props) {
29
- const { t } = useLocaleContext();
28
+ export function FilterProducts({ hasSelected, onSelect }: any) {
29
+ const size = { width: 16, height: 16 };
30
30
  const { products } = useProductsContext();
31
31
  const { settings } = usePaymentContext();
32
- const [mode, setMode] = useState(initialMode);
33
32
 
33
+ return (
34
+ <>
35
+ {filterProducts(products, hasSelected).map((product) => [
36
+ <ListSubheader key={product.id} sx={{ fontSize: '1rem', color: 'text.secondary', lineHeight: '2.5rem' }}>
37
+ <Stack direction="row" alignItems="center" spacing={0.5}>
38
+ {product.images[0] ? (
39
+ <Avatar src={product.images[0]} alt={product.name} variant="square" sx={size} />
40
+ ) : (
41
+ <Avatar variant="square" sx={size}>
42
+ {product.name.slice(0, 1)}
43
+ </Avatar>
44
+ )}
45
+ <Typography component="span">{product.name}</Typography>
46
+ </Stack>
47
+ </ListSubheader>,
48
+ ...product.prices.map((price) => (
49
+ <MenuItem
50
+ key={price.id}
51
+ sx={{ pl: 4.5 }}
52
+ value={price.id}
53
+ onClick={() => {
54
+ if (onSelect) {
55
+ const wrapPrice: any = price;
56
+ wrapPrice.productName = product.name;
57
+ wrapPrice.displayPrice = formatPrice(price, settings.baseCurrency);
58
+ onSelect(wrapPrice);
59
+ }
60
+ }}>
61
+ <Typography color="text.primary">{formatPrice(price, settings.baseCurrency)}</Typography>
62
+ <Typography color="text.secondary" sx={{ ml: 2 }}>
63
+ {getPriceCurrencyOptions(price).length > 1
64
+ ? ` +${getPriceCurrencyOptions(price).length - 1} more currencies`
65
+ : ''}
66
+ </Typography>
67
+ </MenuItem>
68
+ )),
69
+ ])}
70
+ </>
71
+ );
72
+ }
73
+
74
+ export default function ProductSelect({ mode: initialMode, hasSelected, onSelect }: Props) {
75
+ const { t } = useLocaleContext();
76
+ const [mode, setMode] = useState(initialMode);
34
77
  const handleSelect = (e: any) => {
35
78
  setMode('waiting');
36
79
  onSelect(e.target.value);
37
80
  };
38
81
 
39
82
  if (mode === 'selecting') {
40
- const size = { width: 16, height: 16 };
41
83
  return (
42
84
  <Select value="" fullWidth size="small" onChange={handleSelect} MenuProps={{ style: { maxHeight: 480 } }}>
43
85
  <MenuItem value="add">
44
86
  <AddOutlined />
45
87
  {t('admin.product.add')}
46
88
  </MenuItem>
47
- {filterProducts(products, hasSelected).map((product) => [
48
- <ListSubheader key={product.id} sx={{ fontSize: '1rem', color: 'text.secondary', lineHeight: '2.5rem' }}>
49
- <Stack direction="row" alignItems="center" spacing={0.5}>
50
- {product.images[0] ? (
51
- <Avatar src={product.images[0]} alt={product.name} variant="square" sx={size} />
52
- ) : (
53
- <Avatar variant="square" sx={size}>
54
- {product.name.slice(0, 1)}
55
- </Avatar>
56
- )}
57
- <Typography component="span">{product.name}</Typography>
58
- </Stack>
59
- </ListSubheader>,
60
- ...product.prices.map((price) => (
61
- <MenuItem key={price.id} sx={{ pl: 4.5 }} value={price.id}>
62
- <Typography color="text.primary">{formatPrice(price, settings.baseCurrency)}</Typography>
63
- <Typography color="text.secondary" sx={{ ml: 2 }}>
64
- {getPriceCurrencyOptions(price).length > 1
65
- ? ` +${getPriceCurrencyOptions(price).length - 1} more currencies`
66
- : ''}
67
- </Typography>
68
- </MenuItem>
69
- )),
70
- ])}
89
+ <FilterProducts hasSelected={hasSelected} />
71
90
  </Select>
72
91
  );
73
92
  }
@@ -3,31 +3,39 @@ import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
4
  import { Status, api, formatTime, getPaymentIntentStatusColor } from '@blocklet/payment-react';
5
5
  import type { TRefundExpanded } from '@blocklet/payment-types';
6
- import { Alert, CircularProgress, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
6
+ import { CircularProgress, Typography } from '@mui/material';
7
7
  import { fromUnitToToken } from '@ocap/util';
8
- import { useRequest } from 'ahooks';
9
8
  import { useEffect, useState } from 'react';
10
9
  import { useNavigate } from 'react-router-dom';
11
10
 
12
11
  import CustomerLink from '../customer/link';
12
+ import FilterTooolbar from '../filter-toolbar';
13
13
  import Table from '../table';
14
14
  import RefundActions from './actions';
15
15
 
16
16
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TRefundExpanded[]; count: number }> => {
17
17
  const search = new URLSearchParams();
18
18
  Object.keys(params).forEach((key) => {
19
- search.set(key, String(params[key]));
19
+ let v = params[key];
20
+ if (key === 'q') {
21
+ v = Object.entries(v)
22
+ .map((x) => x.join(':'))
23
+ .join(' ');
24
+ }
25
+ search.set(key, String(v));
20
26
  });
21
27
  return api.get(`/api/refunds?${search.toString()}`).then((res) => res.data);
22
28
  };
23
29
 
24
30
  type SearchProps = {
25
- status: string;
31
+ status?: string;
26
32
  pageSize: number;
27
33
  page: number;
28
34
  customer_id?: string;
29
35
  invoice_id?: string;
30
36
  subscription_id?: string;
37
+ q?: Record<string, string>;
38
+ o?: string;
31
39
  };
32
40
 
33
41
  type ListProps = {
@@ -78,16 +86,18 @@ export default function RefundList({ customer_id, invoice_id, subscription_id, f
78
86
  page: persisted.page ? persisted.page + 1 : 1,
79
87
  });
80
88
 
81
- const { loading, error, data, refresh } = useRequest(() => fetchData(search));
89
+ const [data, setData] = useState({}) as any;
90
+
91
+ const refresh = () =>
92
+ fetchData(search).then((res: any) => {
93
+ setData(res);
94
+ });
95
+
82
96
  useEffect(() => {
83
97
  refresh();
84
- }, [search, refresh]);
98
+ }, [search]);
85
99
 
86
- if (error) {
87
- return <Alert severity="error">{error.message}</Alert>;
88
- }
89
-
90
- if (loading || !data) {
100
+ if (!data.list) {
91
101
  return <CircularProgress />;
92
102
  }
93
103
 
@@ -135,6 +145,7 @@ export default function RefundList({ customer_id, invoice_id, subscription_id, f
135
145
  label: t('common.createdAt'),
136
146
  name: 'created_at',
137
147
  options: {
148
+ sort: true,
138
149
  customBodyRender: (e: string) => {
139
150
  return formatTime(e);
140
151
  },
@@ -179,7 +190,7 @@ export default function RefundList({ customer_id, invoice_id, subscription_id, f
179
190
  durableKeys={['page', 'rowsPerPage']}
180
191
  data={data.list}
181
192
  columns={columns}
182
- loading={loading}
193
+ loading={!data.list}
183
194
  onChange={onTableChange}
184
195
  options={{
185
196
  count: data.count,
@@ -189,23 +200,38 @@ export default function RefundList({ customer_id, invoice_id, subscription_id, f
189
200
  const item = data.list[dataIndex] as TRefundExpanded;
190
201
  navigate(`/admin/payments/${item.id}`);
191
202
  },
203
+ onColumnSortChange(_: any, order: any) {
204
+ setSearch({
205
+ ...search,
206
+ q: search.q || {},
207
+ o: order,
208
+ });
209
+ },
210
+ onSearchChange: (text: string) => {
211
+ if (text) {
212
+ setSearch({
213
+ q: {
214
+ 'like-description': text,
215
+ 'like-metadata': text,
216
+ },
217
+ status: '',
218
+ pageSize: 100,
219
+ page: 1,
220
+ });
221
+ } else {
222
+ setSearch({
223
+ status: '',
224
+ customer_id,
225
+ invoice_id,
226
+ pageSize: 100,
227
+ page: 1,
228
+ });
229
+ }
230
+ },
192
231
  }}
193
232
  toolbar={features?.toolbar}
194
233
  footer={features?.footer}
195
- title={
196
- <div className="table-toolbar-left">
197
- <ToggleButtonGroup
198
- value={search.status}
199
- onChange={(_, value) => setSearch((x) => ({ ...x, status: value }))}
200
- exclusive>
201
- <ToggleButton value="">All</ToggleButton>
202
- <ToggleButton value="succeeded">Succeeded</ToggleButton>
203
- <ToggleButton value="canceled">Canceled</ToggleButton>
204
- <ToggleButton value="pending,requires_action">Pending</ToggleButton>
205
- <ToggleButton value="failed">Failed</ToggleButton>
206
- </ToggleButtonGroup>
207
- </div>
208
- }
234
+ title={<FilterTooolbar setSearch={setSearch} search={search} status={['successed']} currency />}
209
235
  />
210
236
  );
211
237
  }
@@ -46,6 +46,7 @@ export function UsageRecordDialog(props: { subscriptionId: string; id: string; o
46
46
  label: t('common.createdAt'),
47
47
  name: 'id',
48
48
  options: {
49
+ sort: true,
49
50
  customBodyRenderLite: (_: string, index: number) => {
50
51
  const item = data.list[index] as TUsageRecord;
51
52
  return formatToDatetime(item.timestamp * 1000);
@@ -3,12 +3,12 @@ import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
4
  import { Status, api, formatTime } from '@blocklet/payment-react';
5
5
  import type { TSubscriptionExpanded } from '@blocklet/payment-types';
6
- import { CircularProgress, ToggleButton, ToggleButtonGroup } from '@mui/material';
6
+ import { CircularProgress } from '@mui/material';
7
7
  import { useEffect, useState } from 'react';
8
8
  import { useNavigate } from 'react-router-dom';
9
9
 
10
- import { debounce } from '../../libs/util';
11
10
  import CustomerLink from '../customer/link';
11
+ import FilterTooolbar from '../filter-toolbar';
12
12
  import Table from '../table';
13
13
  import SubscriptionActions from './actions';
14
14
  import SubscriptionDescription from './description';
@@ -19,15 +19,13 @@ const fetchData = (params: Record<string, any> = {}): Promise<{ list: TSubscript
19
19
  Object.keys(params).forEach((key) => {
20
20
  let v = params[key];
21
21
  if (key === 'q') {
22
- v = Object.entries(params[key])
22
+ v = Object.entries(v)
23
23
  .map((x) => x.join(':'))
24
24
  .join(' ');
25
25
  }
26
26
  search.set(key, String(v));
27
27
  });
28
- return params.q
29
- ? api.get(`api/subscriptions/search?${search.toString()}`).then((res) => res.data)
30
- : api.get(`/api/subscriptions?${search.toString()}`).then((res) => res.data);
28
+ return api.get(`/api/subscriptions?${search.toString()}`).then((res) => res.data);
31
29
  };
32
30
 
33
31
  type SearchProps = {
@@ -36,6 +34,7 @@ type SearchProps = {
36
34
  page: number;
37
35
  customer_id?: string;
38
36
  q?: any;
37
+ o?: string;
39
38
  };
40
39
 
41
40
  type ListProps = {
@@ -86,7 +85,7 @@ export default function SubscriptionList({ customer_id, features, status }: List
86
85
  });
87
86
 
88
87
  useEffect(() => {
89
- debounce(refresh, 1000)();
88
+ refresh();
90
89
  }, [search]);
91
90
 
92
91
  if (!data.list) {
@@ -134,6 +133,7 @@ export default function SubscriptionList({ customer_id, features, status }: List
134
133
  label: t('common.createdAt'),
135
134
  name: 'created_at',
136
135
  options: {
136
+ sort: true,
137
137
  customBodyRender: (e: string) => {
138
138
  return formatTime(e);
139
139
  },
@@ -193,35 +193,19 @@ export default function SubscriptionList({ customer_id, features, status }: List
193
193
  const item = data.list[dataIndex] as TSubscriptionExpanded;
194
194
  navigate(`/admin/billing/${item.id}`);
195
195
  },
196
- onFilterChange: (key: any, filterList: any, type: any, index: number) => {
197
- if (type === 'reset') {
198
- setSearch({
199
- status: '',
200
- customer_id,
201
- pageSize: 100,
202
- page: 1,
203
- });
204
- } else if (filterList && filterList[index]) {
205
- // 这里暂时没有更好的取值方式
206
- const value = filterList[index][0];
207
- setSearch({
208
- q: {
209
- ...search.q,
210
- [key]: encodeURIComponent(value),
211
- },
212
- pageSize: 100,
213
- page: 1,
214
- });
215
- }
196
+ onColumnSortChange(_: any, order: any) {
197
+ setSearch({
198
+ ...search,
199
+ q: search.q || {},
200
+ o: order,
201
+ });
216
202
  },
217
203
  onSearchChange: (text: string) => {
218
204
  if (text) {
219
205
  setSearch({
220
206
  q: {
221
207
  'like-metadata': text,
222
- 'like-did': text,
223
- 'like-name': text,
224
- // 'like-product-name': text, // 这个还没实现
208
+ 'like-description': text,
225
209
  },
226
210
  pageSize: 100,
227
211
  page: 1,
@@ -238,20 +222,7 @@ export default function SubscriptionList({ customer_id, features, status }: List
238
222
  }}
239
223
  toolbar={features?.toolbar}
240
224
  footer={features?.footer}
241
- title={
242
- <div className="table-toolbar-left">
243
- <ToggleButtonGroup
244
- value={search.status}
245
- onChange={(_, value) => setSearch((x) => ({ ...x, status: value }))}
246
- exclusive>
247
- <ToggleButton value="active">Active</ToggleButton>
248
- <ToggleButton value="trialing">Scheduled</ToggleButton>
249
- <ToggleButton value="canceled">Canceled</ToggleButton>
250
- <ToggleButton value="incomplete,incomplete_expired">Incomplete</ToggleButton>
251
- <ToggleButton value="past_due,unpaid">Due</ToggleButton>
252
- </ToggleButtonGroup>
253
- </div>
254
- }
225
+ title={<FilterTooolbar setSearch={setSearch} search={search} status={['active', 'canceled']} />}
255
226
  />
256
227
  );
257
228
  }
@@ -14,12 +14,13 @@ export default function Table({ options, columns, toolbar = true, footer = true,
14
14
  const defaultOptions = {
15
15
  print: false,
16
16
  download: false,
17
- filter: true,
17
+ filter: false,
18
18
  selectableRows: 'none',
19
19
  rowsPerPage: 10,
20
20
  rowsPerPageOptions: [10, 20, 50, 100],
21
21
  searchDebounceTime: 300,
22
22
  tableBodyHeight: '100%',
23
+ loading: true,
23
24
  };
24
25
 
25
26
  const components: any = {};
@@ -30,6 +31,8 @@ export default function Table({ options, columns, toolbar = true, footer = true,
30
31
  components.TableFooter = EmptyStub;
31
32
  }
32
33
 
34
+ // components.TableHead = EmptyStub2
35
+
33
36
  return (
34
37
  <Wrapped
35
38
  locale={locale}
@@ -64,6 +67,15 @@ const Wrapped = styled(Datatable)`
64
67
  overflow: visible;
65
68
  }
66
69
 
70
+ .custom-toobar-title-inner span {
71
+ overflow: visible !important;
72
+ }
73
+
74
+ .custom-toobar-title-inner {
75
+ line-height: 30px !important;
76
+ padding: 10px 0;
77
+ }
78
+
67
79
  ${(props) => {
68
80
  if (props.isMobile) {
69
81
  return '';
package/src/global.css CHANGED
@@ -44,7 +44,7 @@ a:hover {
44
44
  .MuiFormLabel-root {
45
45
  color: rgba(0, 0, 0, 0.8);
46
46
  display: block;
47
- margin-bottom: 8px;
47
+ margin-bottom: 4px;
48
48
  font-weight: 500;
49
49
  }
50
50
 
@@ -9,22 +9,19 @@ import { useEffect, useState } from 'react';
9
9
  import { useNavigate } from 'react-router-dom';
10
10
 
11
11
  import Table from '../../../../components/table';
12
- import { debounce } from '../../../../libs/util';
13
12
 
14
13
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TCustomer[]; count: number }> => {
15
14
  const search = new URLSearchParams();
16
15
  Object.keys(params).forEach((key) => {
17
16
  let v = params[key];
18
17
  if (key === 'q') {
19
- v = Object.entries(params[key])
18
+ v = Object.entries(v)
20
19
  .map((x) => x.join(':'))
21
20
  .join(' ');
22
21
  }
23
22
  search.set(key, String(v));
24
23
  });
25
- return params.q
26
- ? api.get(`api/customers/search?${search.toString()}`).then((res) => res.data)
27
- : api.get(`/api/customers?${search.toString()}`).then((res) => res.data);
24
+ return api.get(`/api/customers?${search.toString()}`).then((res: any) => res.data);
28
25
  };
29
26
 
30
27
  export default function CustomersList() {
@@ -33,8 +30,9 @@ export default function CustomersList() {
33
30
 
34
31
  const { t } = useLocaleContext();
35
32
  const navigate = useNavigate();
36
- const [search, setSearch] = useState<{ active?: string; pageSize: number; page: number; q?: any }>({
33
+ const [search, setSearch] = useState<{ active?: string; pageSize: number; page: number; q?: any; o?: string }>({
37
34
  active: '',
35
+ o: 'desc',
38
36
  pageSize: persisted.rowsPerPage || 20,
39
37
  page: persisted.page ? persisted.page + 1 : 1,
40
38
  });
@@ -47,7 +45,7 @@ export default function CustomersList() {
47
45
  });
48
46
 
49
47
  useEffect(() => {
50
- debounce(refresh, 1000)();
48
+ refresh();
51
49
  }, [search]);
52
50
 
53
51
  if (!data.list) {
@@ -92,6 +90,7 @@ export default function CustomersList() {
92
90
  label: t('common.createdAt'),
93
91
  name: 'created_at',
94
92
  options: {
93
+ sort: true,
95
94
  customBodyRender: (e: string) => {
96
95
  return formatTime(e);
97
96
  },
@@ -121,7 +120,13 @@ export default function CustomersList() {
121
120
  const item = data.list[dataIndex] as TCustomer;
122
121
  navigate(`/admin/customers/${item.id}`);
123
122
  },
124
-
123
+ onColumnSortChange(_: any, order: any) {
124
+ setSearch({
125
+ ...search,
126
+ q: search.q || {},
127
+ o: order,
128
+ });
129
+ },
125
130
  onSearchChange: (text: string) => {
126
131
  if (text) {
127
132
  setSearch({
@@ -129,6 +134,8 @@ export default function CustomersList() {
129
134
  'like-name': text,
130
135
  'like-email': text,
131
136
  'like-did': text,
137
+ 'like-metadata': text,
138
+ 'like-phone': text,
132
139
  },
133
140
  pageSize: 100,
134
141
  page: 1,
@@ -97,6 +97,7 @@ function PaymentLinks() {
97
97
  label: t('common.createdAt'),
98
98
  name: 'created_at',
99
99
  options: {
100
+ sort: true,
100
101
  customBodyRender: (e: string) => {
101
102
  return formatTime(e);
102
103
  },
@@ -56,6 +56,7 @@ export default function PricesList({ product, onChange }: { product: Product; on
56
56
  label: t('common.createdAt'),
57
57
  name: 'created_at',
58
58
  options: {
59
+ sort: true,
59
60
  customBodyRender: (e: string) => {
60
61
  return formatTime(e);
61
62
  },
@@ -84,6 +84,7 @@ function PricingTables() {
84
84
  label: t('common.createdAt'),
85
85
  name: 'created_at',
86
86
  options: {
87
+ sort: true,
87
88
  customBodyRender: (e: string) => {
88
89
  return formatTime(e);
89
90
  },
@@ -83,6 +83,7 @@ export default function ProductsList() {
83
83
  label: t('common.createdAt'),
84
84
  name: 'created_at',
85
85
  options: {
86
+ sort: true,
86
87
  customBodyRender: (e: string) => {
87
88
  return formatTime(e);
88
89
  },
@@ -1,13 +1,7 @@
1
1
  import DID from '@arcblock/ux/lib/Address';
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import Toast from '@arcblock/ux/lib/Toast';
4
- import {
5
- CustomerInvoiceList,
6
- CustomerPaymentList,
7
- PaymentProvider,
8
- formatError,
9
- getPrefix,
10
- } from '@blocklet/payment-react';
4
+ import { CustomerInvoiceList, PaymentProvider, formatError, getPrefix } from '@blocklet/payment-react';
11
5
  import type { GroupedBN, TCustomerExpanded } from '@blocklet/payment-types';
12
6
  import { Edit } from '@mui/icons-material';
13
7
  import { Alert, Box, Button, CircularProgress, Grid, Stack, Tooltip } from '@mui/material';
@@ -117,12 +111,6 @@ export default function CustomerHome() {
117
111
  <CustomerInvoiceList customer_id={data.id} />
118
112
  </Box>
119
113
  </Box>
120
- <Box className="section">
121
- <SectionHeader title={t('payment.customer.payments')} mb={0} />
122
- <Box className="section-body">
123
- <CustomerPaymentList customer_id={data.id} />
124
- </Box>
125
- </Box>
126
114
  </Root>
127
115
  </Grid>
128
116
  <Grid item xs={12} md={4}>