@blocklet/payment-react 1.13.123 → 1.13.125

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 (47) hide show
  1. package/es/api.js +0 -1
  2. package/es/components/confirm.d.ts +14 -0
  3. package/es/components/confirm.js +31 -0
  4. package/es/components/pricing-table.js +77 -67
  5. package/es/contexts/payment.d.ts +4 -2
  6. package/es/index.d.ts +4 -1
  7. package/es/index.js +7 -1
  8. package/es/locales/en.js +5 -1
  9. package/es/locales/zh.js +4 -0
  10. package/es/payment/form/index.js +2 -2
  11. package/es/payment/index.js +3 -3
  12. package/es/portal/invoice/list.d.ts +6 -0
  13. package/es/portal/invoice/list.js +84 -0
  14. package/es/portal/payment/list.d.ts +6 -0
  15. package/es/portal/payment/list.js +84 -0
  16. package/es/util.d.ts +4 -0
  17. package/es/util.js +56 -0
  18. package/lib/api.js +0 -1
  19. package/lib/components/confirm.d.ts +14 -0
  20. package/lib/components/confirm.js +49 -0
  21. package/lib/components/pricing-table.js +143 -135
  22. package/lib/contexts/payment.d.ts +4 -2
  23. package/lib/index.d.ts +4 -1
  24. package/lib/index.js +24 -0
  25. package/lib/locales/en.js +5 -1
  26. package/lib/locales/zh.js +4 -0
  27. package/lib/payment/form/index.js +2 -2
  28. package/lib/payment/index.js +3 -3
  29. package/lib/portal/invoice/list.d.ts +6 -0
  30. package/lib/portal/invoice/list.js +150 -0
  31. package/lib/portal/payment/list.d.ts +6 -0
  32. package/lib/portal/payment/list.js +149 -0
  33. package/lib/util.d.ts +4 -0
  34. package/lib/util.js +62 -1
  35. package/package.json +3 -3
  36. package/src/api.ts +1 -1
  37. package/src/components/confirm.tsx +39 -0
  38. package/src/components/pricing-table.tsx +125 -113
  39. package/src/contexts/payment.tsx +2 -2
  40. package/src/index.ts +6 -0
  41. package/src/locales/en.tsx +5 -1
  42. package/src/locales/zh.tsx +4 -0
  43. package/src/payment/form/index.tsx +5 -3
  44. package/src/payment/index.tsx +3 -3
  45. package/src/portal/invoice/list.tsx +122 -0
  46. package/src/portal/payment/list.tsx +120 -0
  47. package/src/util.ts +60 -0
@@ -17,6 +17,7 @@ import {
17
17
  ToggleButtonGroup,
18
18
  Typography,
19
19
  } from '@mui/material';
20
+ import { styled } from '@mui/system';
20
21
  import { useSetState } from 'ahooks';
21
22
  import { useEffect } from 'react';
22
23
 
@@ -83,123 +84,134 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
83
84
  }
84
85
  };
85
86
 
87
+ const Root = styled(Box)`
88
+ @media (max-width: ${({ theme }) => theme.breakpoints.values.sm}px) {
89
+ .price-table-item {
90
+ width: 90% !important;
91
+ }
92
+ }
93
+ `;
86
94
  return (
87
- <Stack
88
- direction="column"
89
- alignItems={alignItems === 'center' ? 'center' : 'flex-start'}
90
- sx={{
91
- pt: {
92
- xs: 4,
93
- sm: 2,
94
- },
95
- gap: {
96
- xs: 3,
97
- sm: mode === 'select' ? 3 : 5,
98
- },
99
- }}>
100
- {Object.keys(recurring).length > 1 && (
101
- <ToggleButtonGroup
102
- color="primary"
103
- value={state.interval}
104
- onChange={(_, value) => {
105
- if (value !== null) {
106
- setState({ interval: value });
107
- }
108
- }}
109
- exclusive>
110
- {Object.keys(recurring).map((x) => (
111
- <ToggleButton key={x} value={x} sx={{ textTransform: 'capitalize' }}>
112
- {formatRecurring(recurring[x] as PriceRecurring, true, '', locale)}
113
- </ToggleButton>
114
- ))}
115
- </ToggleButtonGroup>
116
- )}
95
+ <Root>
117
96
  <Stack
118
- flexWrap="wrap"
119
- direction="row"
120
- gap={{ xs: 3, sm: 5, md: mode === 'checkout' ? 10 : 5 }}
121
- justifyContent={alignItems === 'center' ? 'center' : 'flex-start'}>
122
- {grouped[state.interval as string]?.map((x: TPricingTableItem) => {
123
- let action = x.subscription_data?.trial_period_days
124
- ? t('payment.checkout.try')
125
- : t('payment.checkout.subscription');
126
- if (mode === 'select') {
127
- action = x.is_selected ? t('payment.checkout.selected') : t('payment.checkout.select');
128
- }
97
+ direction="column"
98
+ alignItems={alignItems === 'center' ? 'center' : 'flex-start'}
99
+ sx={{
100
+ pt: {
101
+ xs: 4,
102
+ sm: 2,
103
+ },
104
+ gap: {
105
+ xs: 3,
106
+ sm: mode === 'select' ? 3 : 5,
107
+ },
108
+ }}>
109
+ {Object.keys(recurring).length > 1 && (
110
+ <ToggleButtonGroup
111
+ color="primary"
112
+ value={state.interval}
113
+ onChange={(_, value) => {
114
+ if (value !== null) {
115
+ setState({ interval: value });
116
+ }
117
+ }}
118
+ exclusive>
119
+ {Object.keys(recurring).map((x) => (
120
+ <ToggleButton key={x} value={x} sx={{ textTransform: 'capitalize' }}>
121
+ {formatRecurring(recurring[x] as PriceRecurring, true, '', locale)}
122
+ </ToggleButton>
123
+ ))}
124
+ </ToggleButtonGroup>
125
+ )}
126
+ <Stack
127
+ flexWrap="wrap"
128
+ direction="row"
129
+ gap="calc(10px + 3%)"
130
+ justifyContent={alignItems === 'center' ? 'center' : 'flex-start'}>
131
+ {grouped[state.interval as string]?.map(
132
+ (x: TPricingTableItem & { is_selected?: boolean; is_disabled?: boolean }) => {
133
+ let action = x.subscription_data?.trial_period_days
134
+ ? t('payment.checkout.try')
135
+ : t('payment.checkout.subscription');
136
+ if (mode === 'select') {
137
+ action = x.is_selected ? t('payment.checkout.selected') : t('payment.checkout.select');
138
+ }
129
139
 
130
- return (
131
- <Fade key={x.price_id} in>
132
- <Stack
133
- padding={4}
134
- spacing={2}
135
- direction="column"
136
- alignItems="center"
137
- sx={{
138
- width: 320,
139
- cursor: 'pointer',
140
- borderWidth: '1px',
141
- borderStyle: 'solid',
142
- borderColor: mode === 'select' && x.is_selected ? 'primary.main' : '#eee',
143
- borderRadius: 1,
144
- transition: 'border-color 0.3s ease 0s, box-shadow 0.3s ease 0s',
145
- boxShadow: '0 4px 8px rgba(0, 0, 0, 20%)',
146
- '&:hover': {
147
- borderColor: mode === 'select' && x.is_selected ? 'primary.main' : '#ddd',
148
- boxShadow: '0 8px 16px rgba(0, 0, 0, 20%)',
149
- },
150
- }}>
151
- <Box textAlign="center">
152
- <Stack direction="row" alignItems="center" spacing={1}>
153
- <Typography variant="h5" color="text.primary" fontWeight={600}>
154
- {x.product.name}
155
- </Typography>
156
- {x.is_highlight && <Chip label={x.highlight_text} color="default" size="small" />}
140
+ return (
141
+ <Fade key={x.price_id} in>
142
+ <Stack
143
+ padding={4}
144
+ spacing={2}
145
+ direction="column"
146
+ alignItems="center"
147
+ className="price-table-item"
148
+ sx={{
149
+ width: '30%',
150
+ cursor: 'pointer',
151
+ borderWidth: '1px',
152
+ borderStyle: 'solid',
153
+ borderColor: mode === 'select' && x.is_selected ? 'primary.main' : '#eee',
154
+ borderRadius: 1,
155
+ transition: 'border-color 0.3s ease 0s, box-shadow 0.3s ease 0s',
156
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 20%)',
157
+ '&:hover': {
158
+ borderColor: mode === 'select' && x.is_selected ? 'primary.main' : '#ddd',
159
+ boxShadow: '0 8px 16px rgba(0, 0, 0, 20%)',
160
+ },
161
+ }}>
162
+ <Box textAlign="center">
163
+ <Stack direction="row" alignItems="center" spacing={1}>
164
+ <Typography variant="h5" color="text.primary" fontWeight={600}>
165
+ {x.product.name}
166
+ </Typography>
167
+ {x.is_highlight && <Chip label={x.highlight_text} color="default" size="small" />}
168
+ </Stack>
169
+ <Typography color="text.secondary">{x.product.description}</Typography>
170
+ </Box>
171
+ <Stack direction="row" alignItems="center" spacing={1}>
172
+ <Amount amount={formatPriceAmount(x.price, table.currency, x.product.unit_label)} />
173
+ <Stack direction="column" alignItems="flex-start">
174
+ <Typography component="span" color="text.secondary" fontSize="0.8rem">
175
+ {t('payment.checkout.per')}
176
+ </Typography>
177
+ <Typography component="span" color="text.secondary" fontSize="0.8rem">
178
+ {formatRecurring(x.price.recurring as PriceRecurring, false, '', locale)}
179
+ </Typography>
180
+ </Stack>
181
+ </Stack>
182
+ <LoadingButton
183
+ fullWidth
184
+ size="large"
185
+ variant={x.is_highlight || x.is_selected ? 'contained' : 'outlined'}
186
+ color={x.is_highlight || x.is_selected ? 'primary' : 'info'}
187
+ sx={{ fontSize: '1.2rem' }}
188
+ loading={state.loading === x.price_id}
189
+ disabled={x.is_disabled}
190
+ onClick={() => handleSelect(x.price_id)}>
191
+ {action}
192
+ </LoadingButton>
193
+ {x.product.features.length > 0 && (
194
+ <Box>
195
+ <Typography>{t('payment.checkout.include')}</Typography>
196
+ <List dense>
197
+ {x.product.features.map((f: any) => (
198
+ <ListItem key={f.name} disableGutters disablePadding>
199
+ <ListItemIcon sx={{ minWidth: 25 }}>
200
+ <CheckOutlined color="success" fontSize="small" />
201
+ </ListItemIcon>
202
+ <ListItemText primary={f.name} />
203
+ </ListItem>
204
+ ))}
205
+ </List>
206
+ </Box>
207
+ )}
157
208
  </Stack>
158
- <Typography color="text.secondary">{x.product.description}</Typography>
159
- </Box>
160
- <Stack direction="row" alignItems="center" spacing={1}>
161
- <Amount amount={formatPriceAmount(x.price, table.currency, x.product.unit_label)} />
162
- <Stack direction="column" alignItems="flex-start">
163
- <Typography component="span" color="text.secondary" fontSize="0.8rem">
164
- {t('payment.checkout.per')}
165
- </Typography>
166
- <Typography component="span" color="text.secondary" fontSize="0.8rem">
167
- {formatRecurring(x.price.recurring as PriceRecurring, false, '', locale)}
168
- </Typography>
169
- </Stack>
170
- </Stack>
171
- <LoadingButton
172
- fullWidth
173
- size="large"
174
- loadingPosition="end"
175
- variant={x.is_highlight || x.is_selected ? 'contained' : 'outlined'}
176
- color={x.is_highlight || x.is_selected ? 'primary' : 'info'}
177
- sx={{ fontSize: '1.2rem' }}
178
- loading={state.loading === x.price_id}
179
- disabled={x.is_disabled}
180
- onClick={() => handleSelect(x.price_id)}>
181
- {action}
182
- </LoadingButton>
183
- {x.product.features.length > 0 && (
184
- <Box>
185
- <Typography>{t('payment.checkout.include')}</Typography>
186
- <List dense>
187
- {x.product.features.map((f: any) => (
188
- <ListItem key={f.name} disableGutters disablePadding>
189
- <ListItemIcon sx={{ minWidth: 25 }}>
190
- <CheckOutlined color="success" fontSize="small" />
191
- </ListItemIcon>
192
- <ListItemText primary={f.name} />
193
- </ListItem>
194
- ))}
195
- </List>
196
- </Box>
197
- )}
198
- </Stack>
199
- </Fade>
200
- );
201
- })}
209
+ </Fade>
210
+ );
211
+ }
212
+ )}
213
+ </Stack>
202
214
  </Stack>
203
- </Stack>
215
+ </Root>
204
216
  );
205
217
  }
@@ -16,8 +16,8 @@ export interface Settings {
16
16
 
17
17
  export type PaymentContextType = {
18
18
  livemode: boolean;
19
- session: any;
20
- connect: any;
19
+ session: import('@arcblock/did-connect/lib/types').SessionContext['session'] & { user: any };
20
+ connect: import('@arcblock/did-connect/lib/types').SessionContext['connectApi'];
21
21
  prefix: string;
22
22
  settings: Settings;
23
23
  refresh: () => void;
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import api from './api';
2
2
  import CheckoutForm from './checkout/form';
3
3
  import CheckoutTable from './checkout/table';
4
+ import ConfirmDialog from './components/confirm';
4
5
  import FormInput from './components/input';
5
6
  import Livemode from './components/livemode';
6
7
  import PricingTable from './components/pricing-table';
@@ -11,6 +12,8 @@ import Amount from './payment/amount';
11
12
  import PhoneInput from './payment/form/phone';
12
13
  import Payment from './payment/index';
13
14
  import ProductSkeleton from './payment/product-skeleton';
15
+ import CustomerInvoiceList from './portal/invoice/list';
16
+ import CustomerPaymentList from './portal/payment/list';
14
17
 
15
18
  export * from './util';
16
19
  export * from './contexts/payment';
@@ -25,10 +28,13 @@ export {
25
28
  Status,
26
29
  Livemode,
27
30
  Switch,
31
+ ConfirmDialog,
28
32
  CheckoutForm,
29
33
  CheckoutTable,
30
34
  Payment,
31
35
  PricingTable,
32
36
  ProductSkeleton,
33
37
  Amount,
38
+ CustomerInvoiceList,
39
+ CustomerPaymentList,
34
40
  };
@@ -150,6 +150,7 @@ export default flat({
150
150
  },
151
151
  },
152
152
  customer: {
153
+ payments: 'Payment History',
153
154
  invoices: 'Invoice History',
154
155
  details: 'Billing Details',
155
156
  update: 'Update Information',
@@ -206,7 +207,10 @@ export default flat({
206
207
  pay: 'Pay this invoice',
207
208
  paySuccess: 'You have successfully paid the invoice',
208
209
  payError: 'Failed to paid the invoice',
209
- empty: 'Seems you do not have any payments here',
210
+ empty: 'Seems you do not have any invoice here',
211
+ },
212
+ payment: {
213
+ empty: 'Seems you do not have any payment here',
210
214
  },
211
215
  subscriptions: {
212
216
  title: 'Manage subscriptions',
@@ -147,6 +147,7 @@ export default flat({
147
147
  },
148
148
  },
149
149
  customer: {
150
+ payments: '支付历史',
150
151
  invoices: '发票历史',
151
152
  details: '计费详情',
152
153
  update: '更新客户信息',
@@ -202,6 +203,9 @@ export default flat({
202
203
  pay: '支付此发票',
203
204
  paySuccess: '支付成功',
204
205
  payError: '支付失败',
206
+ empty: '你没有任何发票',
207
+ },
208
+ payment: {
205
209
  empty: '你没有任何支付',
206
210
  },
207
211
  subscriptions: {
@@ -225,10 +225,11 @@ export default function PaymentForm({
225
225
  if (result.data.balance?.sufficient || result.data.delegation?.sufficient) {
226
226
  await handleConnected();
227
227
  } else {
228
+ // @FIXME: 需要考虑如何正确地适配前端组件的使用 @wangshijun
228
229
  connect.open({
230
+ containerEl: undefined as unknown as Element,
229
231
  action: checkoutSession.mode,
230
- prefix: joinURL(getPrefix(), '/api/did'),
231
- timeout: 5 * 60 * 1000,
232
+ prefix: joinURL(window.location.origin, getPrefix(), '/api/did'),
232
233
  extraParams: { checkoutSessionId: checkoutSession.id },
233
234
  onSuccess: async () => {
234
235
  connect.close();
@@ -242,7 +243,7 @@ export default function PaymentForm({
242
243
  setState({ submitting: false, paying: false });
243
244
  onError(err);
244
245
  },
245
- });
246
+ } as any);
246
247
  }
247
248
  }
248
249
  if (['stripe'].includes(method.type)) {
@@ -266,6 +267,7 @@ export default function PaymentForm({
266
267
  if (session?.user) {
267
268
  handleSubmit(onSubmit)();
268
269
  } else {
270
+ // @ts-ignored
269
271
  session?.login({
270
272
  onSuccess: onUserLoggedIn,
271
273
  extraParams: {},
@@ -350,18 +350,18 @@ export const Root = styled(Box)<{ mode: LiteralUnion<'standalone' | 'inline' | '
350
350
  }
351
351
 
352
352
  .cko-payment-card-unselect {
353
- border: 2px solid #bbb;
353
+ border: 2px solid #ddd;
354
354
  padding: 5px 10px;
355
355
  margin: 5px 0;
356
356
  cursor: pointer;
357
357
  }
358
358
 
359
359
  .cko-payment-card:nth-child(odd) {
360
- margin-right: 5px;
360
+ margin-right: 16px;
361
361
  }
362
362
 
363
363
  .cko-payment-card-unselect:nth-child(odd) {
364
- margin-right: 5px;
364
+ margin-right: 16px;
365
365
  }
366
366
 
367
367
  .cko-payment-card::after {
@@ -0,0 +1,122 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import type { Paginated, TInvoiceExpanded } from '@blocklet/payment-types';
4
+ import { Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
5
+ import { fromUnitToToken } from '@ocap/util';
6
+ import { useInfiniteScroll } from 'ahooks';
7
+
8
+ import api from '../../api';
9
+ import Status from '../../components/status';
10
+ import { formatToDate, getInvoiceStatusColor } from '../../util';
11
+
12
+ const groupByDate = (items: TInvoiceExpanded[]) => {
13
+ const grouped: { [key: string]: TInvoiceExpanded[] } = {};
14
+ items.forEach((item) => {
15
+ const date = new Date(item.created_at).toLocaleDateString();
16
+ if (!grouped[date]) {
17
+ grouped[date] = [];
18
+ }
19
+ grouped[date]?.push(item);
20
+ });
21
+ return grouped;
22
+ };
23
+
24
+ const fetchData = (params: Record<string, any> = {}): Promise<Paginated<TInvoiceExpanded>> => {
25
+ const search = new URLSearchParams();
26
+ Object.keys(params).forEach((key) => {
27
+ search.set(key, String(params[key]));
28
+ });
29
+ return api.get(`/api/invoices?${search.toString()}`).then((res: any) => res.data);
30
+ };
31
+
32
+ type Props = {
33
+ customer_id: string;
34
+ };
35
+
36
+ const pageSize = 10;
37
+
38
+ export default function CustomerInvoiceList({ customer_id }: Props) {
39
+ const { t } = useLocaleContext();
40
+
41
+ const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TInvoiceExpanded>>(
42
+ (d) => {
43
+ const page = d ? Math.ceil(d.list.length / pageSize) + 1 : 1;
44
+ return fetchData({ page, pageSize, status: 'open,paid,uncollectible', customer_id });
45
+ },
46
+ {
47
+ reloadDeps: [customer_id],
48
+ }
49
+ );
50
+
51
+ if (loading || !data) {
52
+ return <CircularProgress />;
53
+ }
54
+
55
+ if (data && data.list.length === 0) {
56
+ return <Typography color="text.secondary">{t('payment.customer.invoice.empty')}</Typography>;
57
+ }
58
+
59
+ const hasMore = data && data.list.length < data.count;
60
+
61
+ const grouped = groupByDate(data.list as any);
62
+
63
+ return (
64
+ <Stack direction="column" gap={1} sx={{ mt: 1 }}>
65
+ {Object.entries(grouped).map(([date, invoices]) => (
66
+ <Box key={date}>
67
+ <Typography sx={{ fontWeight: 'bold', color: 'text.secondary', mt: 2, mb: 1 }}>{date}</Typography>
68
+ {invoices.map((invoice) => (
69
+ <Stack
70
+ key={invoice.id}
71
+ direction={{
72
+ xs: 'column',
73
+ sm: 'row',
74
+ }}
75
+ sx={{ my: 1 }}
76
+ gap={{
77
+ xs: 0.5,
78
+ sm: 1.5,
79
+ md: 3,
80
+ }}
81
+ flexWrap="nowrap">
82
+ <Box flex={3}>
83
+ <a href={`/customer/invoice/${invoice.id}`}>
84
+ <Typography component="span">{invoice.number}</Typography>
85
+ </a>
86
+ </Box>
87
+ <Box flex={3}>
88
+ <Typography>{formatToDate(invoice.created_at)}</Typography>
89
+ </Box>
90
+ <Box flex={2}>
91
+ <Typography textAlign="right">
92
+ {fromUnitToToken(invoice.total, invoice.paymentCurrency.decimal)}&nbsp;
93
+ {invoice.paymentCurrency.symbol}
94
+ </Typography>
95
+ </Box>
96
+ <Box flex={2}>
97
+ <Status label={invoice.status} color={getInvoiceStatusColor(invoice.status)} />
98
+ </Box>
99
+ <Box flex={4}>
100
+ <Typography>{invoice.description || invoice.id}</Typography>
101
+ </Box>
102
+ </Stack>
103
+ ))}
104
+ </Box>
105
+ ))}
106
+ <Box>
107
+ {hasMore && (
108
+ <Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
109
+ {loadingMore
110
+ ? t('common.loadingMore', { resource: t('payment.customer.invoices') })
111
+ : t('common.loadMore', { resource: t('payament.customer.invoices') })}
112
+ </Button>
113
+ )}
114
+ {!hasMore && data.count > pageSize && (
115
+ <Typography color="text.secondary">
116
+ {t('common.noMore', { resource: t('payment.customer.invoices') })}
117
+ </Typography>
118
+ )}
119
+ </Box>
120
+ </Stack>
121
+ );
122
+ }
@@ -0,0 +1,120 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import type { Paginated, TPaymentIntentExpanded } from '@blocklet/payment-types';
4
+ import { Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
5
+ import { fromUnitToToken } from '@ocap/util';
6
+ import { useInfiniteScroll } from 'ahooks';
7
+
8
+ import api from '../../api';
9
+ import Status from '../../components/status';
10
+ import { formatToDate, getPaymentIntentStatusColor } from '../../util';
11
+
12
+ const groupByDate = (items: TPaymentIntentExpanded[]) => {
13
+ const grouped: { [key: string]: TPaymentIntentExpanded[] } = {};
14
+ items.forEach((item) => {
15
+ const date = new Date(item.created_at).toLocaleDateString();
16
+ if (!grouped[date]) {
17
+ grouped[date] = [];
18
+ }
19
+ grouped[date]?.push(item);
20
+ });
21
+ return grouped;
22
+ };
23
+
24
+ const fetchData = (params: Record<string, any> = {}): Promise<Paginated<TPaymentIntentExpanded>> => {
25
+ const search = new URLSearchParams();
26
+ Object.keys(params).forEach((key) => {
27
+ search.set(key, String(params[key]));
28
+ });
29
+ return api.get(`/api/payment-intents?${search.toString()}`).then((res: any) => res.data);
30
+ };
31
+
32
+ type Props = {
33
+ customer_id: string;
34
+ };
35
+
36
+ const pageSize = 10;
37
+
38
+ export default function CustomerPaymentList({ customer_id }: Props) {
39
+ const { t } = useLocaleContext();
40
+
41
+ const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TPaymentIntentExpanded>>(
42
+ (d) => {
43
+ const page = d ? Math.ceil(d.list.length / pageSize) + 1 : 1;
44
+ return fetchData({ page, pageSize, customer_id });
45
+ },
46
+ {
47
+ reloadDeps: [customer_id],
48
+ }
49
+ );
50
+
51
+ if (loading || !data) {
52
+ return <CircularProgress />;
53
+ }
54
+
55
+ if (data && data.list.length === 0) {
56
+ return <Typography color="text.secondary">{t('payment.customer.payment.empty')}</Typography>;
57
+ }
58
+
59
+ const hasMore = data && data.list.length < data.count;
60
+
61
+ const grouped = groupByDate(data.list as any);
62
+
63
+ return (
64
+ <Stack direction="column" gap={1} sx={{ mt: 1 }}>
65
+ {Object.entries(grouped).map(([date, payments]) => (
66
+ <Box key={date}>
67
+ <Typography sx={{ fontWeight: 'bold', color: 'text.secondary', mt: 2, mb: 1 }}>{date}</Typography>
68
+ {payments.map((item) => (
69
+ <Stack
70
+ key={item.id}
71
+ direction={{
72
+ xs: 'column',
73
+ sm: 'row',
74
+ }}
75
+ sx={{ my: 1 }}
76
+ gap={{
77
+ xs: 0.5,
78
+ sm: 1.5,
79
+ md: 3,
80
+ }}
81
+ flexWrap="nowrap">
82
+ <Box flex={3} sx={{ minWidth: '220px' }}>
83
+ <Typography component="span">{item.id}</Typography>
84
+ </Box>
85
+ <Box flex={3}>
86
+ <Typography>{formatToDate(item.created_at)}</Typography>
87
+ </Box>
88
+ <Box flex={2}>
89
+ <Typography textAlign="right">
90
+ {fromUnitToToken(item.amount, item.paymentCurrency.decimal)}&nbsp;
91
+ {item.paymentCurrency.symbol}
92
+ </Typography>
93
+ </Box>
94
+ <Box flex={3}>
95
+ <Status label={item.status} color={getPaymentIntentStatusColor(item.status)} />
96
+ </Box>
97
+ <Box flex={4}>
98
+ <Typography>{item.description || item.id}</Typography>
99
+ </Box>
100
+ </Stack>
101
+ ))}
102
+ </Box>
103
+ ))}
104
+ <Box>
105
+ {hasMore && (
106
+ <Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
107
+ {loadingMore
108
+ ? t('common.loadingMore', { resource: t('payment.customer.payments') })
109
+ : t('common.loadMore', { resource: t('payment.customer.payments') })}
110
+ </Button>
111
+ )}
112
+ {!hasMore && data.count > pageSize && (
113
+ <Typography color="text.secondary">
114
+ {t('common.noMore', { resource: t('payment.customer.payments') })}
115
+ </Typography>
116
+ )}
117
+ </Box>
118
+ </Stack>
119
+ );
120
+ }