payment-kit 1.13.162 → 1.13.163

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.
@@ -2,7 +2,6 @@ import { fromTokenToUnit } from '@ocap/util';
2
2
  import { Router } from 'express';
3
3
  import Joi from 'joi';
4
4
  import pick from 'lodash/pick';
5
- import type { WhereOptions } from 'sequelize';
6
5
 
7
6
  import { getWhereFromKvQuery, getWhereFromQuery } from '../libs/api';
8
7
  import logger from '../libs/logger';
@@ -112,22 +111,33 @@ const paginationSchema = Joi.object<{
112
111
  pageSize: number;
113
112
  livemode?: boolean;
114
113
  active?: boolean;
114
+ status?: string;
115
115
  name?: string;
116
116
  description?: string;
117
+ q?: string;
118
+ o?: string;
117
119
  }>({
118
120
  page: Joi.number().integer().min(1).default(1),
119
121
  pageSize: Joi.number().integer().min(1).max(100).default(20),
120
122
  livemode: Joi.boolean().empty(''),
121
123
  active: Joi.boolean().empty(''),
124
+ status: Joi.string().empty(''),
122
125
  name: Joi.string().empty(''),
123
126
  description: Joi.string().empty(''),
127
+ q: Joi.string().empty(''), // query
128
+ o: Joi.string().empty(''), // order
124
129
  });
125
130
  router.get('/', auth, async (req, res) => {
126
131
  const { page, pageSize, active, livemode, name, description, ...query } = await paginationSchema.validateAsync(
127
132
  req.query,
128
133
  { stripUnknown: false, allowUnknown: true }
129
134
  );
130
- const where: WhereOptions<Product> = {};
135
+ const where = getWhereFromKvQuery(query.q);
136
+
137
+ if (query.status) {
138
+ // 兼容处理,支持 status
139
+ where.active = query.status === 'active';
140
+ }
131
141
 
132
142
  if (typeof active === 'boolean') {
133
143
  where.active = active;
@@ -151,7 +161,7 @@ router.get('/', auth, async (req, res) => {
151
161
 
152
162
  const { rows: list, count } = await Product.findAndCountAll({
153
163
  where,
154
- order: [['created_at', 'DESC']],
164
+ order: [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']],
155
165
  offset: (page - 1) * pageSize,
156
166
  limit: pageSize,
157
167
  include: [{ model: Price, as: 'prices' }],
package/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.13.162
17
+ version: 1.13.163
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.13.162",
3
+ "version": "1.13.163",
4
4
  "scripts": {
5
5
  "dev": "cross-env COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -50,7 +50,7 @@
50
50
  "@arcblock/jwt": "^1.18.110",
51
51
  "@arcblock/ux": "^2.9.39",
52
52
  "@blocklet/logger": "1.16.23",
53
- "@blocklet/payment-react": "1.13.162",
53
+ "@blocklet/payment-react": "1.13.163",
54
54
  "@blocklet/sdk": "1.16.23",
55
55
  "@blocklet/ui-react": "^2.9.39",
56
56
  "@blocklet/uploader": "^0.0.74",
@@ -110,7 +110,7 @@
110
110
  "devDependencies": {
111
111
  "@abtnode/types": "1.16.23",
112
112
  "@arcblock/eslint-config-ts": "^0.2.4",
113
- "@blocklet/payment-types": "1.13.162",
113
+ "@blocklet/payment-types": "1.13.163",
114
114
  "@types/cookie-parser": "^1.4.6",
115
115
  "@types/cors": "^2.8.17",
116
116
  "@types/dotenv-flow": "^3.3.3",
@@ -149,5 +149,5 @@
149
149
  "parser": "typescript"
150
150
  }
151
151
  },
152
- "gitHead": "e11cae8972d35cde567d4784511aacd33a29c1bc"
152
+ "gitHead": "2f485afbabe92bb6e63b09c4e69cb2ae06d3d2be"
153
153
  }
@@ -2,6 +2,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import { api, usePaymentContext } from '@blocklet/payment-react';
3
3
  import type { TCustomer } from '@blocklet/payment-types';
4
4
  import { Add, Close } from '@mui/icons-material';
5
+ import { ClickAwayListener, Menu, MenuItem } from '@mui/material';
5
6
  import { Box, styled } from '@mui/system';
6
7
  import { useEffect, useState } from 'react';
7
8
 
@@ -41,6 +42,7 @@ type Props = {
41
42
 
42
43
  export default function FilterTooolbar(props: Props) {
43
44
  const { setSearch, search, status, currency } = props;
45
+ const isProduct = window.location.pathname.includes('product');
44
46
  const handleSearch = (obj: any) => {
45
47
  setSearch({
46
48
  ...search,
@@ -50,11 +52,15 @@ export default function FilterTooolbar(props: Props) {
50
52
 
51
53
  return (
52
54
  <Root>
53
- <div className="table-toolbar-left">
55
+ <Box className="table-toolbar-left">
54
56
  <SearchStatus setSearch={handleSearch} status={status} />
55
- <SearchCustomers setSearch={handleSearch} />
56
- {currency ? <SearchCurrency setSearch={handleSearch} /> : <SearchProducts setSearch={handleSearch} />}
57
- </div>
57
+ {isProduct ? null : (
58
+ <>
59
+ <SearchCustomers setSearch={handleSearch} />
60
+ {currency ? <SearchCurrency setSearch={handleSearch} /> : <SearchProducts setSearch={handleSearch} />}
61
+ </>
62
+ )}
63
+ </Box>
58
64
  </Root>
59
65
  );
60
66
  }
@@ -72,13 +78,13 @@ SearchProducts.defaultProps = defaultProps;
72
78
  SearchCustomers.defaultProps = defaultProps;
73
79
 
74
80
  function SearchStatus({ status = [], setSearch }: Pick<Props, 'status' | 'setSearch'>) {
75
- const [show, setShow] = useState(false);
81
+ const [show, setShow] = useState(null);
76
82
  const [display, setDisplay] = useState('');
77
83
  const { t } = useLocaleContext();
78
84
  return (
79
85
  <section
80
- onClick={() => {
81
- setShow(!show);
86
+ onClick={(e) => {
87
+ setShow(e.currentTarget as any);
82
88
  }}>
83
89
  <div className="option-btn">
84
90
  {display ? (
@@ -90,6 +96,7 @@ function SearchStatus({ status = [], setSearch }: Pick<Props, 'status' | 'setSea
90
96
  status: '',
91
97
  });
92
98
  setDisplay('');
99
+ setShow(null);
93
100
  }}
94
101
  />
95
102
  ) : (
@@ -98,20 +105,29 @@ function SearchStatus({ status = [], setSearch }: Pick<Props, 'status' | 'setSea
98
105
  {t('common.status')}
99
106
  <span>{display}</span>
100
107
  </div>
101
- <ul className="status-options" style={{ display: show ? 'block' : 'none' }}>
108
+ <Menu
109
+ anchorEl={show}
110
+ open={Boolean(show)}
111
+ onClose={(e: any) => {
112
+ e.stopPropagation();
113
+ setShow(null);
114
+ }}
115
+ className="status-options">
102
116
  {status.map((x: any) => (
103
- <li
104
- onClick={() => {
117
+ <MenuItem
118
+ onClick={(e) => {
119
+ e.stopPropagation();
105
120
  setSearch({
106
121
  status: x,
107
122
  });
108
123
  setDisplay(x);
124
+ setShow(null);
109
125
  }}
110
126
  key={x}>
111
127
  {x}
112
- </li>
128
+ </MenuItem>
113
129
  ))}
114
- </ul>
130
+ </Menu>
115
131
  </section>
116
132
  );
117
133
  }
@@ -128,7 +144,7 @@ function filterCurrency(methods: any) {
128
144
  }
129
145
 
130
146
  function SearchCurrency({ setSearch }: Pick<Props, 'setSearch'>) {
131
- const [show, setShow] = useState(false);
147
+ const [show, setShow] = useState(null);
132
148
  const [display, setDisplay] = useState('');
133
149
  const { settings } = usePaymentContext();
134
150
  const currencies = filterCurrency(settings.paymentMethods);
@@ -136,8 +152,8 @@ function SearchCurrency({ setSearch }: Pick<Props, 'setSearch'>) {
136
152
 
137
153
  return (
138
154
  <section
139
- onClick={() => {
140
- setShow(!show);
155
+ onClick={(e) => {
156
+ setShow(e.currentTarget as any);
141
157
  }}>
142
158
  <div className="option-btn">
143
159
  {display ? (
@@ -149,6 +165,7 @@ function SearchCurrency({ setSearch }: Pick<Props, 'setSearch'>) {
149
165
  currency_id: '',
150
166
  });
151
167
  setDisplay('');
168
+ setShow(null);
152
169
  }}
153
170
  />
154
171
  ) : (
@@ -157,27 +174,36 @@ function SearchCurrency({ setSearch }: Pick<Props, 'setSearch'>) {
157
174
  {t('common.currency')}
158
175
  <span>{display}</span>
159
176
  </div>
160
- <ul className="status-options" style={{ display: show ? 'block' : 'none' }}>
177
+ <Menu
178
+ anchorEl={show}
179
+ open={Boolean(show)}
180
+ onClose={(e: any) => {
181
+ e.stopPropagation();
182
+ setShow(null);
183
+ }}
184
+ className="status-options">
161
185
  {Object.keys(currencies).map((k: any) => (
162
- <li
163
- onClick={() => {
186
+ <MenuItem
187
+ onClick={(e) => {
188
+ e.stopPropagation();
164
189
  setSearch({
165
190
  currency_id: currencies[k],
166
191
  });
167
192
  setDisplay(k);
193
+ setShow(null);
168
194
  }}
169
195
  key={k}>
170
196
  {k}
171
- </li>
197
+ </MenuItem>
172
198
  ))}
173
- </ul>
199
+ </Menu>
174
200
  </section>
175
201
  );
176
202
  }
177
203
 
178
204
  function SearchCustomers({ setSearch }: Pick<Props, 'setSearch'>) {
179
205
  const [customers, setCustomers] = useState([]);
180
- const [show, setShow] = useState(false);
206
+ const [show, setShow] = useState(null);
181
207
  const [text, setText] = useState('');
182
208
  const [display, setDisplay] = useState('');
183
209
  const { t } = useLocaleContext();
@@ -199,7 +225,7 @@ function SearchCustomers({ setSearch }: Pick<Props, 'setSearch'>) {
199
225
  }, [text]);
200
226
 
201
227
  return (
202
- <section onClick={() => setShow(!show)}>
228
+ <section onClick={(e: any) => setShow(e.currentTarget)}>
203
229
  <div className="option-btn">
204
230
  {display ? (
205
231
  <Close
@@ -210,6 +236,7 @@ function SearchCustomers({ setSearch }: Pick<Props, 'setSearch'>) {
210
236
  customer_id: '',
211
237
  });
212
238
  setDisplay('');
239
+ setShow(null);
213
240
  }}
214
241
  />
215
242
  ) : (
@@ -218,22 +245,41 @@ function SearchCustomers({ setSearch }: Pick<Props, 'setSearch'>) {
218
245
  {t('common.customer')}
219
246
  <span>{display}</span>
220
247
  </div>
221
- <ul className="status-options" style={{ display: show ? 'block' : 'none' }} onClick={(e) => e.stopPropagation()}>
222
- <input
223
- type="text"
224
- placeholder="search customers"
225
- onChange={(e) => {
226
- setText(e.target.value);
227
- }}
228
- />
248
+
249
+ <Menu
250
+ anchorEl={show}
251
+ open={Boolean(show)}
252
+ onClose={(e: any) => {
253
+ e.stopPropagation();
254
+ setShow(null);
255
+ }}>
256
+ <section style={{ padding: '10px 10px 0 10px' }}>
257
+ <input
258
+ style={{
259
+ width: '100%',
260
+ boxSizing: 'border-box',
261
+ border: '1px solid #eee',
262
+ padding: '5px 10px',
263
+ borderRadius: '20px',
264
+ marginBottom: '10px',
265
+ }}
266
+ type="text"
267
+ placeholder="search customers"
268
+ onChange={(e) => {
269
+ setText(e.target.value);
270
+ }}
271
+ />
272
+ </section>
229
273
  {customers.map((x: any) => (
230
- <li
274
+ <MenuItem
231
275
  key={x.id}
232
- onClick={() => {
276
+ onClick={(e) => {
277
+ e.stopPropagation();
233
278
  setSearch({
234
279
  customer_id: x.id,
235
280
  });
236
281
  setDisplay(x.name);
282
+ setShow(null);
237
283
  }}>
238
284
  <InfoCard
239
285
  logo={`/.well-known/service/user/avatar/${x.did}?imageFilter=resize&w=48&h=48`}
@@ -241,9 +287,9 @@ function SearchCustomers({ setSearch }: Pick<Props, 'setSearch'>) {
241
287
  key={x.id}
242
288
  description={`${x.did.slice(0, 6)}...${x.did.slice(-6)}`}
243
289
  />
244
- </li>
290
+ </MenuItem>
245
291
  ))}
246
- </ul>
292
+ </Menu>
247
293
  </section>
248
294
  );
249
295
  }
@@ -275,37 +321,50 @@ function SearchProducts({ setSearch }: Pick<Props, 'setSearch'>) {
275
321
  }, [price]);
276
322
 
277
323
  return (
278
- <section onClick={() => setShow(!show)}>
279
- <div className="option-btn">
280
- {display ? (
281
- <Close
282
- sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }}
283
- onClick={(e) => {
284
- e.stopPropagation();
285
- setSearch({
286
- q: '',
287
- });
288
- setDisplay('');
289
- }}
290
- />
291
- ) : (
292
- <Add sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }} />
293
- )}
294
- {t('admin.subscription.product')}
295
- <span>{display}</span>
296
- </div>
297
- <ul className="status-options" style={{ display: show ? 'block' : 'none' }} onClick={(e) => e.stopPropagation()}>
298
- <ProductsProvider>
299
- <FilterProducts
300
- hasSelected={() => false}
301
- onSelect={(p: any) => {
302
- setPrice(p);
303
- setDisplay(`${p.productName}(${p.displayPrice})`);
304
- }}
305
- />
306
- </ProductsProvider>
307
- </ul>
308
- </section>
324
+ <ClickAwayListener
325
+ onClickAway={(e) => {
326
+ e.stopPropagation();
327
+ setShow(false);
328
+ }}>
329
+ <section onClick={() => setShow(!show)}>
330
+ <div className="option-btn">
331
+ {display ? (
332
+ <Close
333
+ sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }}
334
+ onClick={(e) => {
335
+ e.stopPropagation();
336
+ setSearch({
337
+ q: '',
338
+ });
339
+ setDisplay('');
340
+ setShow(false);
341
+ }}
342
+ />
343
+ ) : (
344
+ <Add sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }} />
345
+ )}
346
+ {t('admin.subscription.product')}
347
+ <span>{display}</span>
348
+ </div>
349
+
350
+ <ul
351
+ className="status-options"
352
+ style={{ display: show ? 'block' : 'none' }}
353
+ onClick={(e) => e.stopPropagation()}>
354
+ <Box sx={{ height: '10px' }} />
355
+ <ProductsProvider>
356
+ <FilterProducts
357
+ hasSelected={() => false}
358
+ onSelect={(p: any) => {
359
+ setPrice(p);
360
+ setDisplay(`${p.productName}(${p.displayPrice})`);
361
+ setShow(false);
362
+ }}
363
+ />
364
+ </ProductsProvider>
365
+ </ul>
366
+ </section>
367
+ </ClickAwayListener>
309
368
  );
310
369
  }
311
370
 
@@ -343,29 +402,21 @@ const Root = styled(Box)`
343
402
 
344
403
  .status-options {
345
404
  position: absolute;
346
- padding: 10px;
405
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14),
406
+ 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
407
+ padding: 0;
347
408
  background: #fff;
348
409
  border: 1px solid #eee;
349
- border-radius: 5px;
350
410
  z-index: 999999;
351
- top: 25px;
411
+ top: 15px;
352
412
  display: block;
353
413
  max-height: 500px;
354
414
  overflow-y: auto;
355
415
  overflow-x: hidden;
356
416
  }
357
417
 
358
- .status-options input {
359
- width: 100%;
360
- border: 1px solid #eee;
361
- padding: 5px 10px;
362
- border-radius: 20px;
363
- margin-bottom: 10px;
364
- }
365
-
366
418
  .status-options li {
367
419
  list-style: none;
368
- padding: 5px;
369
420
  line-height: normal;
370
421
  }
371
422
 
@@ -3,12 +3,11 @@ import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
4
  import { Status, api, formatTime, usePaymentContext } from '@blocklet/payment-react';
5
5
  import type { TProductExpanded } from '@blocklet/payment-types';
6
- import { Alert, CircularProgress, ToggleButton, ToggleButtonGroup } from '@mui/material';
7
- import { useRequest } from 'ahooks';
6
+ import { CircularProgress } from '@mui/material';
8
7
  import { useEffect, useState } from 'react';
9
8
  import { useNavigate } from 'react-router-dom';
10
- import useBus from 'use-bus';
11
9
 
10
+ import FilterTooolbar from '../../../../components/filter-toolbar';
12
11
  import InfoCard from '../../../../components/info-card';
13
12
  import ProductActions from '../../../../components/product/actions';
14
13
  import Table from '../../../../components/table';
@@ -17,7 +16,13 @@ import { formatProductPrice } from '../../../../libs/util';
17
16
  const fetchData = (params: Record<string, any> = {}): Promise<{ list: TProductExpanded[]; count: number }> => {
18
17
  const search = new URLSearchParams();
19
18
  Object.keys(params).forEach((key) => {
20
- 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));
21
26
  });
22
27
  return api.get(`/api/products?${search.toString()}`).then((res) => res.data);
23
28
  };
@@ -29,24 +34,24 @@ export default function ProductsList() {
29
34
  const { t, locale } = useLocaleContext();
30
35
  const navigate = useNavigate();
31
36
  const { settings } = usePaymentContext();
32
- const [search, setSearch] = useState<{ active: string; pageSize: number; page: number }>({
37
+ const [search, setSearch] = useState<{ active: string; pageSize: number; page: any; q?: any; o?: any }>({
33
38
  active: '',
34
39
  pageSize: persisted.rowsPerPage || 20,
35
40
  page: persisted.page ? persisted.page + 1 : 1,
36
41
  });
37
42
 
38
- const { loading, error, data, refresh } = useRequest(() => fetchData(search));
39
- useEffect(() => {
40
- refresh();
41
- }, [search, refresh]);
43
+ const [data, setData] = useState({}) as any;
42
44
 
43
- useBus('project.created', () => refresh(), []);
45
+ const refresh = () =>
46
+ fetchData(search).then((res: any) => {
47
+ setData(res);
48
+ });
44
49
 
45
- if (error) {
46
- return <Alert severity="error">{error.message}</Alert>;
47
- }
50
+ useEffect(() => {
51
+ refresh();
52
+ }, [search]);
48
53
 
49
- if (loading || !data) {
54
+ if (!data.list) {
50
55
  return <CircularProgress />;
51
56
  }
52
57
 
@@ -125,30 +130,45 @@ export default function ProductsList() {
125
130
  <Table
126
131
  durable={listKey}
127
132
  durableKeys={['page', 'rowsPerPage']}
128
- title={
129
- <div className="table-toolbar-left">
130
- <ToggleButtonGroup
131
- value={search.active}
132
- onChange={(_, value) => setSearch((x) => ({ ...x, active: value }))}
133
- exclusive>
134
- <ToggleButton value="">All</ToggleButton>
135
- <ToggleButton value="true">Active</ToggleButton>
136
- <ToggleButton value="false">Archived</ToggleButton>
137
- </ToggleButtonGroup>
138
- </div>
139
- }
133
+ title={<FilterTooolbar setSearch={setSearch} search={search} status={['active', 'archived']} />}
140
134
  data={data.list}
141
135
  columns={columns}
142
136
  options={{
143
137
  count: data.count,
144
138
  page: search.page - 1,
145
139
  rowsPerPage: search.pageSize,
140
+ onColumnSortChange(_: any, order: any) {
141
+ setSearch({
142
+ ...search,
143
+ q: search.q || {},
144
+ o: order,
145
+ });
146
+ },
147
+ onSearchChange: (text: string) => {
148
+ if (text) {
149
+ setSearch({
150
+ q: {
151
+ 'like-description': text,
152
+ 'like-name': text,
153
+ },
154
+ active: '',
155
+ pageSize: 100,
156
+ page: 1,
157
+ });
158
+ } else {
159
+ setSearch({
160
+ active: '',
161
+ pageSize: 100,
162
+ page: 1,
163
+ });
164
+ }
165
+ },
146
166
  onRowClick: (_: any, { dataIndex }: any) => {
147
167
  const item = data.list[dataIndex] as TProductExpanded;
148
168
  navigate(`/admin/products/${item.id}`);
149
169
  },
150
170
  }}
151
- loading={loading}
171
+ loading={!data.list}
152
172
  onChange={onTableChange}
153
173
  />
154
174
  );