@blocklet/ui-react 2.12.8 → 2.12.10
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 +34 -0
- package/lib/@types/index.js +16 -0
- package/lib/@types/shims.d.ts +1 -0
- package/lib/UserCenter/components/config-profile.js +23 -1
- package/lib/UserCenter/components/editable-field.d.ts +22 -0
- package/lib/UserCenter/components/editable-field.js +159 -0
- package/lib/UserCenter/components/nft.d.ts +4 -0
- package/lib/UserCenter/components/nft.js +93 -0
- package/lib/UserCenter/components/settings.js +32 -15
- package/lib/UserCenter/components/status-selector/duration-menu.d.ts +9 -0
- package/lib/UserCenter/components/status-selector/duration-menu.js +75 -0
- package/lib/UserCenter/components/status-selector/index.d.ts +9 -0
- package/lib/UserCenter/components/status-selector/index.js +39 -0
- package/lib/UserCenter/components/status-selector/menu-item.d.ts +24 -0
- package/lib/UserCenter/components/status-selector/menu-item.js +24 -0
- package/lib/UserCenter/components/user-center.js +119 -122
- package/lib/UserCenter/components/user-info/clock.d.ts +4 -0
- package/lib/UserCenter/components/user-info/clock.js +23 -0
- package/lib/UserCenter/components/user-info/link-preview-input.d.ts +5 -0
- package/lib/UserCenter/components/user-info/link-preview-input.js +181 -0
- package/lib/UserCenter/components/user-info/metadata.d.ts +7 -0
- package/lib/UserCenter/components/user-info/metadata.js +458 -0
- package/lib/UserCenter/components/user-info/switch-role.js +2 -3
- package/lib/UserCenter/components/user-info/user-basic-info.d.ts +2 -0
- package/lib/UserCenter/components/user-info/user-basic-info.js +159 -90
- package/lib/UserCenter/components/user-info/user-info.js +2 -16
- package/lib/UserCenter/components/user-info/user-status.d.ts +8 -0
- package/lib/UserCenter/components/user-info/user-status.js +153 -0
- package/lib/UserCenter/components/user-info/utils.d.ts +19 -0
- package/lib/UserCenter/components/user-info/utils.js +86 -0
- package/lib/UserCenter/libs/locales.d.ts +65 -0
- package/lib/UserCenter/libs/locales.js +67 -2
- package/lib/UserSessions/components/user-sessions.js +48 -14
- package/package.json +8 -5
- package/src/@types/index.ts +39 -0
- package/src/@types/shims.d.ts +1 -0
- package/src/UserCenter/components/config-profile.tsx +20 -1
- package/src/UserCenter/components/editable-field.tsx +180 -0
- package/src/UserCenter/components/nft.tsx +122 -0
- package/src/UserCenter/components/settings.tsx +16 -4
- package/src/UserCenter/components/status-selector/duration-menu.tsx +87 -0
- package/src/UserCenter/components/status-selector/index.tsx +52 -0
- package/src/UserCenter/components/status-selector/menu-item.tsx +52 -0
- package/src/UserCenter/components/user-center.tsx +104 -103
- package/src/UserCenter/components/user-info/clock.tsx +29 -0
- package/src/UserCenter/components/user-info/link-preview-input.tsx +227 -0
- package/src/UserCenter/components/user-info/metadata.tsx +465 -0
- package/src/UserCenter/components/user-info/switch-role.tsx +3 -3
- package/src/UserCenter/components/user-info/user-basic-info.tsx +150 -87
- package/src/UserCenter/components/user-info/user-info.tsx +6 -16
- package/src/UserCenter/components/user-info/user-status.tsx +182 -0
- package/src/UserCenter/components/user-info/utils.ts +114 -0
- package/src/UserCenter/libs/locales.ts +65 -0
- package/src/UserSessions/components/user-sessions.tsx +68 -18
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Box, Pagination, Skeleton, Typography } from '@mui/material';
|
|
2
|
+
import { useCreation, useMemoizedFn, useReactive, useRequest } from 'ahooks';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { temp as colors } from '@arcblock/ux/lib/Colors';
|
|
5
|
+
import NFTDisplay from '@arcblock/ux/lib/NFTDisplay';
|
|
6
|
+
import Empty from '@arcblock/ux/lib/Empty';
|
|
7
|
+
import { WELLKNOWN_SERVICE_PATH_PREFIX } from '@abtnode/constant';
|
|
8
|
+
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
9
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
10
|
+
import { translations } from '../libs/locales';
|
|
11
|
+
import { User } from '../../@types';
|
|
12
|
+
|
|
13
|
+
interface NftInfo {
|
|
14
|
+
address: string;
|
|
15
|
+
data: Record<string, any>;
|
|
16
|
+
display: Record<string, any>;
|
|
17
|
+
issuer: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ResponseData {
|
|
21
|
+
assets: NftInfo[];
|
|
22
|
+
code: string;
|
|
23
|
+
page: { cursor: string; next: boolean; total: number };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PaginationProps {
|
|
27
|
+
page: number;
|
|
28
|
+
size: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function Nft({ user }: { user: User }) {
|
|
32
|
+
const { locale } = useLocaleContext();
|
|
33
|
+
const t = useMemoizedFn((key, data = {}) => {
|
|
34
|
+
return translate(translations, key, locale, 'en', data);
|
|
35
|
+
});
|
|
36
|
+
const paging = useReactive<PaginationProps>({
|
|
37
|
+
page: 1,
|
|
38
|
+
size: 20,
|
|
39
|
+
});
|
|
40
|
+
const userState = useRequest<ResponseData, [PaginationProps]>(
|
|
41
|
+
async (pagination: PaginationProps = paging) => {
|
|
42
|
+
const response = await axios.get(`${WELLKNOWN_SERVICE_PATH_PREFIX}/ocap/listAssets`, {
|
|
43
|
+
params: {
|
|
44
|
+
ownerAddress: user.did,
|
|
45
|
+
...pagination,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return response.data;
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
defaultParams: [paging],
|
|
52
|
+
refreshDeps: [user.did, paging],
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const { loading, data } = userState;
|
|
57
|
+
|
|
58
|
+
const dataPage = data?.page ?? { cursor: 0, next: false, total: 0 };
|
|
59
|
+
|
|
60
|
+
const handlePageChange = (event: React.ChangeEvent<unknown>, value: number) => {
|
|
61
|
+
paging.page = value;
|
|
62
|
+
userState.run(paging);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const content = useCreation(() => {
|
|
66
|
+
if (loading) {
|
|
67
|
+
return (
|
|
68
|
+
<Box display="flex" flexDirection="column" gap={2}>
|
|
69
|
+
<Skeleton width="20%" />
|
|
70
|
+
<Skeleton variant="rectangular" height={200} sx={{ borderRadius: 2 }} />
|
|
71
|
+
</Box>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<Typography
|
|
77
|
+
sx={{
|
|
78
|
+
color: colors.foregroundsFgBase,
|
|
79
|
+
fontWeight: 600,
|
|
80
|
+
mb: 2.5,
|
|
81
|
+
}}>
|
|
82
|
+
{t('common.nft')}
|
|
83
|
+
</Typography>
|
|
84
|
+
<Box className="nft-list-wrapper" display="flex" flexDirection="row" gap={2}>
|
|
85
|
+
{data?.assets?.map((item) => (
|
|
86
|
+
<Box key={item.address} width={166} height={166}>
|
|
87
|
+
<NFTDisplay
|
|
88
|
+
data={item.display}
|
|
89
|
+
address={item.address}
|
|
90
|
+
inset
|
|
91
|
+
imageFilter={{
|
|
92
|
+
imageFilter: 'resize',
|
|
93
|
+
w: '500',
|
|
94
|
+
f: 'webp',
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</Box>
|
|
98
|
+
))}
|
|
99
|
+
{data?.assets?.length === 0 && !loading && (
|
|
100
|
+
<Box display="flex" justifyContent="center" alignItems="center" width="100%" height="100%">
|
|
101
|
+
<Empty>{t('common.noNFT')}</Empty>
|
|
102
|
+
</Box>
|
|
103
|
+
)}
|
|
104
|
+
</Box>
|
|
105
|
+
{dataPage.next || paging.page > 1 ? (
|
|
106
|
+
<Pagination
|
|
107
|
+
sx={{
|
|
108
|
+
display: 'flex',
|
|
109
|
+
justifyContent: 'end',
|
|
110
|
+
}}
|
|
111
|
+
page={paging.page}
|
|
112
|
+
onChange={handlePageChange}
|
|
113
|
+
count={Math.ceil(dataPage.total / paging.size)}
|
|
114
|
+
size="small"
|
|
115
|
+
/>
|
|
116
|
+
) : null}
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
}, [loading, dataPage, paging.page, paging.size, handlePageChange]);
|
|
120
|
+
|
|
121
|
+
return <Box sx={{ border: `1px solid ${colors.dividerColor}`, borderRadius: 2, p: 2, mb: 5 }}>{content}</Box>;
|
|
122
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import { Box,
|
|
2
|
+
import { Box, Typography } from '@mui/material';
|
|
3
3
|
import type { BoxProps } from '@mui/material';
|
|
4
4
|
import { useCreation, useMemoizedFn } from 'ahooks';
|
|
5
5
|
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
@@ -85,13 +85,26 @@ export default function Settings({
|
|
|
85
85
|
{...rest}
|
|
86
86
|
sx={{
|
|
87
87
|
...rest?.sx,
|
|
88
|
+
display: 'flex',
|
|
89
|
+
flexDirection: 'column',
|
|
90
|
+
gap: 2.5,
|
|
88
91
|
minWidth: {
|
|
89
92
|
md: 500,
|
|
90
93
|
},
|
|
91
94
|
maxWidth: '100%',
|
|
92
95
|
}}>
|
|
93
|
-
{tabs.map((tab
|
|
94
|
-
<Box
|
|
96
|
+
{tabs.map((tab) => (
|
|
97
|
+
<Box
|
|
98
|
+
id={tab.value}
|
|
99
|
+
key={tab.value}
|
|
100
|
+
sx={{
|
|
101
|
+
border: `1px solid ${colors.dividerColor}`,
|
|
102
|
+
borderRadius: 2,
|
|
103
|
+
p: 2,
|
|
104
|
+
'&:last-child': {
|
|
105
|
+
mb: 5,
|
|
106
|
+
},
|
|
107
|
+
}}>
|
|
95
108
|
<Typography
|
|
96
109
|
sx={{
|
|
97
110
|
color: colors.foregroundsFgBase,
|
|
@@ -100,7 +113,6 @@ export default function Settings({
|
|
|
100
113
|
{tab.label}
|
|
101
114
|
</Typography>
|
|
102
115
|
<Box mt={2.5}>{tab.content}</Box>
|
|
103
|
-
{index < tabs.length - 1 ? <Divider sx={{ mt: 2.5, mb: 2.5, borderColor: colors.dividerColor }} /> : null}
|
|
104
116
|
</Box>
|
|
105
117
|
))}
|
|
106
118
|
</Box>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Menu, Typography } from '@mui/material';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { temp as colors } from '@arcblock/ux/lib/Colors';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
6
|
+
import { useMemoizedFn } from 'ahooks';
|
|
7
|
+
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
8
|
+
import { translations } from '../../libs/locales';
|
|
9
|
+
import { DurationEnum } from '../../../@types';
|
|
10
|
+
import StatusMenuItem, { BaseStatusProps, StatusItem, StyledMenu } from './menu-item';
|
|
11
|
+
|
|
12
|
+
interface StatusDurationMenuProps extends BaseStatusProps {
|
|
13
|
+
data: StatusItem;
|
|
14
|
+
}
|
|
15
|
+
function DurationMenu({ data, selected, onSelect }: StatusDurationMenuProps) {
|
|
16
|
+
const { locale } = useLocaleContext();
|
|
17
|
+
const t = useMemoizedFn((key, _data = {}) => {
|
|
18
|
+
return translate(translations, key, locale, 'en', _data);
|
|
19
|
+
});
|
|
20
|
+
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
21
|
+
|
|
22
|
+
const open = Boolean(anchorEl);
|
|
23
|
+
|
|
24
|
+
const openSubMenu = (e: React.MouseEvent<HTMLElement>) => {
|
|
25
|
+
setAnchorEl(e.currentTarget);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const closeSubMenu = () => {
|
|
29
|
+
setAnchorEl(null);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const onSelectStatus = (e: React.MouseEvent<HTMLElement>) => {
|
|
33
|
+
openSubMenu(e);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const onSelectDuration = (e: React.MouseEvent<HTMLElement>, item: StatusItem) => {
|
|
37
|
+
onSelect({
|
|
38
|
+
...(selected ?? {}),
|
|
39
|
+
value: data.id,
|
|
40
|
+
duration: item.id as DurationEnum,
|
|
41
|
+
});
|
|
42
|
+
closeSubMenu();
|
|
43
|
+
};
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
<StatusMenuItem icon={data.icon} selected={selected?.value === data.id} onClick={onSelectStatus}>
|
|
47
|
+
{data.name}
|
|
48
|
+
</StatusMenuItem>
|
|
49
|
+
|
|
50
|
+
<StyledMenu
|
|
51
|
+
anchorOrigin={{
|
|
52
|
+
vertical: 'top',
|
|
53
|
+
horizontal: 'right',
|
|
54
|
+
}}
|
|
55
|
+
transformOrigin={{
|
|
56
|
+
vertical: 'top',
|
|
57
|
+
horizontal: 'left',
|
|
58
|
+
}}
|
|
59
|
+
open={open}
|
|
60
|
+
onClose={closeSubMenu}
|
|
61
|
+
anchorEl={anchorEl}>
|
|
62
|
+
<Typography component="span" color="text.secondary" pl={2} fontSize="14px">
|
|
63
|
+
{t('profile.removeStatusAfter')}
|
|
64
|
+
</Typography>
|
|
65
|
+
{data.children?.map((item) => (
|
|
66
|
+
<StatusMenuItem
|
|
67
|
+
key={item.id}
|
|
68
|
+
selected={selected?.duration === item.id}
|
|
69
|
+
onClick={(e) => onSelectDuration(e, item)}>
|
|
70
|
+
{item.name}
|
|
71
|
+
</StatusMenuItem>
|
|
72
|
+
))}
|
|
73
|
+
</StyledMenu>
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default DurationMenu;
|
|
79
|
+
|
|
80
|
+
export const MenuDiv = styled(Menu)`
|
|
81
|
+
.MuiList-root {
|
|
82
|
+
min-width: 160px;
|
|
83
|
+
}
|
|
84
|
+
.selected {
|
|
85
|
+
background-color: ${colors.backgroundsBgSubtitle};
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PopoverProps, Typography } from '@mui/material';
|
|
2
|
+
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
3
|
+
import { useMemoizedFn } from 'ahooks';
|
|
4
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
5
|
+
import { UserMetadata } from '../../../@types';
|
|
6
|
+
import StatusDurationMenu from './duration-menu';
|
|
7
|
+
import StatusMenuItem, { BaseStatusProps, StatusItem, StyledMenu } from './menu-item';
|
|
8
|
+
import { translations } from '../../libs/locales';
|
|
9
|
+
|
|
10
|
+
interface StatusSelectorProps extends BaseStatusProps {
|
|
11
|
+
data: Array<StatusItem>;
|
|
12
|
+
open: boolean;
|
|
13
|
+
anchorEl?: PopoverProps['anchorEl'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function StatusSelector({ data, open, onSelect, anchorEl, selected }: StatusSelectorProps) {
|
|
17
|
+
const { locale } = useLocaleContext();
|
|
18
|
+
const t = useMemoizedFn((key, _data = {}) => {
|
|
19
|
+
return translate(translations, key, locale, 'en', _data);
|
|
20
|
+
});
|
|
21
|
+
const onSelectStatus = (v?: UserMetadata['status']) => {
|
|
22
|
+
onSelect(v);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const renderMenuItems = () => {
|
|
26
|
+
return data.map((item) => {
|
|
27
|
+
if (item.children) {
|
|
28
|
+
return <StatusDurationMenu key={item.id} data={item} selected={selected} onSelect={onSelectStatus} />;
|
|
29
|
+
}
|
|
30
|
+
return (
|
|
31
|
+
<StatusMenuItem
|
|
32
|
+
key={item.id}
|
|
33
|
+
icon={item.icon}
|
|
34
|
+
selected={selected?.value === item.id}
|
|
35
|
+
onClick={() => onSelectStatus({ value: item.id })}>
|
|
36
|
+
{item.name}
|
|
37
|
+
</StatusMenuItem>
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<StyledMenu open={open} onClose={() => onSelectStatus()} anchorEl={anchorEl}>
|
|
44
|
+
<Typography component="span" color="text.secondary" pl={2} fontSize="14px">
|
|
45
|
+
{t('profile.setStatus')}
|
|
46
|
+
</Typography>
|
|
47
|
+
{renderMenuItems()}
|
|
48
|
+
</StyledMenu>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default StatusSelector;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ListItemIcon, MenuItem, Menu } from '@mui/material';
|
|
2
|
+
import { SvgIconProps } from '@mui/material/SvgIcon';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import styled from '@emotion/styled';
|
|
5
|
+
import { temp as colors } from '@arcblock/ux/lib/Colors';
|
|
6
|
+
import { UserMetadata } from '../../../@types';
|
|
7
|
+
|
|
8
|
+
interface StatusMenuItemProps {
|
|
9
|
+
icon?: React.FC<SvgIconProps>;
|
|
10
|
+
selected?: boolean;
|
|
11
|
+
onClick: (e: React.MouseEvent<HTMLElement>) => void;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function StatusMenuItem({ icon, selected, onClick, children }: StatusMenuItemProps) {
|
|
16
|
+
return (
|
|
17
|
+
<MenuItem onClick={onClick} className={selected ? 'selected' : ''}>
|
|
18
|
+
{icon && (
|
|
19
|
+
<ListItemIcon style={{ minWidth: '24px' }}>
|
|
20
|
+
{React.createElement(icon, {
|
|
21
|
+
style: { fontSize: '16px', width: '16px', height: '16px' },
|
|
22
|
+
})}
|
|
23
|
+
</ListItemIcon>
|
|
24
|
+
)}
|
|
25
|
+
{children}
|
|
26
|
+
</MenuItem>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const StyledMenu = styled(Menu)`
|
|
31
|
+
.MuiPaper-root {
|
|
32
|
+
box-shadow: 0px 6px 24px rgba(0, 0, 0, 0.15);
|
|
33
|
+
}
|
|
34
|
+
.MuiList-root {
|
|
35
|
+
min-width: 160px;
|
|
36
|
+
}
|
|
37
|
+
.selected {
|
|
38
|
+
background-color: ${colors.backgroundsBgSubtitle};
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
export interface StatusItem {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
icon?: React.FC<SvgIconProps>;
|
|
46
|
+
children?: Array<StatusItem>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface BaseStatusProps {
|
|
50
|
+
selected: UserMetadata['status'];
|
|
51
|
+
onSelect: (v: UserMetadata['status']) => void;
|
|
52
|
+
}
|
|
@@ -24,7 +24,7 @@ import { PROFILE_URL } from '@arcblock/ux/lib/Util/constant';
|
|
|
24
24
|
import Footer from '../../Footer';
|
|
25
25
|
import Header from '../../Header';
|
|
26
26
|
import { translations } from '../libs/locales';
|
|
27
|
-
import {
|
|
27
|
+
import { UserBasicInfo } from './user-info';
|
|
28
28
|
import type { SessionContext as TSessionContext, User, UserCenterTab } from '../../@types';
|
|
29
29
|
// @ts-ignore
|
|
30
30
|
import { formatBlockletInfo, getLink, getLocalizedNavigation } from '../../blocklets';
|
|
@@ -34,17 +34,18 @@ import { client } from '../../libs/client';
|
|
|
34
34
|
import useMobile from '../../hooks/use-mobile';
|
|
35
35
|
import { ConfigUserSpaceProvider } from '../../contexts/config-user-space';
|
|
36
36
|
import DidSpace from './storage';
|
|
37
|
+
import Nft from './nft';
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
+
const nftsLink = joinURL(PROFILE_URL, '/nfts');
|
|
39
40
|
const settingsLink = joinURL(PROFILE_URL, '/settings');
|
|
40
41
|
const didSpacesLink = joinURL(PROFILE_URL, '/did-spaces');
|
|
41
42
|
|
|
42
43
|
const Main = styled(Box)(({ theme }) => ({
|
|
43
44
|
flex: 1,
|
|
44
45
|
boxSizing: 'border-box',
|
|
45
|
-
padding:
|
|
46
|
+
padding: '0 16px',
|
|
46
47
|
width: '100%',
|
|
47
|
-
maxWidth:
|
|
48
|
+
maxWidth: 1600,
|
|
48
49
|
margin: '0 auto',
|
|
49
50
|
display: 'flex',
|
|
50
51
|
alignItems: 'stretch',
|
|
@@ -184,29 +185,31 @@ export default function UserCenter({
|
|
|
184
185
|
}, []);
|
|
185
186
|
|
|
186
187
|
const defaultTabs = useCreation((): any => {
|
|
187
|
-
|
|
188
|
-
{
|
|
189
|
-
label: t('common.profile'),
|
|
190
|
-
protected: false,
|
|
191
|
-
value: profileLink,
|
|
192
|
-
url: getLink(profileLink, locale),
|
|
193
|
-
},
|
|
194
|
-
];
|
|
188
|
+
let tabs: any = [];
|
|
195
189
|
if (isMyself) {
|
|
196
|
-
tabs
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
190
|
+
tabs = [
|
|
191
|
+
{
|
|
192
|
+
label: t('common.nft'),
|
|
193
|
+
protected: true,
|
|
194
|
+
isPrivate: true, // 隐私数据,仅自己可见
|
|
195
|
+
value: nftsLink,
|
|
196
|
+
url: getLink(nftsLink, locale),
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
label: t('common.setting'),
|
|
200
|
+
protected: true,
|
|
201
|
+
isPrivate: true,
|
|
202
|
+
value: settingsLink,
|
|
203
|
+
url: getLink(settingsLink, locale),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
label: t('storageManagement'),
|
|
207
|
+
protected: true,
|
|
208
|
+
isPrivate: true,
|
|
209
|
+
value: didSpacesLink,
|
|
210
|
+
url: getLink(didSpacesLink, locale),
|
|
211
|
+
},
|
|
212
|
+
];
|
|
210
213
|
}
|
|
211
214
|
return tabs;
|
|
212
215
|
}, [isMyself, locale]);
|
|
@@ -224,7 +227,7 @@ export default function UserCenter({
|
|
|
224
227
|
label: x.title || x.label,
|
|
225
228
|
url: x.link || x.url,
|
|
226
229
|
protected: privacyState?.data?.[value] ?? false,
|
|
227
|
-
isPrivate: x.isPrivate,
|
|
230
|
+
isPrivate: x.isPrivate || x._rawLink === '/payment-kit/customer', // FIXME: HACK: 隐藏 payment-kit/customer 菜单, 需要一个通用的解决方案,在嵌入的时候就决定是否是私有的
|
|
228
231
|
// icon: x.icon,
|
|
229
232
|
};
|
|
230
233
|
})
|
|
@@ -268,8 +271,11 @@ export default function UserCenter({
|
|
|
268
271
|
return currentActiveTab && currentActiveTab?.value === joinURL(PROFILE_URL, '/settings');
|
|
269
272
|
}, [currentActiveTab]);
|
|
270
273
|
|
|
271
|
-
const
|
|
272
|
-
return
|
|
274
|
+
const isNftsTab = useCreation(() => {
|
|
275
|
+
return (
|
|
276
|
+
(currentActiveTab && currentActiveTab?.value === joinURL(PROFILE_URL, '/profile')) ||
|
|
277
|
+
currentActiveTab?.value === joinURL(PROFILE_URL, '/nfts')
|
|
278
|
+
);
|
|
273
279
|
}, [currentActiveTab]);
|
|
274
280
|
|
|
275
281
|
const isDidSpaceTab = useCreation(() => {
|
|
@@ -289,44 +295,26 @@ export default function UserCenter({
|
|
|
289
295
|
});
|
|
290
296
|
|
|
291
297
|
const renderDefaultTab = useCreation(() => {
|
|
292
|
-
if (
|
|
298
|
+
if (isNftsTab && isMyself) {
|
|
293
299
|
return (
|
|
294
300
|
<Box
|
|
295
301
|
sx={{
|
|
296
|
-
|
|
297
|
-
|
|
302
|
+
display: 'flex',
|
|
303
|
+
flexDirection: 'column',
|
|
304
|
+
gap: 2.5,
|
|
298
305
|
}}>
|
|
299
|
-
<Box
|
|
300
|
-
|
|
301
|
-
display: 'flex',
|
|
302
|
-
flexDirection: 'column',
|
|
303
|
-
gap: 2.5,
|
|
304
|
-
position: {
|
|
305
|
-
xs: 'static',
|
|
306
|
-
md: stickySidebar ? 'sticky' : 'static',
|
|
307
|
-
},
|
|
308
|
-
top: (theme) => (stickySidebar ? theme.spacing(3) : 'unset'),
|
|
309
|
-
}}>
|
|
310
|
-
<Box
|
|
306
|
+
<Box sx={{ border: `1px solid ${colors.dividerColor}`, borderRadius: 2, p: 2 }}>
|
|
307
|
+
<Typography
|
|
311
308
|
sx={{
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
},
|
|
309
|
+
color: colors.foregroundsFgBase,
|
|
310
|
+
fontWeight: 600,
|
|
311
|
+
mb: 2.5,
|
|
316
312
|
}}>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
</Box>
|
|
321
|
-
<UserInfo user={userState.data as User} isMySelf={isMyself} />
|
|
322
|
-
</Box>
|
|
323
|
-
{isMyself ? (
|
|
324
|
-
<Box>
|
|
325
|
-
<Typography sx={{ fontWeight: 600, mb: 1.5 }}>{t('passport')}</Typography>
|
|
326
|
-
<Passport user={userState.data as User} />
|
|
327
|
-
</Box>
|
|
328
|
-
) : null}
|
|
313
|
+
{t('passport')}
|
|
314
|
+
</Typography>
|
|
315
|
+
<Passport user={userState.data as User} />
|
|
329
316
|
</Box>
|
|
317
|
+
<Nft user={userState.data as User} />
|
|
330
318
|
</Box>
|
|
331
319
|
);
|
|
332
320
|
}
|
|
@@ -341,7 +329,7 @@ export default function UserCenter({
|
|
|
341
329
|
);
|
|
342
330
|
}
|
|
343
331
|
return null;
|
|
344
|
-
}, [isSettingTab,
|
|
332
|
+
}, [isSettingTab, isNftsTab, userState, isMyself, stickySidebar, settingContent]);
|
|
345
333
|
|
|
346
334
|
const emptyContent = useCreation(() => {
|
|
347
335
|
return (
|
|
@@ -464,57 +452,70 @@ export default function UserCenter({
|
|
|
464
452
|
}
|
|
465
453
|
|
|
466
454
|
return (
|
|
467
|
-
<ContentWrapper>
|
|
455
|
+
<ContentWrapper display="flex" flexDirection={isMobile ? 'column' : 'row'}>
|
|
456
|
+
<Box flex="1" order={isMobile ? 2 : 'unset'}>
|
|
457
|
+
{userCenterTabs.length > 0 && currentTab ? (
|
|
458
|
+
<Box
|
|
459
|
+
display="flex"
|
|
460
|
+
flexDirection="column"
|
|
461
|
+
sx={{
|
|
462
|
+
height: '100%',
|
|
463
|
+
overflow: 'auto',
|
|
464
|
+
padding: '1px',
|
|
465
|
+
}}>
|
|
466
|
+
<Tabs
|
|
467
|
+
orientation="horizontal"
|
|
468
|
+
variant="line"
|
|
469
|
+
tabs={userCenterTabs}
|
|
470
|
+
current={currentActiveTab?.value ?? currentTab}
|
|
471
|
+
onChange={handleChangeTab}
|
|
472
|
+
sx={{
|
|
473
|
+
mb: 2,
|
|
474
|
+
'.MuiTabs-flexContainer': {
|
|
475
|
+
gap: 3,
|
|
476
|
+
'.MuiButtonBase-root': {
|
|
477
|
+
padding: '40px 4px 32px 4px',
|
|
478
|
+
fontSize: 16,
|
|
479
|
+
},
|
|
480
|
+
'.MuiTab-root': {
|
|
481
|
+
display: 'block',
|
|
482
|
+
textAlign: 'left',
|
|
483
|
+
alignItems: 'flex-start',
|
|
484
|
+
justifyContent: 'flex-start',
|
|
485
|
+
fontWeight: 400,
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
'.MuiTabs-scroller': {
|
|
489
|
+
'&:after': {
|
|
490
|
+
content: '""',
|
|
491
|
+
display: 'block',
|
|
492
|
+
width: '100%',
|
|
493
|
+
height: '1px',
|
|
494
|
+
backgroundColor: `${colors.dividerColor} !important`,
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
}}
|
|
498
|
+
/>
|
|
499
|
+
{tabContent}
|
|
500
|
+
</Box>
|
|
501
|
+
) : null}
|
|
502
|
+
{userCenterTabs.length === 0 && emptyContent}
|
|
503
|
+
</Box>
|
|
504
|
+
{!isMobile && <Divider orientation="vertical" sx={{ borderColor: colors.dividerColor, ml: 5 }} />}
|
|
468
505
|
<UserBasicInfo
|
|
506
|
+
isMobile={isMobile}
|
|
507
|
+
order={isMobile ? 1 : 'unset'}
|
|
469
508
|
isMyself={isMyself}
|
|
470
509
|
switchPassport={handleSwitchPassport}
|
|
471
510
|
switchProfile={session.switchProfile}
|
|
472
511
|
user={userState.data as User}
|
|
473
512
|
showFullDid={false}
|
|
474
513
|
sx={{
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
514
|
+
padding: !isMobile ? '40px 24px 24px 40px' : '24px 0',
|
|
515
|
+
...(!isMobile ? { width: 320, maxWidth: 320, flexShrink: 0 } : {}),
|
|
516
|
+
boxSizing: 'content-box',
|
|
478
517
|
}}
|
|
479
518
|
/>
|
|
480
|
-
<Divider sx={{ mt: 3, mb: 3, borderColor: colors.dividerColor }} />
|
|
481
|
-
{userCenterTabs.length > 0 && currentTab ? (
|
|
482
|
-
<Box
|
|
483
|
-
display={isMobile ? 'block' : 'flex'}
|
|
484
|
-
sx={{
|
|
485
|
-
height: '100%',
|
|
486
|
-
overflow: 'auto',
|
|
487
|
-
padding: '1px',
|
|
488
|
-
}}>
|
|
489
|
-
<Tabs
|
|
490
|
-
orientation={isMobile ? 'horizontal' : 'vertical'}
|
|
491
|
-
variant="line"
|
|
492
|
-
tabs={userCenterTabs}
|
|
493
|
-
current={currentActiveTab?.value ?? currentTab}
|
|
494
|
-
onChange={handleChangeTab}
|
|
495
|
-
sx={{
|
|
496
|
-
width: isMobile ? '100%' : 160,
|
|
497
|
-
marginBottom: isMobile ? '8px' : 0,
|
|
498
|
-
flexShrink: 0,
|
|
499
|
-
'.MuiTabs-flexContainer': {
|
|
500
|
-
gap: 1.5,
|
|
501
|
-
'.MuiButtonBase-root': {
|
|
502
|
-
fontSize: 16,
|
|
503
|
-
},
|
|
504
|
-
'.MuiTab-root': {
|
|
505
|
-
display: 'block',
|
|
506
|
-
textAlign: 'left',
|
|
507
|
-
alignItems: 'flex-start',
|
|
508
|
-
justifyContent: 'flex-start',
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
}}
|
|
512
|
-
/>
|
|
513
|
-
<Divider orientation="vertical" sx={{ height: '100%', mr: 3, borderColor: colors.dividerColor }} />
|
|
514
|
-
{tabContent}
|
|
515
|
-
</Box>
|
|
516
|
-
) : null}
|
|
517
|
-
{userCenterTabs.length === 0 && emptyContent}
|
|
518
519
|
</ContentWrapper>
|
|
519
520
|
);
|
|
520
521
|
}, [
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import utc from 'dayjs/plugin/utc';
|
|
4
|
+
import timezonePlugin from 'dayjs/plugin/timezone';
|
|
5
|
+
import { formatToDatetime } from '@arcblock/ux/lib/Util';
|
|
6
|
+
import { useCreation } from 'ahooks';
|
|
7
|
+
|
|
8
|
+
dayjs.extend(utc);
|
|
9
|
+
dayjs.extend(timezonePlugin);
|
|
10
|
+
|
|
11
|
+
export default function Clock({ timezone = 'utc', locale = 'zh' }: { timezone?: string; locale?: string }) {
|
|
12
|
+
const [time, setTime] = useState(dayjs().tz(timezone));
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const timerId = setInterval(() => {
|
|
16
|
+
setTime(dayjs().tz(timezone));
|
|
17
|
+
}, 6000); // 每分钟更新一次
|
|
18
|
+
|
|
19
|
+
// 清除定时器
|
|
20
|
+
return () => clearInterval(timerId);
|
|
21
|
+
}, [timezone]);
|
|
22
|
+
|
|
23
|
+
const formatTime = useCreation(() => {
|
|
24
|
+
const localeOption = locale === 'zh' ? 'zh-cn' : 'en-us';
|
|
25
|
+
return formatToDatetime(time.toDate(), { tz: timezone, locale: localeOption });
|
|
26
|
+
}, [time, timezone, locale]);
|
|
27
|
+
|
|
28
|
+
return <div>{formatTime}</div>;
|
|
29
|
+
}
|