@blocklet/ui-react 2.12.12 → 2.12.14
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.
- package/lib/@types/index.d.ts +2 -1
- package/lib/@types/index.js +1 -0
- package/lib/Footer/links.js +2 -1
- package/lib/Header/index.js +2 -1
- package/lib/UserCenter/components/editable-field.d.ts +2 -1
- package/lib/UserCenter/components/editable-field.js +5 -1
- package/lib/UserCenter/components/status-dialog/date-picker.d.ts +10 -0
- package/lib/UserCenter/components/status-dialog/date-picker.js +52 -0
- package/lib/UserCenter/components/status-dialog/index.d.ts +12 -0
- package/lib/UserCenter/components/status-dialog/index.js +238 -0
- package/lib/UserCenter/components/status-selector/menu-item.d.ts +2 -0
- package/lib/UserCenter/components/user-center.js +8 -3
- package/lib/UserCenter/components/user-info/clock.js +3 -2
- package/lib/UserCenter/components/user-info/metadata.js +15 -62
- package/lib/UserCenter/components/user-info/timezone-select.d.ts +8 -0
- package/lib/UserCenter/components/user-info/timezone-select.js +99 -0
- package/lib/UserCenter/components/user-info/user-basic-info.js +4 -14
- package/lib/UserCenter/components/user-info/user-status.d.ts +2 -1
- package/lib/UserCenter/components/user-info/user-status.js +49 -14
- package/lib/UserCenter/components/user-info/utils.d.ts +23 -0
- package/lib/UserCenter/components/user-info/utils.js +25 -1
- package/lib/UserCenter/libs/locales.d.ts +12 -0
- package/lib/UserCenter/libs/locales.js +18 -6
- package/lib/blocklets.js +7 -9
- package/package.json +4 -4
- package/src/@types/index.ts +1 -0
- package/src/Footer/links.jsx +2 -1
- package/src/Header/index.tsx +7 -1
- package/src/UserCenter/components/editable-field.tsx +6 -0
- package/src/UserCenter/components/status-dialog/date-picker.tsx +67 -0
- package/src/UserCenter/components/status-dialog/index.tsx +280 -0
- package/src/UserCenter/components/status-selector/menu-item.tsx +2 -0
- package/src/UserCenter/components/user-center.tsx +7 -2
- package/src/UserCenter/components/user-info/clock.tsx +3 -2
- package/src/UserCenter/components/user-info/metadata.tsx +17 -70
- package/src/UserCenter/components/user-info/timezone-select.tsx +114 -0
- package/src/UserCenter/components/user-info/user-basic-info.tsx +4 -11
- package/src/UserCenter/components/user-info/user-status.tsx +57 -13
- package/src/UserCenter/components/user-info/utils.ts +29 -1
- package/src/UserCenter/libs/locales.ts +14 -2
- package/src/blocklets.js +7 -9
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { useEffect, useState, createElement, Suspense, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogTitle,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogActions,
|
|
7
|
+
Button,
|
|
8
|
+
FormControl,
|
|
9
|
+
InputLabel,
|
|
10
|
+
Select,
|
|
11
|
+
MenuItem,
|
|
12
|
+
ListItemIcon,
|
|
13
|
+
Box,
|
|
14
|
+
Typography,
|
|
15
|
+
IconButton,
|
|
16
|
+
} from '@mui/material';
|
|
17
|
+
import dayjs from 'dayjs';
|
|
18
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
19
|
+
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
20
|
+
import { useMemoizedFn } from 'ahooks';
|
|
21
|
+
import { temp as colors } from '@arcblock/ux/lib/Colors';
|
|
22
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
23
|
+
import ArrowDownwardIcon from '@arcblock/icons/lib/ArrowDown';
|
|
24
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
25
|
+
import { DurationEnum, UserMetadata } from '../../../@types';
|
|
26
|
+
import { StatusItem } from '../status-selector/menu-item';
|
|
27
|
+
import { translations } from '../../libs/locales';
|
|
28
|
+
import DateTimeInput from './date-picker';
|
|
29
|
+
import { defaultButtonStyle, primaryButtonStyle } from '../user-info/utils';
|
|
30
|
+
|
|
31
|
+
const selectStyle = {
|
|
32
|
+
padding: '8px 16px',
|
|
33
|
+
borderRadius: '8px',
|
|
34
|
+
'&:hover': {
|
|
35
|
+
'fieldset.MuiOutlinedInput-notchedOutline': {
|
|
36
|
+
borderColor: colors.dividerColor,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
'fieldset.MuiOutlinedInput-notchedOutline': {
|
|
40
|
+
borderColor: colors.dividerColor,
|
|
41
|
+
borderRadius: '8px',
|
|
42
|
+
},
|
|
43
|
+
'.MuiSelect-select': {
|
|
44
|
+
padding: '0 !important',
|
|
45
|
+
display: 'flex',
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
interface StatusDialogProps {
|
|
50
|
+
open: boolean;
|
|
51
|
+
onClose: () => void;
|
|
52
|
+
data: Array<StatusItem>;
|
|
53
|
+
selected: UserMetadata['status'];
|
|
54
|
+
onSelect: (v: UserMetadata['status']) => void;
|
|
55
|
+
timezone?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default function StatusDialog({ open, onClose, data, selected, onSelect, timezone }: StatusDialogProps) {
|
|
59
|
+
const { locale } = useLocaleContext();
|
|
60
|
+
const t = useMemoizedFn((key, _data = {}) => {
|
|
61
|
+
return translate(translations, key, locale, 'en', _data);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const [status, setStatus] = useState(selected?.value || '');
|
|
65
|
+
const [duration, setDuration] = useState(selected?.duration || '');
|
|
66
|
+
const [customDate, setCustomDate] = useState(selected?.dateRange?.[1] || '');
|
|
67
|
+
const [changed, setChanged] = useState(false);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
setStatus(selected?.value || '');
|
|
71
|
+
setDuration(selected?.duration || '');
|
|
72
|
+
setCustomDate(selected?.dateRange?.[1] || '');
|
|
73
|
+
}, [selected]);
|
|
74
|
+
|
|
75
|
+
const isValid = useMemo(() => {
|
|
76
|
+
return status && duration;
|
|
77
|
+
}, [status, duration]);
|
|
78
|
+
|
|
79
|
+
const selectedStatus = data.find((item) => item.id === status);
|
|
80
|
+
|
|
81
|
+
const handleQuickSettingClick = (item: StatusItem) => {
|
|
82
|
+
onSelect({
|
|
83
|
+
value: item.id,
|
|
84
|
+
...(item.duration ? { duration: item.duration as DurationEnum } : {}),
|
|
85
|
+
});
|
|
86
|
+
onClose();
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleSubmit = (clean = false) => {
|
|
90
|
+
if (clean) {
|
|
91
|
+
onSelect(undefined);
|
|
92
|
+
} else {
|
|
93
|
+
const current = dayjs();
|
|
94
|
+
onSelect({
|
|
95
|
+
value: status,
|
|
96
|
+
...(duration ? { duration: duration as DurationEnum } : {}),
|
|
97
|
+
...(duration === DurationEnum.Custom && customDate
|
|
98
|
+
? { dateRange: [current.toDate(), dayjs(customDate).toDate()] }
|
|
99
|
+
: {}),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
onClose();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Dialog
|
|
107
|
+
open={open}
|
|
108
|
+
onClose={onClose}
|
|
109
|
+
maxWidth="xs"
|
|
110
|
+
fullWidth
|
|
111
|
+
PaperProps={{
|
|
112
|
+
sx: {
|
|
113
|
+
borderRadius: '8px',
|
|
114
|
+
},
|
|
115
|
+
}}>
|
|
116
|
+
<DialogTitle sx={{ borderBottom: `1px solid ${colors.dividerColor}` }}>
|
|
117
|
+
<Typography variant="h6" sx={{ fontSize: '16px !important', mb: 0 }}>
|
|
118
|
+
{t('profile.setStatus')}
|
|
119
|
+
</Typography>
|
|
120
|
+
<IconButton
|
|
121
|
+
aria-label="close"
|
|
122
|
+
onClick={onClose}
|
|
123
|
+
sx={{
|
|
124
|
+
position: 'absolute',
|
|
125
|
+
right: 8,
|
|
126
|
+
top: 8,
|
|
127
|
+
color: (theme) => theme.palette.grey[500],
|
|
128
|
+
}}>
|
|
129
|
+
<CloseIcon />
|
|
130
|
+
</IconButton>
|
|
131
|
+
</DialogTitle>
|
|
132
|
+
<DialogContent>
|
|
133
|
+
<FormControl fullWidth sx={{ mt: 2 }}>
|
|
134
|
+
<InputLabel size="small" sx={{ fontSize: '14px' }}>
|
|
135
|
+
{t('profile.setStatus')}
|
|
136
|
+
</InputLabel>
|
|
137
|
+
<Select
|
|
138
|
+
value={status}
|
|
139
|
+
label={t('profile.setStatus')}
|
|
140
|
+
onChange={(e) => {
|
|
141
|
+
setStatus(e.target.value);
|
|
142
|
+
setChanged(true);
|
|
143
|
+
}}
|
|
144
|
+
variant="outlined"
|
|
145
|
+
// eslint-disable-next-line react/no-unstable-nested-components
|
|
146
|
+
IconComponent={(props) => <ArrowDownwardIcon {...props} width={20} height={20} />}
|
|
147
|
+
sx={selectStyle}>
|
|
148
|
+
{data.map((item) => (
|
|
149
|
+
<MenuItem key={item.id} value={item.id}>
|
|
150
|
+
<Suspense fallback={null}>
|
|
151
|
+
{item.icon && (
|
|
152
|
+
<ListItemIcon style={{ minWidth: '24px' }}>
|
|
153
|
+
{createElement(item.icon, {
|
|
154
|
+
style: { fontSize: '16px', width: '16px', height: '16px' },
|
|
155
|
+
})}
|
|
156
|
+
</ListItemIcon>
|
|
157
|
+
)}
|
|
158
|
+
{item.name}
|
|
159
|
+
</Suspense>
|
|
160
|
+
</MenuItem>
|
|
161
|
+
))}
|
|
162
|
+
</Select>
|
|
163
|
+
</FormControl>
|
|
164
|
+
|
|
165
|
+
{!status ? (
|
|
166
|
+
<Box sx={{ mt: '24px' }}>
|
|
167
|
+
<Typography variant="body2" color="text.secondary">
|
|
168
|
+
{t('profile.quickSettings')}
|
|
169
|
+
</Typography>
|
|
170
|
+
{data.map((item) => (
|
|
171
|
+
<Box
|
|
172
|
+
key={item.id}
|
|
173
|
+
onClick={() => handleQuickSettingClick(item)}
|
|
174
|
+
sx={{
|
|
175
|
+
display: 'flex',
|
|
176
|
+
alignItems: 'center',
|
|
177
|
+
gap: '4px',
|
|
178
|
+
p: '4px',
|
|
179
|
+
cursor: 'pointer',
|
|
180
|
+
borderRadius: '8px',
|
|
181
|
+
'&:hover': {
|
|
182
|
+
bgcolor: 'action.hover',
|
|
183
|
+
},
|
|
184
|
+
}}>
|
|
185
|
+
<Suspense fallback={null}>
|
|
186
|
+
{item.icon && (
|
|
187
|
+
<ListItemIcon style={{ minWidth: '24px' }}>
|
|
188
|
+
{createElement(item.icon, {
|
|
189
|
+
style: { fontSize: '16px', width: '16px', height: '16px' },
|
|
190
|
+
})}
|
|
191
|
+
</ListItemIcon>
|
|
192
|
+
)}
|
|
193
|
+
{item.name}
|
|
194
|
+
{item.durationName && (
|
|
195
|
+
<Typography variant="body2" color="text.secondary">
|
|
196
|
+
- {item.durationName}
|
|
197
|
+
</Typography>
|
|
198
|
+
)}
|
|
199
|
+
</Suspense>
|
|
200
|
+
</Box>
|
|
201
|
+
))}
|
|
202
|
+
</Box>
|
|
203
|
+
) : (
|
|
204
|
+
<FormControl fullWidth sx={{ mt: '24px' }}>
|
|
205
|
+
<InputLabel size="small" sx={{ fontSize: '14px' }}>
|
|
206
|
+
{t('profile.removeStatusAfter')}
|
|
207
|
+
</InputLabel>
|
|
208
|
+
<Select
|
|
209
|
+
value={duration}
|
|
210
|
+
label={t('profile.removeStatusAfter')}
|
|
211
|
+
onChange={(e) => {
|
|
212
|
+
const { value } = e.target;
|
|
213
|
+
setDuration(value);
|
|
214
|
+
if (value === DurationEnum.Custom) {
|
|
215
|
+
setCustomDate(dayjs().toDate());
|
|
216
|
+
}
|
|
217
|
+
setChanged(true);
|
|
218
|
+
}}
|
|
219
|
+
variant="outlined"
|
|
220
|
+
// eslint-disable-next-line react/no-unstable-nested-components
|
|
221
|
+
IconComponent={(props) => <ArrowDownwardIcon {...props} width={20} height={20} />}
|
|
222
|
+
sx={selectStyle}>
|
|
223
|
+
{selectedStatus?.children?.map((item) => (
|
|
224
|
+
<MenuItem key={item.id} value={item.id}>
|
|
225
|
+
{item.name}
|
|
226
|
+
</MenuItem>
|
|
227
|
+
))}
|
|
228
|
+
</Select>
|
|
229
|
+
</FormControl>
|
|
230
|
+
)}
|
|
231
|
+
{duration === DurationEnum.Custom && (
|
|
232
|
+
<Box sx={{ mt: '24px' }}>
|
|
233
|
+
<DateTimeInput
|
|
234
|
+
value={customDate as Date}
|
|
235
|
+
onChange={(e) => {
|
|
236
|
+
setCustomDate(e);
|
|
237
|
+
setChanged(true);
|
|
238
|
+
}}
|
|
239
|
+
label={t('profile.selectEndTime')}
|
|
240
|
+
timezone={timezone}
|
|
241
|
+
error={duration === DurationEnum.Custom && !customDate}
|
|
242
|
+
helperText={duration === DurationEnum.Custom && !customDate ? t('profile.pleaseSelectTime') : undefined}
|
|
243
|
+
/>
|
|
244
|
+
</Box>
|
|
245
|
+
)}
|
|
246
|
+
</DialogContent>
|
|
247
|
+
<DialogActions sx={{ p: '16px 24px', borderTop: `1px solid ${colors.dividerColor}` }}>
|
|
248
|
+
{selected?.value && !changed ? (
|
|
249
|
+
<Button
|
|
250
|
+
sx={{ ...defaultButtonStyle, minWidth: '54px' }}
|
|
251
|
+
size="small"
|
|
252
|
+
variant="outlined"
|
|
253
|
+
onClick={() => handleSubmit(true)}>
|
|
254
|
+
{t('profile.cleanStatus')}
|
|
255
|
+
</Button>
|
|
256
|
+
) : (
|
|
257
|
+
<>
|
|
258
|
+
<Button size="small" variant="outlined" sx={{ ...defaultButtonStyle, minWidth: '54px' }} onClick={onClose}>
|
|
259
|
+
{t('common.cancel')}
|
|
260
|
+
</Button>
|
|
261
|
+
<Button
|
|
262
|
+
sx={{
|
|
263
|
+
...primaryButtonStyle,
|
|
264
|
+
minWidth: '54px',
|
|
265
|
+
'&.Mui-disabled': {
|
|
266
|
+
backgroundColor: 'rgba(0, 0, 0, 0.12)',
|
|
267
|
+
},
|
|
268
|
+
}}
|
|
269
|
+
disabled={!isValid}
|
|
270
|
+
onClick={() => handleSubmit(false)}
|
|
271
|
+
size="small"
|
|
272
|
+
variant="outlined">
|
|
273
|
+
{t('common.confirm')}
|
|
274
|
+
</Button>
|
|
275
|
+
</>
|
|
276
|
+
)}
|
|
277
|
+
</DialogActions>
|
|
278
|
+
</Dialog>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
@@ -62,6 +62,11 @@ const ContentWrapper = styled(Box)(({ theme }) => ({
|
|
|
62
62
|
[theme.breakpoints.up('md')]: {
|
|
63
63
|
flex: 1,
|
|
64
64
|
},
|
|
65
|
+
'@media (min-width: 850px) and (max-width: 1050px)': {
|
|
66
|
+
'& .user-center-tabs': {
|
|
67
|
+
maxWidth: '500px',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
65
70
|
}));
|
|
66
71
|
|
|
67
72
|
export default function UserCenter({
|
|
@@ -227,7 +232,7 @@ export default function UserCenter({
|
|
|
227
232
|
label: x.title || x.label,
|
|
228
233
|
url: x.link || x.url,
|
|
229
234
|
protected: privacyState?.data?.[value] ?? false,
|
|
230
|
-
isPrivate: x.isPrivate || x.
|
|
235
|
+
isPrivate: x.isPrivate || x.private || x._rawLink.includes('/customer'), // FIXME: HACK: 隐藏 /customer 菜单, 需要一个通用的解决方案,在嵌入的时候就决定是否是私有的
|
|
231
236
|
// icon: x.icon,
|
|
232
237
|
};
|
|
233
238
|
})
|
|
@@ -453,7 +458,7 @@ export default function UserCenter({
|
|
|
453
458
|
|
|
454
459
|
return (
|
|
455
460
|
<ContentWrapper display="flex" flexDirection={isMobile ? 'column' : 'row'}>
|
|
456
|
-
<Box flex="1" order={isMobile ? 2 : 'unset'}>
|
|
461
|
+
<Box flex="1" className="user-center-tabs" order={isMobile ? 2 : 'unset'}>
|
|
457
462
|
{userCenterTabs.length > 0 && currentTab ? (
|
|
458
463
|
<Box
|
|
459
464
|
display="flex"
|
|
@@ -4,12 +4,13 @@ import utc from 'dayjs/plugin/utc';
|
|
|
4
4
|
import timezonePlugin from 'dayjs/plugin/timezone';
|
|
5
5
|
import { formatToDatetime } from '@arcblock/ux/lib/Util';
|
|
6
6
|
import { useCreation } from 'ahooks';
|
|
7
|
+
import { currentTimezone } from './utils';
|
|
7
8
|
|
|
8
9
|
dayjs.extend(utc);
|
|
9
10
|
dayjs.extend(timezonePlugin);
|
|
10
11
|
|
|
11
|
-
export default function Clock({ timezone =
|
|
12
|
-
const [time, setTime] = useState(dayjs().tz(timezone));
|
|
12
|
+
export default function Clock({ timezone = currentTimezone, locale = 'zh' }: { timezone?: string; locale?: string }) {
|
|
13
|
+
const [time, setTime] = useState(dayjs().tz(timezone || currentTimezone));
|
|
13
14
|
|
|
14
15
|
useEffect(() => {
|
|
15
16
|
const timerId = setInterval(() => {
|
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
3
3
|
|
|
4
4
|
import Box from '@mui/material/Box';
|
|
5
|
-
import MenuItem from '@mui/material/MenuItem';
|
|
6
|
-
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
|
7
5
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
|
8
6
|
import SwipeableDrawer from '@mui/material/SwipeableDrawer';
|
|
9
7
|
import Backdrop, { BackdropProps } from '@mui/material/Backdrop';
|
|
8
|
+
|
|
10
9
|
import styled from '@emotion/styled';
|
|
11
10
|
import { joinURL } from 'ufo';
|
|
12
11
|
import Button from '@arcblock/ux/lib/Button';
|
|
13
12
|
import cloneDeep from 'lodash/cloneDeep';
|
|
14
|
-
|
|
13
|
+
|
|
15
14
|
import { useCreation, useMemoizedFn, useReactive } from 'ahooks';
|
|
16
15
|
import { useMemo, useRef, useState, memo, forwardRef, useEffect, lazy } from 'react';
|
|
17
16
|
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
@@ -19,46 +18,20 @@ import isEmail from 'validator/lib/isEmail';
|
|
|
19
18
|
import isMobilePhone from 'validator/lib/isMobilePhone';
|
|
20
19
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
21
20
|
|
|
22
|
-
import ArrowDownwardIcon from '@arcblock/icons/lib/ArrowDown';
|
|
23
21
|
import { useBrowser } from '@arcblock/react-hooks';
|
|
24
22
|
import { translations } from '../../libs/locales';
|
|
25
23
|
import type { User, UserMetadata } from '../../../@types';
|
|
26
24
|
import EditableField from '../editable-field';
|
|
27
25
|
import { LinkPreviewInput } from './link-preview-input';
|
|
28
|
-
import {
|
|
26
|
+
import { currentTimezone, defaultButtonStyle, primaryButtonStyle } from './utils';
|
|
29
27
|
import Clock from './clock';
|
|
30
|
-
|
|
31
|
-
const timezones = getTimezones();
|
|
28
|
+
import { TimezoneSelect } from './timezone-select';
|
|
32
29
|
|
|
33
30
|
const LocationIcon = lazy(() => import('@arcblock/icons/lib/Location'));
|
|
34
31
|
const TimezoneIcon = lazy(() => import('@arcblock/icons/lib/Timezone'));
|
|
35
32
|
const EmailIcon = lazy(() => import('@arcblock/icons/lib/Email'));
|
|
36
33
|
const PhoneIcon = lazy(() => import('@arcblock/icons/lib/Phone'));
|
|
37
34
|
|
|
38
|
-
const defaultButtonStyle = {
|
|
39
|
-
color: colors.foregroundsFgBase,
|
|
40
|
-
borderColor: colors.strokeBorderBase,
|
|
41
|
-
backgroundColor: colors.buttonsButtonNeutral,
|
|
42
|
-
'&:hover': {
|
|
43
|
-
borderColor: colors.strokeBorderBase,
|
|
44
|
-
backgroundColor: colors.buttonsButtonNeutralHover,
|
|
45
|
-
},
|
|
46
|
-
py: 0.5,
|
|
47
|
-
borderRadius: 2,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const primaryButtonStyle = {
|
|
51
|
-
color: colors.buttonsButtonNeutral,
|
|
52
|
-
borderColor: colors.foregroundsFgInteractive,
|
|
53
|
-
backgroundColor: colors.foregroundsFgInteractive,
|
|
54
|
-
'&:hover': {
|
|
55
|
-
borderColor: colors.foregroundsFgInteractive,
|
|
56
|
-
backgroundColor: colors.foregroundsFgInteractive,
|
|
57
|
-
},
|
|
58
|
-
py: 0.5,
|
|
59
|
-
borderRadius: 2,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
35
|
const iconSize = {
|
|
63
36
|
width: 20,
|
|
64
37
|
height: 20,
|
|
@@ -201,6 +174,9 @@ export default function UserMetadataComponent({
|
|
|
201
174
|
if (k === 'bio') {
|
|
202
175
|
metadata[k] = metadata[k]?.slice(0, bioMaxLength);
|
|
203
176
|
}
|
|
177
|
+
if (k === 'timezone') {
|
|
178
|
+
metadata[k] = (value as string) || currentTimezone;
|
|
179
|
+
}
|
|
204
180
|
});
|
|
205
181
|
|
|
206
182
|
onSave(metadata);
|
|
@@ -219,6 +195,7 @@ export default function UserMetadataComponent({
|
|
|
219
195
|
component="textarea"
|
|
220
196
|
inline={false}
|
|
221
197
|
rows={3}
|
|
198
|
+
label={t('profile.bio')}
|
|
222
199
|
maxLength={bioMaxLength}
|
|
223
200
|
style={{
|
|
224
201
|
...(editing ? { marginBottom: 8 } : {}),
|
|
@@ -249,7 +226,7 @@ export default function UserMetadataComponent({
|
|
|
249
226
|
/>
|
|
250
227
|
|
|
251
228
|
<EditableField
|
|
252
|
-
value={metadata.timezone
|
|
229
|
+
value={metadata.timezone || currentTimezone}
|
|
253
230
|
onChange={(value) => onChange(value, 'timezone')}
|
|
254
231
|
editable={editing}
|
|
255
232
|
placeholder="timezone"
|
|
@@ -261,48 +238,17 @@ export default function UserMetadataComponent({
|
|
|
261
238
|
<Clock timezone={metadata.timezone} locale={locale} />
|
|
262
239
|
</p>
|
|
263
240
|
}>
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
onChange={(e: SelectChangeEvent) => onChange(e.target.value, 'timezone')}
|
|
241
|
+
<TimezoneSelect
|
|
242
|
+
value={metadata.timezone || currentTimezone}
|
|
243
|
+
onChange={(value) => onChange(value, 'timezone')}
|
|
268
244
|
disabled={!editing}
|
|
269
|
-
|
|
270
|
-
variant="outlined"
|
|
271
|
-
placeholder="Timezone"
|
|
272
|
-
IconComponent={(props) => <ArrowDownwardIcon {...props} width={20} height={20} />}
|
|
273
|
-
MenuProps={{
|
|
274
|
-
PaperProps: {
|
|
275
|
-
style: {
|
|
276
|
-
maxHeight: '400px',
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
style: {
|
|
280
|
-
zIndex: mode === 'drawer' ? 9999 : 1300,
|
|
281
|
-
},
|
|
282
|
-
}}
|
|
283
|
-
sx={{
|
|
284
|
-
width: '100%',
|
|
285
|
-
|
|
286
|
-
'&:hover': {
|
|
287
|
-
'fieldset.MuiOutlinedInput-notchedOutline': {
|
|
288
|
-
borderColor: colors.dividerColor,
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
fieldset: {
|
|
292
|
-
borderColor: colors.dividerColor,
|
|
293
|
-
},
|
|
294
|
-
}}>
|
|
295
|
-
{timezones.map((tz) => (
|
|
296
|
-
<MenuItem key={tz.value} value={tz.value}>
|
|
297
|
-
{tz.label}
|
|
298
|
-
</MenuItem>
|
|
299
|
-
))}
|
|
300
|
-
</Select>
|
|
245
|
+
/>
|
|
301
246
|
</EditableField>
|
|
302
247
|
|
|
303
248
|
<EditableField
|
|
304
249
|
value={metadata.email ?? user?.email ?? ''}
|
|
305
|
-
editable={editing
|
|
250
|
+
editable={editing}
|
|
251
|
+
canEdit={!emailVerified}
|
|
306
252
|
verified={emailVerified}
|
|
307
253
|
placeholder="Email"
|
|
308
254
|
icon={<EmailIcon {...iconSize} />}
|
|
@@ -320,7 +266,8 @@ export default function UserMetadataComponent({
|
|
|
320
266
|
|
|
321
267
|
<EditableField
|
|
322
268
|
value={metadata.phone ?? user?.phone ?? ''}
|
|
323
|
-
editable={editing
|
|
269
|
+
editable={editing}
|
|
270
|
+
canEdit={!phoneVerified}
|
|
324
271
|
verified={phoneVerified}
|
|
325
272
|
placeholder="Phone"
|
|
326
273
|
icon={<PhoneIcon {...iconSize} />}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
3
|
+
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
|
4
|
+
import ListSubheader from '@mui/material/ListSubheader';
|
|
5
|
+
import TextField from '@mui/material/TextField';
|
|
6
|
+
import debounce from 'lodash/debounce';
|
|
7
|
+
import { temp as colors } from '@arcblock/ux/lib/Colors';
|
|
8
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
|
+
import ArrowDownwardIcon from '@arcblock/icons/lib/ArrowDown';
|
|
10
|
+
import { getTimezones } from './utils';
|
|
11
|
+
|
|
12
|
+
const timezones = getTimezones();
|
|
13
|
+
|
|
14
|
+
interface TimezoneSelectProps {
|
|
15
|
+
value: string;
|
|
16
|
+
onChange: (value: string) => void;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
mode?: 'drawer' | 'self';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function TimezoneSelect({ value, onChange, disabled = false, mode = 'self' }: TimezoneSelectProps) {
|
|
22
|
+
const [timezoneData, setTimezoneData] = useState(timezones);
|
|
23
|
+
const [searchText, setSearchText] = useState('');
|
|
24
|
+
|
|
25
|
+
const timezoneDebounce = useMemo(
|
|
26
|
+
() =>
|
|
27
|
+
debounce((v: string) => {
|
|
28
|
+
return v ? timezones.filter((tz) => tz.value.toLowerCase().includes(v.toLowerCase())) : timezones;
|
|
29
|
+
}, 300),
|
|
30
|
+
[]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const data = timezoneDebounce(searchText);
|
|
35
|
+
setTimezoneData(data ?? timezones);
|
|
36
|
+
}, [searchText, timezoneDebounce]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
return () => {
|
|
40
|
+
timezoneDebounce.cancel();
|
|
41
|
+
};
|
|
42
|
+
}, [timezoneDebounce]);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Select
|
|
46
|
+
className={`timezone-select ${disabled ? 'disabled' : ''}`}
|
|
47
|
+
value={value}
|
|
48
|
+
onChange={(e: SelectChangeEvent) => onChange(e.target.value)}
|
|
49
|
+
disabled={disabled}
|
|
50
|
+
displayEmpty
|
|
51
|
+
variant="outlined"
|
|
52
|
+
placeholder="Timezone"
|
|
53
|
+
// eslint-disable-next-line react/no-unstable-nested-components
|
|
54
|
+
IconComponent={(props) => <ArrowDownwardIcon {...props} width={20} height={20} />}
|
|
55
|
+
MenuProps={{
|
|
56
|
+
PaperProps: {
|
|
57
|
+
style: {
|
|
58
|
+
maxHeight: '400px',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
style: {
|
|
62
|
+
zIndex: mode === 'drawer' ? 9999 : 1300,
|
|
63
|
+
},
|
|
64
|
+
}}
|
|
65
|
+
sx={{
|
|
66
|
+
width: '100%',
|
|
67
|
+
'&:hover': {
|
|
68
|
+
'fieldset.MuiOutlinedInput-notchedOutline': {
|
|
69
|
+
borderColor: colors.dividerColor,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
'fieldset.MuiOutlinedInput-notchedOutline': {
|
|
73
|
+
borderColor: colors.dividerColor,
|
|
74
|
+
},
|
|
75
|
+
}}>
|
|
76
|
+
<ListSubheader>
|
|
77
|
+
<TextField
|
|
78
|
+
autoFocus
|
|
79
|
+
value={searchText}
|
|
80
|
+
placeholder="Timezone"
|
|
81
|
+
variant="outlined"
|
|
82
|
+
size="small"
|
|
83
|
+
fullWidth
|
|
84
|
+
onChange={(event) => setSearchText(event.target.value)}
|
|
85
|
+
onKeyDown={(e) => {
|
|
86
|
+
if (e.key !== 'Escape') {
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
}
|
|
89
|
+
}}
|
|
90
|
+
sx={{
|
|
91
|
+
marginTop: '8px',
|
|
92
|
+
'& .MuiOutlinedInput-root': {
|
|
93
|
+
'& fieldset': {
|
|
94
|
+
borderColor: colors.dividerColor,
|
|
95
|
+
borderWidth: '1px',
|
|
96
|
+
},
|
|
97
|
+
'&:hover fieldset': {
|
|
98
|
+
borderColor: colors.dividerColor,
|
|
99
|
+
},
|
|
100
|
+
'&.Mui-focused fieldset': {
|
|
101
|
+
borderColor: colors.dividerColor,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
</ListSubheader>
|
|
107
|
+
{timezoneData.map((tz) => (
|
|
108
|
+
<MenuItem key={tz.value} value={tz.value}>
|
|
109
|
+
{tz.label}
|
|
110
|
+
</MenuItem>
|
|
111
|
+
))}
|
|
112
|
+
</Select>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -15,7 +15,7 @@ import { parseURL, joinURL } from 'ufo';
|
|
|
15
15
|
import { translations } from '../../libs/locales';
|
|
16
16
|
import type { User, UserMetadata } from '../../../@types';
|
|
17
17
|
import { formatAxiosError } from '../../libs/utils';
|
|
18
|
-
import { getStatusDuration, isValidUrl } from './utils';
|
|
18
|
+
import { currentTimezone, getStatusDuration, isValidUrl } from './utils';
|
|
19
19
|
import SwitchRole from './switch-role';
|
|
20
20
|
import UserMetadataComponent from './metadata';
|
|
21
21
|
import UserStatus from './user-status';
|
|
@@ -63,7 +63,7 @@ export default function UserBasicInfo({
|
|
|
63
63
|
// @ts-ignore
|
|
64
64
|
metadata: {
|
|
65
65
|
...(user?.metadata ?? { joinedAt: user?.createdAt, email: user?.email, phone: user?.phone }),
|
|
66
|
-
status: v,
|
|
66
|
+
status: v || {},
|
|
67
67
|
},
|
|
68
68
|
});
|
|
69
69
|
} catch (err) {
|
|
@@ -162,6 +162,7 @@ export default function UserBasicInfo({
|
|
|
162
162
|
isMobile={isMobile}
|
|
163
163
|
size={rest.size || (isMobile ? 64 : 100)}
|
|
164
164
|
isMyself={isMyself}
|
|
165
|
+
timezone={user?.metadata?.timezone || currentTimezone}
|
|
165
166
|
status={userStatus}
|
|
166
167
|
onChange={onUpdateUserStatus}
|
|
167
168
|
/>
|
|
@@ -183,15 +184,7 @@ export default function UserBasicInfo({
|
|
|
183
184
|
{user?.fullName}
|
|
184
185
|
{isMyself ? <SwitchRole user={user} switchPassport={switchPassport} /> : null}
|
|
185
186
|
</Typography>
|
|
186
|
-
<DID
|
|
187
|
-
did={user.did}
|
|
188
|
-
showQrcode
|
|
189
|
-
copyable
|
|
190
|
-
compact={!showFullDid}
|
|
191
|
-
responsive={!showFullDid}
|
|
192
|
-
locale={locale}
|
|
193
|
-
style={{ maxWidth: 260 }}
|
|
194
|
-
/>
|
|
187
|
+
<DID did={user.did} showQrcode copyable compact={!showFullDid} responsive={!showFullDid} locale={locale} />
|
|
195
188
|
</Box>
|
|
196
189
|
</Box>
|
|
197
190
|
<UserMetadataComponent isMyself={isMyself} user={user} onSave={onSave} />
|