@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.
Files changed (41) hide show
  1. package/lib/@types/index.d.ts +2 -1
  2. package/lib/@types/index.js +1 -0
  3. package/lib/Footer/links.js +2 -1
  4. package/lib/Header/index.js +2 -1
  5. package/lib/UserCenter/components/editable-field.d.ts +2 -1
  6. package/lib/UserCenter/components/editable-field.js +5 -1
  7. package/lib/UserCenter/components/status-dialog/date-picker.d.ts +10 -0
  8. package/lib/UserCenter/components/status-dialog/date-picker.js +52 -0
  9. package/lib/UserCenter/components/status-dialog/index.d.ts +12 -0
  10. package/lib/UserCenter/components/status-dialog/index.js +238 -0
  11. package/lib/UserCenter/components/status-selector/menu-item.d.ts +2 -0
  12. package/lib/UserCenter/components/user-center.js +8 -3
  13. package/lib/UserCenter/components/user-info/clock.js +3 -2
  14. package/lib/UserCenter/components/user-info/metadata.js +15 -62
  15. package/lib/UserCenter/components/user-info/timezone-select.d.ts +8 -0
  16. package/lib/UserCenter/components/user-info/timezone-select.js +99 -0
  17. package/lib/UserCenter/components/user-info/user-basic-info.js +4 -14
  18. package/lib/UserCenter/components/user-info/user-status.d.ts +2 -1
  19. package/lib/UserCenter/components/user-info/user-status.js +49 -14
  20. package/lib/UserCenter/components/user-info/utils.d.ts +23 -0
  21. package/lib/UserCenter/components/user-info/utils.js +25 -1
  22. package/lib/UserCenter/libs/locales.d.ts +12 -0
  23. package/lib/UserCenter/libs/locales.js +18 -6
  24. package/lib/blocklets.js +7 -9
  25. package/package.json +4 -4
  26. package/src/@types/index.ts +1 -0
  27. package/src/Footer/links.jsx +2 -1
  28. package/src/Header/index.tsx +7 -1
  29. package/src/UserCenter/components/editable-field.tsx +6 -0
  30. package/src/UserCenter/components/status-dialog/date-picker.tsx +67 -0
  31. package/src/UserCenter/components/status-dialog/index.tsx +280 -0
  32. package/src/UserCenter/components/status-selector/menu-item.tsx +2 -0
  33. package/src/UserCenter/components/user-center.tsx +7 -2
  34. package/src/UserCenter/components/user-info/clock.tsx +3 -2
  35. package/src/UserCenter/components/user-info/metadata.tsx +17 -70
  36. package/src/UserCenter/components/user-info/timezone-select.tsx +114 -0
  37. package/src/UserCenter/components/user-info/user-basic-info.tsx +4 -11
  38. package/src/UserCenter/components/user-info/user-status.tsx +57 -13
  39. package/src/UserCenter/components/user-info/utils.ts +29 -1
  40. package/src/UserCenter/libs/locales.ts +14 -2
  41. 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
+ }
@@ -43,6 +43,8 @@ export interface StatusItem {
43
43
  id: string;
44
44
  name: string;
45
45
  icon?: React.FC<SvgIconProps>;
46
+ duration?: string;
47
+ durationName?: string;
46
48
  children?: Array<StatusItem>;
47
49
  }
48
50
 
@@ -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._rawLink === '/payment-kit/customer', // FIXME: HACK: 隐藏 payment-kit/customer 菜单, 需要一个通用的解决方案,在嵌入的时候就决定是否是私有的
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 = 'utc', locale = 'zh' }: { timezone?: string; locale?: string }) {
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
- import { temp as colors } from '@arcblock/ux/lib/Colors';
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 { getTimezones } from './utils';
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
- <Select
265
- className={`timezone-select ${editing ? '' : 'disabled'}`}
266
- value={metadata.timezone}
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
- displayEmpty
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 && !emailVerified}
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 && !phoneVerified}
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} />