payment-kit 1.14.27 → 1.14.29

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 (33) hide show
  1. package/api/src/routes/prices.ts +2 -2
  2. package/blocklet.yml +1 -1
  3. package/package.json +4 -4
  4. package/src/components/copyable.tsx +8 -4
  5. package/src/components/filter-toolbar.tsx +57 -49
  6. package/src/components/info-card.tsx +6 -1
  7. package/src/components/info-row.tsx +7 -1
  8. package/src/components/invoice/list.tsx +1 -1
  9. package/src/components/payment-intent/actions.tsx +2 -1
  10. package/src/components/payment-link/before-pay.tsx +13 -1
  11. package/src/components/pricing-table/preview.tsx +2 -2
  12. package/src/components/pricing-table/product-settings.tsx +12 -1
  13. package/src/components/subscription/portal/list.tsx +42 -18
  14. package/src/components/uploader.tsx +3 -1
  15. package/src/locales/en.tsx +3 -0
  16. package/src/locales/zh.tsx +3 -0
  17. package/src/pages/admin/billing/invoices/detail.tsx +1 -1
  18. package/src/pages/admin/billing/subscriptions/detail.tsx +1 -1
  19. package/src/pages/admin/customers/customers/detail.tsx +1 -1
  20. package/src/pages/admin/payments/intents/detail.tsx +1 -1
  21. package/src/pages/admin/payments/payouts/detail.tsx +1 -1
  22. package/src/pages/admin/payments/refunds/detail.tsx +1 -1
  23. package/src/pages/admin/products/links/create.tsx +11 -2
  24. package/src/pages/admin/products/links/detail.tsx +1 -1
  25. package/src/pages/admin/products/prices/actions.tsx +2 -2
  26. package/src/pages/admin/products/prices/detail.tsx +1 -1
  27. package/src/pages/admin/products/pricing-tables/create.tsx +11 -5
  28. package/src/pages/admin/products/pricing-tables/detail.tsx +1 -1
  29. package/src/pages/admin/products/products/detail.tsx +1 -1
  30. package/src/pages/customer/index.tsx +26 -14
  31. package/src/pages/customer/invoice/detail.tsx +1 -1
  32. package/src/pages/customer/refund/list.tsx +2 -0
  33. package/src/pages/home.tsx +1 -1
@@ -176,12 +176,12 @@ const priceAmountSchema = Joi.object({
176
176
  currency_options: Joi.array()
177
177
  .items(
178
178
  Joi.object({
179
- unit_amount: Joi.number().greater(0).required(),
179
+ unit_amount: Joi.number().greater(0).optional(),
180
180
  // 其他属性
181
181
  }).unknown(true)
182
182
  )
183
183
  .optional(),
184
- unit_amount: Joi.number().greater(0).required(),
184
+ unit_amount: Joi.number().greater(0).optional(),
185
185
  });
186
186
 
187
187
  // FIXME: @wangshijun use schema validation
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.14.27
17
+ version: 1.14.29
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.14.27",
3
+ "version": "1.14.29",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -52,7 +52,7 @@
52
52
  "@arcblock/validator": "^1.18.128",
53
53
  "@blocklet/js-sdk": "1.16.28",
54
54
  "@blocklet/logger": "1.16.28",
55
- "@blocklet/payment-react": "1.14.27",
55
+ "@blocklet/payment-react": "1.14.29",
56
56
  "@blocklet/sdk": "1.16.28",
57
57
  "@blocklet/ui-react": "^2.10.16",
58
58
  "@blocklet/uploader": "^0.1.20",
@@ -118,7 +118,7 @@
118
118
  "devDependencies": {
119
119
  "@abtnode/types": "1.16.28",
120
120
  "@arcblock/eslint-config-ts": "^0.3.2",
121
- "@blocklet/payment-types": "1.14.27",
121
+ "@blocklet/payment-types": "1.14.29",
122
122
  "@types/cookie-parser": "^1.4.7",
123
123
  "@types/cors": "^2.8.17",
124
124
  "@types/debug": "^4.1.12",
@@ -160,5 +160,5 @@
160
160
  "parser": "typescript"
161
161
  }
162
162
  },
163
- "gitHead": "8a67ed1dbdda51526d4cd03c9c10c6930a9ac6a0"
163
+ "gitHead": "104f4189d9ef96c1e332ffa90824ebd3063c989d"
164
164
  }
@@ -1,5 +1,6 @@
1
1
  import { CopyButton } from '@arcblock/ux/lib/ClickToCopy';
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import { getWordBreakStyle } from '@blocklet/payment-react';
3
4
  import { Box, Stack, Typography } from '@mui/material';
4
5
 
5
6
  export default function Copyable({ text, children, style }: { text: string; children?: React.ReactNode; style?: any }) {
@@ -18,10 +19,13 @@ export default function Copyable({ text, children, style }: { text: string; chil
18
19
  sx={{
19
20
  mr: 0.5,
20
21
  maxWidth: 480,
21
- overflow: 'hidden',
22
- fontSize: 'inherit',
23
- textOverflow: 'ellipsis',
24
- whiteSpace: 'nowrap',
22
+ // overflow: 'hidden',
23
+ // fontSize: 'inherit',
24
+ // textOverflow: 'ellipsis',
25
+ // whiteSpace: 'nowrap',
26
+ wordBreak: getWordBreakStyle(text),
27
+ whiteSpace: 'break-spaces',
28
+ minWidth: '60px',
25
29
  }}>
26
30
  {text}
27
31
  </Typography>
@@ -2,7 +2,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import { api, useMobile, 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 { Button, ClickAwayListener, Menu, MenuItem } from '@mui/material';
5
+ import { Button, Menu, MenuItem } from '@mui/material';
6
6
  import { Box, styled } from '@mui/system';
7
7
  import { useEffect, useState } from 'react';
8
8
 
@@ -54,7 +54,12 @@ export default function FilterToolbar(props: Props) {
54
54
  };
55
55
 
56
56
  return (
57
- <Root>
57
+ <Root
58
+ sx={{
59
+ '.MuiTouchRipple-root': {
60
+ display: 'none !important',
61
+ },
62
+ }}>
58
63
  <Box className="table-toolbar-left">
59
64
  <SearchStatus setSearch={handleSearch} search={search} status={status} formatStatus={formatStatus} />
60
65
  {!isMobile && (
@@ -308,7 +313,7 @@ function SearchCustomers({ setSearch, search }: Pick<Props, 'setSearch' | 'searc
308
313
  }
309
314
 
310
315
  function SearchProducts({ setSearch }: Pick<Props, 'setSearch'>) {
311
- const [show, setShow] = useState(false);
316
+ const [show, setShow] = useState(null);
312
317
  const [price, setPrice] = useState({} as any);
313
318
  const [display, setDisplay] = useState('');
314
319
  const isSubscription = window.location.pathname.includes('subscriptions');
@@ -334,51 +339,53 @@ function SearchProducts({ setSearch }: Pick<Props, 'setSearch'>) {
334
339
  }, [price]);
335
340
 
336
341
  return (
337
- <ClickAwayListener
338
- onClickAway={(e) => {
339
- e.stopPropagation();
340
- setShow(false);
341
- }}>
342
- <section onClick={() => setShow(!show)}>
343
- <Button className="option-btn" variant="text">
344
- {display ? (
345
- <Close
346
- sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }}
347
- onClick={(e) => {
348
- e.stopPropagation();
349
- setSearch({
350
- q: '',
351
- });
352
- setDisplay('');
353
- setShow(false);
354
- }}
355
- />
356
- ) : (
357
- <Add sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }} />
358
- )}
359
- {t('admin.subscription.product')}
360
- <span>{display}</span>
361
- </Button>
362
-
363
- <ul
364
- className="status-options"
365
- style={{ display: show ? 'block' : 'none' }}
366
- onClick={(e) => e.stopPropagation()}>
367
- <Box sx={{ height: '10px' }} />
368
- <ProductsProvider>
369
- <ProductSelect
370
- mode="inline"
371
- hasSelected={() => false}
372
- onSelect={(p: any) => {
373
- setPrice(p);
374
- setDisplay(`${p.productName}(${p.displayPrice})`);
375
- setShow(false);
376
- }}
377
- />
378
- </ProductsProvider>
379
- </ul>
380
- </section>
381
- </ClickAwayListener>
342
+ <section>
343
+ <Button
344
+ className="option-btn"
345
+ variant="text"
346
+ onClick={(e) => {
347
+ setShow(e.currentTarget as any);
348
+ }}>
349
+ {display ? (
350
+ <Close
351
+ sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }}
352
+ onClick={(e) => {
353
+ e.stopPropagation();
354
+ setSearch({
355
+ q: '',
356
+ });
357
+ setDisplay('');
358
+ setShow(null);
359
+ }}
360
+ />
361
+ ) : (
362
+ <Add sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: '1.2rem' }} />
363
+ )}
364
+ {t('admin.subscription.product')}
365
+ <span>{display}</span>
366
+ </Button>
367
+ <Menu
368
+ anchorEl={show}
369
+ open={Boolean(show)}
370
+ className="status-options"
371
+ sx={{ height: 300 }}
372
+ onClose={(e: any) => {
373
+ e.stopPropagation();
374
+ setShow(null);
375
+ }}>
376
+ <ProductsProvider>
377
+ <ProductSelect
378
+ mode="inline"
379
+ hasSelected={() => false}
380
+ onSelect={(p: any) => {
381
+ setPrice(p);
382
+ setDisplay(`${p.productName}(${p.displayPrice})`);
383
+ setShow(null);
384
+ }}
385
+ />
386
+ </ProductsProvider>
387
+ </Menu>
388
+ </section>
382
389
  );
383
390
  }
384
391
 
@@ -458,12 +465,13 @@ const Root = styled(Box)`
458
465
  color: #555;
459
466
  font-size: 14px;
460
467
  line-height: 14px;
461
- overflow: hidden;
468
+ overflow: visible;
462
469
  }
463
470
 
464
471
  .option-btn span {
465
472
  color: #3773f2;
466
473
  padding: 0 3px;
474
+ overflow: visible;
467
475
  }
468
476
 
469
477
  .status-options {
@@ -1,3 +1,4 @@
1
+ import { getWordBreakStyle } from '@blocklet/payment-react';
1
2
  import { Avatar, Stack, SxProps, Typography } from '@mui/material';
2
3
  import type { LiteralUnion } from 'type-fest';
3
4
 
@@ -22,7 +23,11 @@ export default function InfoCard(props: Props) {
22
23
  {props.name.slice(0, 1)}
23
24
  </Avatar>
24
25
  )}
25
- <Stack direction="column" alignItems="flex-start" justifyContent="space-around">
26
+ <Stack
27
+ direction="column"
28
+ alignItems="flex-start"
29
+ justifyContent="space-around"
30
+ sx={{ wordBreak: getWordBreakStyle(props.name), minWidth: 140 }}>
26
31
  <Typography variant="body1" color="text.primary">
27
32
  {props.name}
28
33
  </Typography>
@@ -1,6 +1,7 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import { Box, Stack, SxProps } from '@mui/material';
3
3
  import type { ReactNode } from 'react';
4
+ import { getWordBreakStyle } from '@blocklet/payment-react';
4
5
  import { isEmptyExceptNumber } from '../libs/util';
5
6
 
6
7
  type Props = {
@@ -49,7 +50,12 @@ export default function InfoRow(props: Props) {
49
50
  }}>
50
51
  {props.label}
51
52
  </Box>
52
- <Box flex={sizes[1]} color={isNone ? 'text.disabled' : 'text.secondary'} className="info-row-value" fontSize={14}>
53
+ <Box
54
+ flex={sizes[1]}
55
+ color={isNone ? 'text.disabled' : 'text.secondary'}
56
+ className="info-row-value"
57
+ fontSize={14}
58
+ sx={{ wordBreak: getWordBreakStyle(props.value) }}>
53
59
  {isNone ? t('common.none') : props.value}
54
60
  </Box>
55
61
  </Stack>
@@ -129,7 +129,7 @@ export default function InvoiceList({
129
129
  {
130
130
  label: t('common.amount'),
131
131
  name: 'total',
132
- width: 60,
132
+ width: 80,
133
133
  align: 'right',
134
134
  options: {
135
135
  customBodyRenderLite: (_: string, index: number) => {
@@ -141,6 +141,7 @@ function RefundForm({ data, refundMaxAmount }: { data: TPaymentIntentExpanded; r
141
141
  control={control}
142
142
  rules={{
143
143
  required: t('common.required'),
144
+ validate: (value) => value.trim() !== '' || t('common.required'),
144
145
  }}
145
146
  render={({ field }) => (
146
147
  <TextField
@@ -205,7 +206,7 @@ export function PaymentIntentActionsInner({ data, variant, onChange }: Props) {
205
206
  reset();
206
207
  const curAmount = formatBNStr(res?.amount, data.paymentCurrency.decimal);
207
208
  if (Number(curAmount) <= 0) {
208
- Toast.info(t('admin.paymentIntent.refund.empty'));
209
+ Toast.info(t('admin.paymentIntent.refundForm.empty'));
209
210
  return;
210
211
  }
211
212
  setValue('refund.amount', curAmount);
@@ -12,7 +12,11 @@ import CreateProduct from '../product/create';
12
12
  import LineItem from './item';
13
13
  import ProductSelect from './product-select';
14
14
 
15
- export default function BeforePay() {
15
+ export default function BeforePay({
16
+ triggerError = () => {},
17
+ }: {
18
+ triggerError: (keys: { [key: string]: boolean }) => void;
19
+ }) {
16
20
  const { t } = useLocaleContext();
17
21
  const [params, setParams] = useSearchParams();
18
22
  const { products, refresh } = useProductsContext();
@@ -69,6 +73,14 @@ export default function BeforePay() {
69
73
  refresh();
70
74
  };
71
75
 
76
+ useEffect(() => {
77
+ const hasDifferentCurrencyOrRecurring = items.fields.some((_, index) => {
78
+ const priceAlignResult = isPriceAligned(items.fields as any[], products, index);
79
+ return !priceAlignResult.currency || !priceAlignResult.recurring;
80
+ });
81
+ triggerError({ line_items: hasDifferentCurrencyOrRecurring });
82
+ }, [items.fields, products]);
83
+
72
84
  return (
73
85
  <Stack spacing={2} sx={{ width: '100%' }}>
74
86
  <Typography variant="h6" sx={{ fontWeight: 600 }}>
@@ -18,9 +18,9 @@ const PricingTablePreview = forwardRef(({ id, version = 1 }: { id: string; versi
18
18
  return (
19
19
  <div ref={innerRef}>
20
20
  {fullscreen ? (
21
- <div style={{ width: '100%', height: '100%', background: '#fff', border: 'none' }}>
21
+ <div style={{ width: '100%', height: '100%', background: '#fff' }}>
22
22
  <IframeResizer
23
- style={{ width: '100%', height: '100vh', overflow: 'hidden' }}
23
+ style={{ width: '100%', height: '100vh', overflow: 'hidden', border: 'none' }}
24
24
  src={`${window.blocklet.prefix}checkout/pricing-table/${id}?preview=1&version=${version}`}
25
25
  />
26
26
  </div>
@@ -11,7 +11,11 @@ import ProductSelect from '../payment-link/product-select';
11
11
  import CreateProduct from '../product/create';
12
12
  import ProductItem from './product-item';
13
13
 
14
- export default function PricingTableProductSettings() {
14
+ export default function PricingTableProductSettings({
15
+ triggerError = () => {},
16
+ }: {
17
+ triggerError: (keys: { [key: string]: boolean }) => void;
18
+ }) {
15
19
  const { t } = useLocaleContext();
16
20
  const [params, setParams] = useSearchParams();
17
21
  const { products, refresh } = useProductsContext();
@@ -98,6 +102,13 @@ export default function PricingTableProductSettings() {
98
102
 
99
103
  const grouped = groupPricingTableItems(items.fields);
100
104
 
105
+ useEffect(() => {
106
+ const hasDifferentCurrency = items.fields.some(
107
+ (_, index) => !isPriceCurrencyAligned(items.fields as any[], products, index)
108
+ );
109
+ triggerError({ items: hasDifferentCurrency });
110
+ }, [items.fields, products]);
111
+
101
112
  return (
102
113
  <Stack spacing={2} alignItems="flex-start">
103
114
  <Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import Empty from '@arcblock/ux/lib/Empty';
3
4
  import {
4
5
  Status,
5
6
  api,
@@ -29,11 +30,21 @@ type Props = {
29
30
  status: string;
30
31
  onChange?: (action?: string) => any | Promise<any>;
31
32
  onClickSubscription: (subscription: TSubscriptionExpanded) => void | Promise<void>;
33
+ onlyActive?: boolean;
34
+ changeActive?: (active: boolean) => void;
32
35
  } & Omit<StackProps, 'onChange'>;
33
36
 
34
37
  const pageSize = 4;
35
38
 
36
- export default function CurrentSubscriptions({ id, status, onChange, onClickSubscription, ...rest }: Props) {
39
+ export default function CurrentSubscriptions({
40
+ id,
41
+ status,
42
+ onChange,
43
+ onClickSubscription,
44
+ onlyActive,
45
+ changeActive = () => {},
46
+ ...rest
47
+ }: Props) {
37
48
  const { t } = useLocaleContext();
38
49
  const { isMobile } = useMobile();
39
50
 
@@ -184,30 +195,43 @@ export default function CurrentSubscriptions({ id, status, onChange, onClickSubs
184
195
  </Stack>
185
196
  );
186
197
  })}
198
+ <Box>
199
+ {hasMore && (
200
+ <Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
201
+ {loadingMore
202
+ ? t('common.loadingMore', { resource: t('admin.subscriptions') })
203
+ : t('common.loadMore', { resource: t('admin.subscriptions') })}
204
+ </Button>
205
+ )}
206
+ {!hasMore && data.count > pageSize && (
207
+ <Typography color="text.secondary">
208
+ {t('common.noMore', { resource: t('admin.subscriptions') })}
209
+ </Typography>
210
+ )}
211
+ </Box>
187
212
  </>
188
213
  ) : (
189
- <Box sx={{ textAlign: 'center' }}>
190
- <Typography>📦</Typography>
191
- <Typography>{t('admin.subscription.empty')}</Typography>
192
- </Box>
214
+ <Empty>
215
+ {onlyActive ? (
216
+ <Box sx={{ textAlign: 'center' }}>
217
+ <Typography>{t('admin.subscription.noActiveEmpty')}</Typography>
218
+ {changeActive && (
219
+ <Button onClick={() => changeActive(false)} variant="text" sx={{ color: 'text.link' }}>
220
+ {t('admin.subscription.viewAll')}
221
+ </Button>
222
+ )}
223
+ </Box>
224
+ ) : (
225
+ t('admin.subscription.empty')
226
+ )}
227
+ </Empty>
193
228
  )}
194
-
195
- <Box>
196
- {hasMore && (
197
- <Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
198
- {loadingMore
199
- ? t('common.loadingMore', { resource: t('admin.subscriptions') })
200
- : t('common.loadMore', { resource: t('admin.subscriptions') })}
201
- </Button>
202
- )}
203
- {!hasMore && data.count > pageSize && (
204
- <Typography color="text.secondary">{t('common.noMore', { resource: t('admin.subscriptions') })}</Typography>
205
- )}
206
- </Box>
207
229
  </Stack>
208
230
  );
209
231
  }
210
232
 
211
233
  CurrentSubscriptions.defaultProps = {
212
234
  onChange: null,
235
+ onlyActive: false,
236
+ changeActive: null,
213
237
  };
@@ -27,7 +27,9 @@ export default function Uploader({ onUploaded, preview, maxFileSize, maxNumberOf
27
27
  useEffect(() => {
28
28
  if (uploaderRef.current) {
29
29
  const uploader = uploaderRef.current.getUploader();
30
- uploader.onUploadSuccess((result: any) => onUploaded({ url: result.response.data.fileUrl }));
30
+ uploader.onUploadSuccess((result: any) => {
31
+ onUploaded({ url: result.response.data.url });
32
+ });
31
33
  }
32
34
  }, [onUploaded]);
33
35
 
@@ -422,6 +422,8 @@ export default flat({
422
422
  view: 'View subscription',
423
423
  name: 'Subscription',
424
424
  empty: 'No subscription',
425
+ viewAll: 'View all subscriptions',
426
+ noActiveEmpty: 'You currently have no active subscriptions. You can choose to view your subscription history.',
425
427
  attention: 'Past due subscriptions',
426
428
  product: 'Product',
427
429
  collectionMethod: 'Billing',
@@ -513,6 +515,7 @@ export default flat({
513
515
  spent: 'Spent',
514
516
  due: 'Due',
515
517
  stake: 'Stake',
518
+ stats: 'Stats',
516
519
  balance: 'Balance',
517
520
  },
518
521
  address: {
@@ -414,6 +414,8 @@ export default flat({
414
414
  view: '查看订阅',
415
415
  name: '订阅',
416
416
  empty: '没有订阅',
417
+ viewAll: '查看历史订阅',
418
+ noActiveEmpty: '您当前没有服务中的订阅,您可以选择查看历史订阅',
417
419
  product: '产品',
418
420
  attention: '将过期的订阅',
419
421
  collectionMethod: '计费',
@@ -504,6 +506,7 @@ export default flat({
504
506
  spent: '花费金额',
505
507
  due: '欠款金额',
506
508
  stake: '质押金额',
509
+ stats: '统计',
507
510
  balance: '余额',
508
511
  },
509
512
  address: {
@@ -195,7 +195,7 @@ export default function InvoiceDetail(props: { id: string }) {
195
195
  width: {
196
196
  xs: '100%',
197
197
  md: '100%',
198
- lg: '580px',
198
+ lg: '320px',
199
199
  },
200
200
  maxWidth: {
201
201
  xs: '100%',
@@ -173,7 +173,7 @@ export default function SubscriptionDetail(props: { id: string }) {
173
173
  width: {
174
174
  xs: '100%',
175
175
  md: '100%',
176
- lg: '580px',
176
+ lg: '320px',
177
177
  },
178
178
  maxWidth: {
179
179
  xs: '100%',
@@ -256,7 +256,7 @@ export default function CustomerDetail(props: { id: string }) {
256
256
  width: {
257
257
  xs: '100%',
258
258
  md: '100%',
259
- lg: '580px',
259
+ lg: '320px',
260
260
  },
261
261
  maxWidth: {
262
262
  xs: '100%',
@@ -199,7 +199,7 @@ export default function PaymentIntentDetail(props: { id: string }) {
199
199
  width: {
200
200
  xs: '100%',
201
201
  md: '100%',
202
- lg: '580px',
202
+ lg: '320px',
203
203
  },
204
204
  maxWidth: {
205
205
  xs: '100%',
@@ -196,7 +196,7 @@ export default function PayoutDetail(props: { id: string }) {
196
196
  width: {
197
197
  xs: '100%',
198
198
  md: '100%',
199
- lg: '580px',
199
+ lg: '320px',
200
200
  },
201
201
  maxWidth: {
202
202
  xs: '100%',
@@ -198,7 +198,7 @@ export default function RefundDetail(props: { id: string }) {
198
198
  width: {
199
199
  xs: '100%',
200
200
  md: '100%',
201
- lg: '580px',
201
+ lg: '320px',
202
202
  },
203
203
  maxWidth: {
204
204
  xs: '100%',
@@ -11,6 +11,7 @@ import { FormProvider, useForm } from 'react-hook-form';
11
11
  import { useSearchParams } from 'react-router-dom';
12
12
  import { dispatch } from 'use-bus';
13
13
 
14
+ import { useSetState } from 'ahooks';
14
15
  import DrawerForm from '../../../../components/drawer-form';
15
16
  import AfterPay from '../../../../components/payment-link/after-pay';
16
17
  import BeforePay from '../../../../components/payment-link/before-pay';
@@ -30,6 +31,7 @@ export default function CreatePaymentLink() {
30
31
  const [stashed, setStashed] = useState(0);
31
32
  const { settings } = usePaymentContext();
32
33
  const fullScreenRef = useRef(null);
34
+ const [errors, triggerError] = useSetState({});
33
35
 
34
36
  const methods = useForm<PaymentLink>({
35
37
  shouldUnregister: false,
@@ -92,7 +94,11 @@ export default function CreatePaymentLink() {
92
94
  }, [JSON.stringify(changes)]);
93
95
 
94
96
  const tabs = [
95
- { label: t('admin.paymentLink.beforePay'), value: 'beforePay', component: BeforePay },
97
+ {
98
+ label: t('admin.paymentLink.beforePay'),
99
+ value: 'beforePay',
100
+ component: BeforePay,
101
+ },
96
102
  { label: t('admin.paymentLink.afterPay'), value: 'afterPay', component: AfterPay },
97
103
  ];
98
104
  const TabComponent = tabs.find((x) => x.value === current)?.component || BeforePay;
@@ -112,6 +118,9 @@ export default function CreatePaymentLink() {
112
118
  return;
113
119
  }
114
120
 
121
+ if (Object.values(errors).some((v) => v)) {
122
+ return;
123
+ }
115
124
  api
116
125
  .post('/api/payment-links', data)
117
126
  .then(() => {
@@ -152,7 +161,7 @@ export default function CreatePaymentLink() {
152
161
  scrollButtons="auto"
153
162
  />
154
163
  <ProductsProvider>
155
- <TabComponent />
164
+ <TabComponent triggerError={triggerError} />
156
165
  </ProductsProvider>
157
166
  </Stack>
158
167
  <Stack flex={1} sx={{ maxWidth: 680 }}>
@@ -206,7 +206,7 @@ export default function PaymentLinkDetail(props: { id: string }) {
206
206
  width: {
207
207
  xs: '100%',
208
208
  md: '100%',
209
- lg: '580px',
209
+ lg: '320px',
210
210
  },
211
211
  maxWidth: {
212
212
  xs: '100%',
@@ -81,7 +81,7 @@ export default function PriceActions({ data, onChange, variant, setAsDefault }:
81
81
  try {
82
82
  setState({ loading: true });
83
83
  await api.put(`/api/products/${data.product_id}`, { default_price_id: data.id }).then((res) => res.data);
84
- Toast.success(t('common.removed'));
84
+ Toast.success(t('common.saveAsDefaultPriceSuccess'));
85
85
  onChange(state.action);
86
86
  } catch (err) {
87
87
  console.error(err);
@@ -135,7 +135,7 @@ export default function PriceActions({ data, onChange, variant, setAsDefault }:
135
135
  { label: t('admin.pricingTable.add'), handler: onCreatePricingTable, color: 'primary', disabled: !data.recurring },
136
136
  ];
137
137
 
138
- if (setAsDefault) {
138
+ if (!setAsDefault) {
139
139
  actions.splice(4, 0, {
140
140
  label: t('admin.price.setAsDefault'),
141
141
  handler: onSetAsDefault,
@@ -194,7 +194,7 @@ export default function PriceDetail(props: { id: string }) {
194
194
  width: {
195
195
  xs: '100%',
196
196
  md: '100%',
197
- lg: '580px',
197
+ lg: '320px',
198
198
  },
199
199
  maxWidth: {
200
200
  xs: '100%',
@@ -10,8 +10,9 @@ import { FormProvider, useForm } from 'react-hook-form';
10
10
  import { useSearchParams } from 'react-router-dom';
11
11
  import { dispatch } from 'use-bus';
12
12
 
13
+ import { useSetState } from 'ahooks';
13
14
  import DrawerForm from '../../../../components/drawer-form';
14
- import PricingTableCustomerSettings from '../../../../components/pricing-table/customer-settings';
15
+ // import PricingTableCustomerSettings from '../../../../components/pricing-table/customer-settings';
15
16
  import PricingTablePaymentSettings from '../../../../components/pricing-table/payment-settings';
16
17
  import PricingTablePreview from '../../../../components/pricing-table/preview';
17
18
  import PricingTableProductSettings from '../../../../components/pricing-table/product-settings';
@@ -25,6 +26,7 @@ export default function CreatePricingTable() {
25
26
  const [step, setStep] = useState(0); // ['products', 'payment', 'portal']
26
27
  const [stashed, setStashed] = useState(0);
27
28
  const fullScreenRef = useRef(null);
29
+ const [errors, triggerError] = useSetState({});
28
30
 
29
31
  const methods = useForm<TPricingTable & any>({
30
32
  shouldUnregister: false,
@@ -64,6 +66,7 @@ export default function CreatePricingTable() {
64
66
  .then(() => {
65
67
  Toast.success(t('admin.pricingTable.saved'));
66
68
  methods.reset();
69
+ setStep(0);
67
70
  dispatch('drawer.submitted');
68
71
  dispatch('pricingTable.created');
69
72
  })
@@ -80,7 +83,10 @@ export default function CreatePricingTable() {
80
83
  };
81
84
 
82
85
  const onContinue = () => {
83
- if (step < 2) {
86
+ if (Object.values(errors).some((v) => v)) {
87
+ return;
88
+ }
89
+ if (step < 1) {
84
90
  setStep(step + 1);
85
91
  } else {
86
92
  methods.handleSubmit(async (formData: any) => {
@@ -107,9 +113,9 @@ export default function CreatePricingTable() {
107
113
  <Box flex={2} sx={{ borderRight: '1px solid #eee' }} position="relative">
108
114
  <Stack height="100%" spacing={2}>
109
115
  <Box overflow="auto" sx={{ pr: 2 }}>
110
- {step === 0 && <PricingTableProductSettings />}
116
+ {step === 0 && <PricingTableProductSettings triggerError={triggerError} />}
111
117
  {step === 1 && <PricingTablePaymentSettings />}
112
- {step === 2 && <PricingTableCustomerSettings />}
118
+ {/* {step === 2 && <PricingTableCustomerSettings />} */}
113
119
  </Box>
114
120
  <Stack
115
121
  padding={2}
@@ -124,7 +130,7 @@ export default function CreatePricingTable() {
124
130
  {t('common.previous')}
125
131
  </Button>
126
132
  <Button variant="contained" color="primary" onClick={onContinue}>
127
- {step === 2 ? t('common.save') : t('common.continue')}
133
+ {step === 1 ? t('common.save') : t('common.continue')}
128
134
  </Button>
129
135
  </Stack>
130
136
  </Stack>
@@ -193,7 +193,7 @@ export default function PricingTableDetail(props: { id: string }) {
193
193
  width: {
194
194
  xs: '100%',
195
195
  md: '100%',
196
- lg: '580px',
196
+ lg: '320px',
197
197
  },
198
198
  maxWidth: {
199
199
  xs: '100%',
@@ -228,7 +228,7 @@ export default function ProductDetail(props: { id: string }) {
228
228
  width: {
229
229
  xs: '100%',
230
230
  md: '100%',
231
- lg: '580px',
231
+ lg: '320px',
232
232
  },
233
233
  maxWidth: {
234
234
  xs: '100%',
@@ -187,6 +187,8 @@ export default function CustomerHome() {
187
187
  <Box className="section-body">
188
188
  <CurrentSubscriptions
189
189
  id={data.id}
190
+ onlyActive={state.onlyActive}
191
+ changeActive={(v) => setState({ onlyActive: v })}
190
192
  status={state.onlyActive ? 'active,trialing' : 'active,trialing,paused,past_due,canceled'}
191
193
  style={{
192
194
  cursor: 'pointer',
@@ -203,7 +205,7 @@ export default function CustomerHome() {
203
205
  const SummaryCard = (
204
206
  <Box className="base-card section section-summary">
205
207
  <Box className="section-header">
206
- <Typography variant="h3">{t('admin.customer.summary.balance')}</Typography>
208
+ <Typography variant="h3">{t('admin.customer.summary.stats')}</Typography>
207
209
  <FormControl
208
210
  sx={{
209
211
  '.MuiInputBase-root': {
@@ -222,31 +224,41 @@ export default function CustomerHome() {
222
224
  inputProps={{ 'aria-label': 'Without label' }}>
223
225
  {currencies.map((c) => (
224
226
  <MenuItem key={c.id} value={c.id}>
225
- {c.symbol}
227
+ <Box alignItems="center" display="flex" gap={1}>
228
+ <Avatar src={c?.logo} alt={c?.name} sx={{ width: 18, height: 18 }} />
229
+ <Typography
230
+ variant="h5"
231
+ component="div"
232
+ sx={{ fontSize: '16px', color: 'text.primary', fontWeight: '500' }}>
233
+ {c?.name}
234
+ </Typography>
235
+ <Typography sx={{ fontSize: 12 }} color="text.lighter">
236
+ {c?.symbol}
237
+ </Typography>
238
+ </Box>
239
+ {/* {c.symbol} */}
226
240
  </MenuItem>
227
241
  ))}
228
242
  </Select>
229
243
  </FormControl>
230
244
  </Box>
231
- <Box alignItems="center" display="flex" gap={1}>
232
- <Avatar src={currency?.logo} alt={currency?.name} sx={{ width: 18, height: 18 }} />
233
- <Typography variant="h5" component="div" sx={{ fontSize: '16px', color: 'text.primary', fontWeight: '500' }}>
234
- {currency?.name}
235
- </Typography>
236
- <Typography sx={{ fontSize: 12 }} color="text.lighter">
237
- {currency?.symbol}
238
- </Typography>
239
- </Box>
240
- <Typography variant="h3" color="text.primary" gutterBottom>
241
- {formatBNStr(data.summary.token?.[currency?.id], currency.decimal, 6, false)}
242
- </Typography>
243
245
  <Stack
244
246
  className="section-body"
245
247
  sx={{
246
248
  display: 'grid',
247
249
  gridTemplateColumns: '1fr 1fr',
248
250
  gap: 2,
251
+ mt: 1.5,
249
252
  }}>
253
+ <CurrencyCard
254
+ label={t('admin.customer.summary.balance')}
255
+ data={data.summary.token || {}}
256
+ currency={currency}
257
+ sx={{
258
+ background: 'var(--tags-tag-orange-bg, #B7FEE3)',
259
+ color: 'var(--tags-tag-orange-text, #007C52)',
260
+ }}
261
+ />
250
262
  <CurrencyCard
251
263
  label={t('admin.customer.summary.spent')}
252
264
  data={data.summary.paid || {}}
@@ -285,7 +285,7 @@ export default function CustomerInvoiceDetail() {
285
285
  <Divider />
286
286
  <Box className="section">
287
287
  <Typography variant="h3" mb={1.5} className="section-header">
288
- {t('payment.customer.specifics')}
288
+ {t('payment.customer.products')}
289
289
  </Typography>
290
290
  <InvoiceTable invoice={data} simple />
291
291
  </Box>
@@ -66,6 +66,8 @@ const RefundTable = memo(({ invoice_id }: Props) => {
66
66
  {
67
67
  label: t('common.amount'),
68
68
  name: 'id',
69
+ width: 80,
70
+ align: 'right',
69
71
  options: {
70
72
  customBodyRenderLite: (_: string, index: number) => {
71
73
  const item = data.list[index] as TRefundExpanded;
@@ -17,7 +17,7 @@ function Home() {
17
17
  />
18
18
  <Stack alignItems="center" justifyContent="center" sx={{ height: '60vh', width: '100vw' }}>
19
19
  <Stack maxWidth="sm" direction="column" alignItems="center" spacing={3}>
20
- <Avatar src={window.blocklet.appLogo} sx={{ width: 80, height: 80 }} />
20
+ <Avatar src={window.blocklet.appLogo} sx={{ width: 80, height: 80 }} variant="square" />
21
21
  <Stack direction="column" alignItems="center" spacing={1}>
22
22
  <Typography variant="h4">Payment Kit</Typography>
23
23
  <Typography variant="h5" color="text.secondary" fontWeight="normal">