@blocklet/payment-react 1.19.0 → 1.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/es/components/blockchain/tx.d.ts +1 -1
  2. package/es/components/blockchain/tx.js +9 -11
  3. package/es/components/country-select.d.ts +1 -1
  4. package/es/components/date-range-picker.d.ts +13 -0
  5. package/es/components/date-range-picker.js +279 -0
  6. package/es/components/input.d.ts +5 -2
  7. package/es/components/input.js +6 -2
  8. package/es/components/label.d.ts +7 -0
  9. package/es/components/label.js +49 -0
  10. package/es/components/loading-button.d.ts +1 -1
  11. package/es/history/credit/grants-list.d.ts +14 -0
  12. package/es/history/credit/grants-list.js +215 -0
  13. package/es/history/credit/transactions-list.d.ts +13 -0
  14. package/es/history/credit/transactions-list.js +255 -0
  15. package/es/history/invoice/list.js +21 -1
  16. package/es/index.d.ts +5 -1
  17. package/es/index.js +10 -1
  18. package/es/libs/util.d.ts +2 -0
  19. package/es/libs/util.js +12 -0
  20. package/es/locales/en.js +20 -2
  21. package/es/locales/zh.js +20 -2
  22. package/es/payment/form/index.js +44 -6
  23. package/es/payment/index.js +18 -3
  24. package/es/payment/product-item.d.ts +8 -1
  25. package/es/payment/product-item.js +137 -5
  26. package/es/payment/summary.d.ts +3 -1
  27. package/es/payment/summary.js +9 -0
  28. package/lib/components/blockchain/tx.d.ts +1 -1
  29. package/lib/components/blockchain/tx.js +9 -8
  30. package/lib/components/country-select.d.ts +1 -1
  31. package/lib/components/date-range-picker.d.ts +13 -0
  32. package/lib/components/date-range-picker.js +329 -0
  33. package/lib/components/input.d.ts +5 -2
  34. package/lib/components/input.js +8 -4
  35. package/lib/components/label.d.ts +7 -0
  36. package/lib/components/label.js +60 -0
  37. package/lib/components/loading-button.d.ts +1 -1
  38. package/lib/history/credit/grants-list.d.ts +14 -0
  39. package/lib/history/credit/grants-list.js +277 -0
  40. package/lib/history/credit/transactions-list.d.ts +13 -0
  41. package/lib/history/credit/transactions-list.js +301 -0
  42. package/lib/history/invoice/list.js +24 -0
  43. package/lib/index.d.ts +5 -1
  44. package/lib/index.js +39 -0
  45. package/lib/libs/util.d.ts +2 -0
  46. package/lib/libs/util.js +14 -0
  47. package/lib/locales/en.js +20 -2
  48. package/lib/locales/zh.js +20 -2
  49. package/lib/payment/form/index.js +45 -6
  50. package/lib/payment/index.js +20 -2
  51. package/lib/payment/product-item.d.ts +8 -1
  52. package/lib/payment/product-item.js +144 -4
  53. package/lib/payment/summary.d.ts +3 -1
  54. package/lib/payment/summary.js +9 -0
  55. package/package.json +3 -3
  56. package/src/components/blockchain/tx.tsx +9 -15
  57. package/src/components/country-select.tsx +2 -2
  58. package/src/components/date-range-picker.tsx +310 -0
  59. package/src/components/input.tsx +14 -3
  60. package/src/components/label.tsx +58 -0
  61. package/src/components/loading-button.tsx +1 -1
  62. package/src/history/credit/grants-list.tsx +276 -0
  63. package/src/history/credit/transactions-list.tsx +317 -0
  64. package/src/history/invoice/list.tsx +18 -1
  65. package/src/index.ts +9 -0
  66. package/src/libs/util.ts +14 -0
  67. package/src/locales/en.tsx +20 -0
  68. package/src/locales/zh.tsx +19 -0
  69. package/src/payment/form/address.tsx +2 -2
  70. package/src/payment/form/index.tsx +110 -52
  71. package/src/payment/index.tsx +17 -1
  72. package/src/payment/product-item.tsx +152 -4
  73. package/src/payment/summary.tsx +13 -2
@@ -6,24 +6,20 @@ import { styled } from '@mui/system';
6
6
 
7
7
  import { getTxLink } from '../../libs/util';
8
8
 
9
- export default function TxLink(rawProps: {
9
+ export default function TxLink({
10
+ details,
11
+ method,
12
+ mode = 'dashboard',
13
+ align = 'left',
14
+ }: {
10
15
  details: PaymentDetails;
11
16
  method: TPaymentMethod;
12
17
  mode?: 'customer' | 'dashboard';
13
18
  align?: 'left' | 'right';
14
19
  }) {
15
- const props = Object.assign(
16
- {
17
- mode: 'dashboard',
18
- align: 'left',
19
- },
20
- rawProps
21
- );
22
-
23
20
  const { t } = useLocaleContext();
24
21
 
25
- // eslint-disable-next-line react/prop-types
26
- if (!props.details || (props.mode === 'customer' && props.method.type === 'stripe')) {
22
+ if (!details || (mode === 'customer' && method.type === 'stripe')) {
27
23
  return (
28
24
  <Typography
29
25
  component="small"
@@ -35,8 +31,7 @@ export default function TxLink(rawProps: {
35
31
  );
36
32
  }
37
33
 
38
- // eslint-disable-next-line react/prop-types
39
- const { text, link } = getTxLink(props.method, props.details);
34
+ const { text, link } = getTxLink(method, details);
40
35
 
41
36
  if (link && text) {
42
37
  return (
@@ -44,8 +39,7 @@ export default function TxLink(rawProps: {
44
39
  <Root
45
40
  direction="row"
46
41
  alignItems="center"
47
- // eslint-disable-next-line react/prop-types
48
- justifyContent={props.align === 'left' ? 'flex-start' : 'flex-end'}
42
+ justifyContent={align === 'left' ? 'flex-start' : 'flex-end'}
49
43
  sx={{ color: 'text.link' }}
50
44
  spacing={1}>
51
45
  <Typography component="span" sx={{ color: 'text.link' }}>
@@ -7,7 +7,7 @@ import type { SlideProps, SxProps } from '@mui/material';
7
7
  import type { CountryIso2 } from 'react-international-phone';
8
8
  import { useMobile } from '../hooks/mobile';
9
9
 
10
- function Transition({ ref, ...props }: { ref: React.RefObject<unknown> } & SlideProps) {
10
+ function Transition({ ref, ...props }: { ref: React.RefObject<unknown | null> } & SlideProps) {
11
11
  return <Slide direction="up" ref={ref} timeout={200} {...props} />;
12
12
  }
13
13
 
@@ -27,7 +27,7 @@ export default function CountrySelect({
27
27
  sx = {},
28
28
  showDialCode = false,
29
29
  }: CountrySelectProps & {
30
- ref?: React.RefObject<HTMLDivElement>;
30
+ ref?: React.RefObject<HTMLDivElement | null>;
31
31
  }) {
32
32
  const { setValue } = useFormContext();
33
33
  const [open, setOpen] = useState(false);
@@ -0,0 +1,310 @@
1
+ /* eslint-disable @typescript-eslint/indent */
2
+ import { useState, useRef } from 'react';
3
+ import {
4
+ TextField,
5
+ Stack,
6
+ Typography,
7
+ useMediaQuery,
8
+ useTheme,
9
+ Popover,
10
+ Dialog,
11
+ DialogTitle,
12
+ DialogContent,
13
+ DialogActions,
14
+ Button,
15
+ Box,
16
+ IconButton,
17
+ } from '@mui/material';
18
+ import { DateRange, Close, Clear } from '@mui/icons-material';
19
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
20
+ import FormLabel from './label';
21
+ import { formatToDate } from '../libs/util';
22
+
23
+ export interface DateRangeValue {
24
+ start: number | undefined;
25
+ end: number | undefined;
26
+ }
27
+
28
+ export interface DateRangePickerProps {
29
+ value: DateRangeValue;
30
+ onChange: (value: DateRangeValue) => void;
31
+ label?: string;
32
+ size?: 'small' | 'medium';
33
+ fullWidth?: boolean;
34
+ disabled?: boolean;
35
+ }
36
+
37
+ interface DatePickerContentProps {
38
+ tempValue: { startDate: string; endDate: string };
39
+ setTempValue: (
40
+ value:
41
+ | { startDate: string; endDate: string }
42
+ | ((prev: { startDate: string; endDate: string }) => { startDate: string; endDate: string })
43
+ ) => void;
44
+ handleCancel: () => void;
45
+ handleApply: () => void;
46
+ handleClear: () => void;
47
+ }
48
+
49
+ // 日期选择器内容组件
50
+ function DatePickerContent({
51
+ tempValue,
52
+ setTempValue,
53
+ handleCancel,
54
+ handleApply,
55
+ handleClear,
56
+ }: DatePickerContentProps) {
57
+ const { t } = useLocaleContext();
58
+ return (
59
+ <Box sx={{ p: 2, minWidth: 320 }}>
60
+ <Stack spacing={2}>
61
+ <Box>
62
+ <FormLabel>{t('common.startDate')}</FormLabel>
63
+ <TextField
64
+ type="date"
65
+ value={tempValue.startDate}
66
+ onChange={(e) => setTempValue((prev) => ({ ...prev, startDate: e.target.value }))}
67
+ size="small"
68
+ fullWidth
69
+ slotProps={{
70
+ inputLabel: { shrink: true },
71
+ }}
72
+ />
73
+ </Box>
74
+ <Box>
75
+ <FormLabel>{t('common.endDate')}</FormLabel>
76
+ <TextField
77
+ type="date"
78
+ value={tempValue.endDate}
79
+ onChange={(e) => setTempValue((prev) => ({ ...prev, endDate: e.target.value }))}
80
+ size="small"
81
+ fullWidth
82
+ slotProps={{
83
+ inputLabel: { shrink: true },
84
+ }}
85
+ />
86
+ </Box>
87
+
88
+ <Stack
89
+ direction="row"
90
+ spacing={1}
91
+ sx={{
92
+ justifyContent: 'space-between',
93
+ }}>
94
+ <Button variant="text" onClick={handleClear} size="small" color="secondary" sx={{ px: 0.5, minWidth: 0 }}>
95
+ {t('common.clear')}
96
+ </Button>
97
+ <Stack direction="row" spacing={1}>
98
+ <Button variant="outlined" onClick={handleCancel} size="small">
99
+ {t('common.cancel')}
100
+ </Button>
101
+ <Button variant="contained" onClick={handleApply} size="small">
102
+ {t('common.confirm')}
103
+ </Button>
104
+ </Stack>
105
+ </Stack>
106
+ </Stack>
107
+ </Box>
108
+ );
109
+ }
110
+
111
+ const DateFormat = 'YYYY-MM-DD';
112
+
113
+ export default function DateRangePicker({
114
+ value,
115
+ onChange,
116
+ label = '',
117
+ size = 'small',
118
+ fullWidth = false,
119
+ disabled = false,
120
+ }: DateRangePickerProps) {
121
+ const theme = useTheme();
122
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
123
+ const [open, setOpen] = useState(false);
124
+ const { t, locale } = useLocaleContext();
125
+ // 内部使用字符串格式的临时值
126
+ const [tempValue, setTempValue] = useState<{ startDate: string; endDate: string }>({
127
+ startDate: value.start ? formatToDate(value.start * 1000, locale, DateFormat) : '',
128
+ endDate: value.end ? formatToDate(value.end * 1000, locale, DateFormat) : '',
129
+ });
130
+ const anchorRef = useRef<HTMLDivElement>(null);
131
+
132
+ const formatDisplayValue = (startUnix: number | undefined, endUnix: number | undefined) => {
133
+ if (!startUnix || !endUnix) return t('common.selectTimeRange');
134
+ const start = formatToDate(startUnix * 1000, locale, DateFormat);
135
+ const end = formatToDate(endUnix * 1000, locale, DateFormat);
136
+ return `${start} ~ ${end}`;
137
+ };
138
+
139
+ const handleOpen = () => {
140
+ if (disabled) return;
141
+ setTempValue({
142
+ startDate: value.start ? formatToDate(value.start * 1000, locale, DateFormat) : '',
143
+ endDate: value.end ? formatToDate(value.end * 1000, locale, DateFormat) : '',
144
+ });
145
+ setOpen(true);
146
+ };
147
+
148
+ const handleClose = () => {
149
+ setOpen(false);
150
+ };
151
+
152
+ const handleApply = () => {
153
+ const unixValue: DateRangeValue = {
154
+ start: tempValue.startDate ? Math.floor(new Date(`${tempValue.startDate}T00:00:00`).getTime() / 1000) : 0,
155
+ end: tempValue.endDate ? Math.floor(new Date(`${tempValue.endDate}T23:59:59`).getTime() / 1000) : 0,
156
+ };
157
+ onChange(unixValue);
158
+ setOpen(false);
159
+ };
160
+
161
+ const handleCancel = () => {
162
+ setTempValue({
163
+ startDate: value.start ? formatToDate(value.start * 1000, locale, DateFormat) : '',
164
+ endDate: value.end ? formatToDate(value.end * 1000, locale, DateFormat) : '',
165
+ });
166
+ setOpen(false);
167
+ };
168
+
169
+ const handleClear = () => {
170
+ const emptyValue: DateRangeValue = { start: undefined, end: undefined };
171
+ onChange(emptyValue);
172
+ setTempValue({ startDate: '', endDate: '' });
173
+ setOpen(false);
174
+ };
175
+
176
+ const hasValue = !!value.start && !!value.end;
177
+
178
+ return (
179
+ <>
180
+ <TextField
181
+ ref={anchorRef}
182
+ label={label}
183
+ value={formatDisplayValue(value.start, value.end)}
184
+ size={size}
185
+ fullWidth={fullWidth}
186
+ disabled={disabled}
187
+ sx={{
188
+ '& .MuiInputBase-input': {
189
+ cursor: disabled ? 'default' : 'pointer',
190
+ },
191
+ }}
192
+ onClick={handleOpen}
193
+ placeholder={t('common.selectTimeRange')}
194
+ slotProps={{
195
+ input: {
196
+ readOnly: true,
197
+ startAdornment: <DateRange fontSize="small" sx={{ mr: 1, color: 'text.secondary' }} />,
198
+ endAdornment: hasValue && !disabled && (
199
+ <IconButton
200
+ size="small"
201
+ onClick={(e) => {
202
+ e.stopPropagation();
203
+ handleClear();
204
+ }}
205
+ sx={{
206
+ color: 'text.secondary',
207
+
208
+ '&:hover': { color: 'text.primary' },
209
+ }}>
210
+ <Clear fontSize="small" />
211
+ </IconButton>
212
+ ),
213
+ },
214
+
215
+ inputLabel: { shrink: true },
216
+ }}
217
+ />
218
+ {/* 桌面端使用 Popover */}
219
+ {!isMobile && (
220
+ <Popover
221
+ open={open}
222
+ anchorEl={anchorRef.current}
223
+ onClose={handleClose}
224
+ anchorOrigin={{
225
+ vertical: 'bottom',
226
+ horizontal: 'left',
227
+ }}
228
+ transformOrigin={{
229
+ vertical: 'top',
230
+ horizontal: 'left',
231
+ }}
232
+ sx={{
233
+ '& .MuiPaper-root': {
234
+ boxShadow: theme.shadows[8],
235
+ border: `1px solid ${theme.palette.divider}`,
236
+ },
237
+ }}>
238
+ <DatePickerContent
239
+ tempValue={tempValue}
240
+ setTempValue={setTempValue}
241
+ handleCancel={handleCancel}
242
+ handleApply={handleApply}
243
+ handleClear={handleClear}
244
+ />
245
+ </Popover>
246
+ )}
247
+ {/* 移动端使用 Dialog */}
248
+ {isMobile && (
249
+ <Dialog
250
+ open={open}
251
+ onClose={handleClose}
252
+ fullWidth
253
+ maxWidth="sm"
254
+ PaperProps={{
255
+ sx: {
256
+ m: 1,
257
+ maxHeight: 'calc(100% - 64px)',
258
+ },
259
+ }}>
260
+ <DialogTitle
261
+ sx={{
262
+ display: 'flex',
263
+ justifyContent: 'space-between',
264
+ alignItems: 'center',
265
+ pb: 1,
266
+ }}>
267
+ <Typography variant="h6">{t('common.selectTimeRange')}</Typography>
268
+ <IconButton edge="end" color="inherit" onClick={handleClose} aria-label="close" size="small">
269
+ <Close />
270
+ </IconButton>
271
+ </DialogTitle>
272
+ <DialogContent sx={{ pb: 1 }}>
273
+ <Stack spacing={2} sx={{ mt: 1 }}>
274
+ <FormLabel>{t('common.startDate')}</FormLabel>
275
+ <TextField
276
+ type="date"
277
+ value={tempValue.startDate}
278
+ onChange={(e) => setTempValue((prev) => ({ ...prev, startDate: e.target.value }))}
279
+ size="small"
280
+ fullWidth
281
+ slotProps={{
282
+ inputLabel: { shrink: true },
283
+ }}
284
+ />
285
+ <FormLabel>{t('common.endDate')}</FormLabel>
286
+ <TextField
287
+ type="date"
288
+ value={tempValue.endDate}
289
+ onChange={(e) => setTempValue((prev) => ({ ...prev, endDate: e.target.value }))}
290
+ size="small"
291
+ fullWidth
292
+ slotProps={{
293
+ inputLabel: { shrink: true },
294
+ }}
295
+ />
296
+ </Stack>
297
+ </DialogContent>
298
+ <DialogActions sx={{ px: 3, pb: 2 }}>
299
+ <Button onClick={handleCancel} color="inherit">
300
+ {t('common.cancel')}
301
+ </Button>
302
+ <Button onClick={handleApply} variant="contained">
303
+ {t('common.confirm')}
304
+ </Button>
305
+ </DialogActions>
306
+ </Dialog>
307
+ )}
308
+ </>
309
+ );
310
+ }
@@ -1,9 +1,10 @@
1
1
  import React, { type ReactNode, useImperativeHandle, useRef } from 'react';
2
- import { Box, FormLabel, InputAdornment, TextField, Typography } from '@mui/material';
2
+ import { Box, InputAdornment, TextField, Typography } from '@mui/material';
3
3
  import get from 'lodash/get';
4
4
  import { Controller, useFormContext } from 'react-hook-form';
5
5
  import type { TextFieldProps } from '@mui/material';
6
6
  import type { RegisterOptions } from 'react-hook-form';
7
+ import FormLabel from './label';
7
8
 
8
9
  type InputProps = TextFieldProps & {
9
10
  name: string;
@@ -12,6 +13,9 @@ type InputProps = TextFieldProps & {
12
13
  errorPosition?: 'right' | 'bottom';
13
14
  rules?: RegisterOptions;
14
15
  wrapperStyle?: React.CSSProperties;
16
+ required?: boolean;
17
+ tooltip?: ReactNode | string;
18
+ description?: ReactNode | string;
15
19
  };
16
20
 
17
21
  function FormInputError({ error }: { error: string }) {
@@ -33,9 +37,12 @@ export default function FormInput({
33
37
  errorPosition = 'bottom',
34
38
  wrapperStyle = {},
35
39
  inputProps = {},
40
+ required = false,
41
+ tooltip = '',
42
+ description = '',
36
43
  ...rest
37
44
  }: InputProps & {
38
- ref?: React.RefObject<HTMLInputElement>;
45
+ ref?: React.RefObject<HTMLInputElement | null>;
39
46
  inputProps?: TextFieldProps['inputProps'];
40
47
  }) {
41
48
  const { control, formState } = useFormContext();
@@ -52,7 +59,11 @@ export default function FormInput({
52
59
  rules={rules}
53
60
  render={({ field }) => (
54
61
  <Box sx={{ width: '100%', ...wrapperStyle }}>
55
- {!!label && <FormLabel sx={{ color: 'text.primary' }}>{label}</FormLabel>}
62
+ {!!label && (
63
+ <FormLabel required={required} tooltip={tooltip} description={description}>
64
+ {label}
65
+ </FormLabel>
66
+ )}
56
67
  <TextField
57
68
  fullWidth
58
69
  error={!!get(formState.errors, name)}
@@ -0,0 +1,58 @@
1
+ import { InfoOutlined } from '@mui/icons-material';
2
+ import { Box, FormLabel, Tooltip, Typography } from '@mui/material';
3
+ import type { FormLabelProps } from '@mui/material';
4
+ import type { ReactNode } from 'react';
5
+
6
+ export default function CustomFormLabel({
7
+ children,
8
+ required = false,
9
+ tooltip = '',
10
+ description = '',
11
+ ...props
12
+ }: FormLabelProps & { required?: boolean; tooltip?: ReactNode | string; description?: ReactNode | string }) {
13
+ return (
14
+ <Box sx={{ mb: 1, width: '100%' }}>
15
+ <FormLabel
16
+ {...props}
17
+ sx={{
18
+ display: 'flex',
19
+ alignItems: 'center',
20
+ gap: 0.5,
21
+ fontWeight: 500,
22
+ color: 'text.primary',
23
+ '&.MuiFormLabel-root': {
24
+ display: 'flex',
25
+ alignItems: 'center',
26
+ gap: 0.5,
27
+ fontWeight: 500,
28
+ color: 'text.primary',
29
+ },
30
+ ...(props.sx || {}),
31
+ }}>
32
+ {children}
33
+ {required && (
34
+ <Typography component="span" color="error">
35
+ *
36
+ </Typography>
37
+ )}
38
+ {tooltip &&
39
+ (typeof tooltip === 'string' ? (
40
+ <Tooltip title={tooltip}>
41
+ <InfoOutlined fontSize="small" sx={{ opacity: 0.7 }} />
42
+ </Tooltip>
43
+ ) : (
44
+ tooltip
45
+ ))}
46
+ </FormLabel>
47
+ {description && (
48
+ <Typography
49
+ variant="caption"
50
+ sx={{
51
+ color: 'text.secondary',
52
+ }}>
53
+ {description}
54
+ </Typography>
55
+ )}
56
+ </Box>
57
+ );
58
+ }
@@ -28,7 +28,7 @@ export default function LoadingButton({
28
28
  sx,
29
29
  ...props
30
30
  }: LoadingButtonProps & {
31
- ref?: React.RefObject<HTMLButtonElement>;
31
+ ref?: React.RefObject<HTMLButtonElement | null>;
32
32
  }) {
33
33
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
34
34
  if (loading) {