@blocklet/ui-react 2.9.52 → 2.9.54
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/es/@types/index.d.ts +3 -5
- package/es/Dashboard/index.d.ts +5 -6
- package/es/Dashboard/index.js +5 -5
- package/es/Footer/index.js +2 -2
- package/es/Header/index.js +5 -5
- package/es/UserCenter/components/notification.js +1 -1
- package/es/UserCenter/components/passport.js +1 -2
- package/es/UserCenter/components/privacy.js +1 -1
- package/es/UserCenter/components/settings.js +9 -1
- package/es/UserCenter/components/storage/connect-to.d.ts +1 -1
- package/es/UserCenter/components/storage/connect-to.js +9 -3
- package/es/UserCenter/components/storage/delete.d.ts +1 -1
- package/es/UserCenter/components/storage/delete.js +4 -1
- package/es/UserCenter/components/storage/item.js +1 -1
- package/es/UserCenter/components/user-center.d.ts +2 -2
- package/es/UserCenter/components/user-center.js +15 -6
- package/es/UserCenter/libs/locales.d.ts +2 -0
- package/es/UserCenter/libs/locales.js +2 -0
- package/es/UserSessions/components/user-session-info.d.ts +6 -0
- package/es/UserSessions/components/user-session-info.js +58 -0
- package/es/UserSessions/components/user-sessions.d.ts +9 -0
- package/es/UserSessions/components/user-sessions.js +255 -0
- package/es/UserSessions/index.d.ts +1 -0
- package/es/UserSessions/index.js +1 -0
- package/es/UserSessions/libs/locales.d.ts +52 -0
- package/es/UserSessions/libs/locales.js +52 -0
- package/es/UserSessions/libs/utils.d.ts +2 -0
- package/es/UserSessions/libs/utils.js +73 -0
- package/es/blocklets.js +6 -6
- package/es/common/header-addons.d.ts +3 -4
- package/es/common/header-addons.js +4 -4
- package/es/contexts/config-user-space.js +2 -2
- package/es/index.d.ts +1 -0
- package/es/index.js +1 -0
- package/es/types.d.ts +2 -2
- package/es/types.js +2 -2
- package/lib/@types/index.d.ts +3 -5
- package/lib/Dashboard/index.d.ts +5 -6
- package/lib/Dashboard/index.js +4 -4
- package/lib/Footer/index.js +1 -1
- package/lib/Header/index.js +4 -4
- package/lib/UserCenter/components/notification.js +1 -1
- package/lib/UserCenter/components/passport.js +1 -2
- package/lib/UserCenter/components/privacy.js +1 -1
- package/lib/UserCenter/components/settings.js +10 -1
- package/lib/UserCenter/components/storage/connect-to.d.ts +1 -1
- package/lib/UserCenter/components/storage/connect-to.js +3 -3
- package/lib/UserCenter/components/storage/delete.d.ts +1 -1
- package/lib/UserCenter/components/storage/item.js +1 -1
- package/lib/UserCenter/components/user-center.d.ts +2 -2
- package/lib/UserCenter/components/user-center.js +20 -10
- package/lib/UserCenter/libs/locales.d.ts +2 -0
- package/lib/UserCenter/libs/locales.js +2 -0
- package/lib/UserSessions/components/user-session-info.d.ts +6 -0
- package/lib/UserSessions/components/user-session-info.js +68 -0
- package/lib/UserSessions/components/user-sessions.d.ts +9 -0
- package/lib/UserSessions/components/user-sessions.js +282 -0
- package/lib/UserSessions/index.d.ts +1 -0
- package/lib/UserSessions/index.js +13 -0
- package/lib/UserSessions/libs/locales.d.ts +52 -0
- package/lib/UserSessions/libs/locales.js +58 -0
- package/lib/UserSessions/libs/utils.d.ts +2 -0
- package/lib/UserSessions/libs/utils.js +80 -0
- package/lib/blocklets.js +6 -6
- package/lib/common/header-addons.d.ts +3 -4
- package/lib/common/header-addons.js +3 -3
- package/lib/contexts/config-user-space.js +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +12 -0
- package/lib/types.d.ts +2 -2
- package/lib/types.js +3 -3
- package/package.json +14 -6
- package/src/@types/index.ts +3 -5
- package/src/Dashboard/index.jsx +7 -3
- package/src/Footer/index.jsx +2 -2
- package/src/Header/index.jsx +5 -3
- package/src/Icon/index.jsx +1 -0
- package/src/UserCenter/components/notification.tsx +2 -2
- package/src/UserCenter/components/passport.tsx +1 -2
- package/src/UserCenter/components/privacy.tsx +1 -1
- package/src/UserCenter/components/settings.tsx +15 -2
- package/src/UserCenter/components/storage/connect-to.tsx +17 -11
- package/src/UserCenter/components/storage/delete.tsx +8 -2
- package/src/UserCenter/components/storage/item.tsx +2 -3
- package/src/UserCenter/components/storage/preview-nft.tsx +1 -1
- package/src/UserCenter/components/user-center.tsx +21 -14
- package/src/UserCenter/components/webhook-item.tsx +1 -1
- package/src/UserCenter/libs/locales.ts +2 -0
- package/src/UserSessions/components/user-session-info.tsx +52 -0
- package/src/UserSessions/components/user-sessions.tsx +276 -0
- package/src/UserSessions/index.tsx +1 -0
- package/src/UserSessions/libs/locales.ts +52 -0
- package/src/UserSessions/libs/utils.ts +82 -0
- package/src/blocklets.js +6 -6
- package/src/common/header-addons.jsx +2 -2
- package/src/contexts/config-user-space.tsx +12 -11
- package/src/index.ts +1 -0
- package/src/{UserCenter/libs → libs}/client.ts +1 -0
- package/src/libs/spaces.tsx +2 -2
- package/src/types.js +2 -2
- /package/es/{UserCenter/libs → libs}/client.d.ts +0 -0
- /package/es/{UserCenter/libs → libs}/client.js +0 -0
- /package/lib/{UserCenter/libs → libs}/client.d.ts +0 -0
- /package/lib/{UserCenter/libs → libs}/client.js +0 -0
|
@@ -5,12 +5,17 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
5
5
|
import DidConnect from '@arcblock/did-connect/lib/Connect';
|
|
6
6
|
import { joinURL } from 'ufo';
|
|
7
7
|
import toast from '@arcblock/ux/lib/Toast';
|
|
8
|
-
import { func, string } from 'prop-types';
|
|
9
8
|
import isEmpty from 'lodash/isEmpty';
|
|
10
|
-
import {axios} from '../../libs/api';
|
|
9
|
+
import { axios } from '../../libs/api';
|
|
11
10
|
import { SpaceGateway } from '../../../contexts/config-user-space';
|
|
12
11
|
|
|
13
|
-
function ConnectTo({
|
|
12
|
+
function ConnectTo({
|
|
13
|
+
onConnect,
|
|
14
|
+
storageEndpoint,
|
|
15
|
+
}: {
|
|
16
|
+
onConnect: (spaceGateway: SpaceGateway) => Promise<void>;
|
|
17
|
+
storageEndpoint: string;
|
|
18
|
+
}) {
|
|
14
19
|
const { t, locale } = useLocaleContext();
|
|
15
20
|
|
|
16
21
|
const [authorizeFromNftConnect, setAuthorizeFromNftConnect] = useState({
|
|
@@ -18,8 +23,11 @@ function ConnectTo({ onConnect, storageEndpoint, ...rest }: { onConnect: (spaceG
|
|
|
18
23
|
action: 'connect-to-did-spaces-for-user',
|
|
19
24
|
checkTimeout: 1000 * 300,
|
|
20
25
|
prefix: '/api/did',
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
checkFn: axios.create({
|
|
27
|
+
// FIXME: @jianchao 检查 env 的使用是否正确
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
baseURL: joinURL(window.location.origin, window.env && window.env.apiPrefix ? window.env.apiPrefix : '/'),
|
|
30
|
+
}).get,
|
|
23
31
|
extraParams: {},
|
|
24
32
|
messages: {
|
|
25
33
|
title: t('storage.spaces.provideNFT.title', { appName: window.blocklet.appName }, locale),
|
|
@@ -36,7 +44,6 @@ function ConnectTo({ onConnect, storageEndpoint, ...rest }: { onConnect: (spaceG
|
|
|
36
44
|
},
|
|
37
45
|
});
|
|
38
46
|
|
|
39
|
-
|
|
40
47
|
const updateSpaceGateway = async (spaceGateway: SpaceGateway) => {
|
|
41
48
|
await onConnect(spaceGateway);
|
|
42
49
|
toast.success(t('storage.spaces.connectedWithName', { name: spaceGateway.name }));
|
|
@@ -73,24 +80,24 @@ function ConnectTo({ onConnect, storageEndpoint, ...rest }: { onConnect: (spaceG
|
|
|
73
80
|
open: true,
|
|
74
81
|
extraParams: {
|
|
75
82
|
appDid: window.blocklet.appId,
|
|
76
|
-
appName:
|
|
83
|
+
appName: window.blocklet.appName,
|
|
77
84
|
appDescription: window.blocklet.appDescription,
|
|
78
85
|
scopes: 'list:object read:object write:object',
|
|
79
86
|
appUrl: window.blocklet.appUrl,
|
|
80
|
-
referrer: window.location.href
|
|
87
|
+
referrer: window.location.href,
|
|
81
88
|
},
|
|
82
89
|
}));
|
|
83
90
|
};
|
|
84
91
|
|
|
85
92
|
return (
|
|
86
93
|
<Box>
|
|
87
|
-
<Button
|
|
94
|
+
<Button
|
|
88
95
|
color="primary"
|
|
89
96
|
style={{ textTransform: 'none !important', fontSize: '1rem' }}
|
|
90
97
|
size="small"
|
|
91
98
|
onClick={handleUseWalletConnect}
|
|
92
99
|
variant="outlined">
|
|
93
|
-
|
|
100
|
+
{storageEndpoint ? t('storage.spaces.connect.useWalletReconnect') : t('storage.spaces.connect.useWallet')}
|
|
94
101
|
</Button>
|
|
95
102
|
|
|
96
103
|
{/* 为了获取 DID Spaces Url */}
|
|
@@ -109,7 +116,6 @@ function ConnectTo({ onConnect, storageEndpoint, ...rest }: { onConnect: (spaceG
|
|
|
109
116
|
messages={authorizeFromNftConnect.messages}
|
|
110
117
|
locale={locale}
|
|
111
118
|
/>
|
|
112
|
-
|
|
113
119
|
</Box>
|
|
114
120
|
);
|
|
115
121
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import { IconButton } from '@mui/material';
|
|
3
3
|
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
|
4
4
|
import Confirm from '@arcblock/ux/lib/Dialog/confirm';
|
|
@@ -6,7 +6,13 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
6
6
|
import toast from '@arcblock/ux/lib/Toast';
|
|
7
7
|
import { SpaceGateway } from '../../../contexts/config-user-space';
|
|
8
8
|
|
|
9
|
-
function SpaceDelete({
|
|
9
|
+
function SpaceDelete({
|
|
10
|
+
spaceGateway,
|
|
11
|
+
onDeleteSpace,
|
|
12
|
+
}: {
|
|
13
|
+
spaceGateway: SpaceGateway;
|
|
14
|
+
onDeleteSpace: (spaceGateway: SpaceGateway) => Promise<void>;
|
|
15
|
+
}) {
|
|
10
16
|
const { t } = useLocaleContext();
|
|
11
17
|
const [open, setOpen] = useState(false);
|
|
12
18
|
const [loading, setLoading] = useState(false);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Box, BoxProps, Chip, Typography } from '@mui/material';
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import toast from '@arcblock/ux/lib/Toast';
|
|
4
3
|
import CheckIcon from '@mui/icons-material/Check';
|
|
5
4
|
import styled from '@emotion/styled';
|
|
5
|
+
import DidAddress from '@arcblock/ux/lib/DID';
|
|
6
|
+
|
|
6
7
|
// @ts-expect-error
|
|
7
8
|
import SpacesConnectedSvg from './icons/space-connected.svg?react';
|
|
8
|
-
import DidAddress from '@arcblock/ux/lib/DID';
|
|
9
|
-
import PropTypes, { bool, func } from 'prop-types';
|
|
10
9
|
import PreviewSpaceNft from './preview-nft';
|
|
11
10
|
import { SpaceGateway } from '../../../contexts/config-user-space';
|
|
12
11
|
import useMobile from '../../../hooks/use-mobile';
|
|
@@ -5,7 +5,7 @@ import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
|
|
|
5
5
|
// @ts-expect-error
|
|
6
6
|
import EmptySpacesNFT from './icons/empty-spaces-nft.svg?react';
|
|
7
7
|
|
|
8
|
-
function PreviewSpaceNft({ src, alt }: {src: string
|
|
8
|
+
function PreviewSpaceNft({ src, alt }: { src: string; alt: string }) {
|
|
9
9
|
const [showEmptySpaceNFT, setShowEmptySpaceNFT] = useState(false);
|
|
10
10
|
const [open, setOpen] = useState(false);
|
|
11
11
|
|
|
@@ -16,17 +16,19 @@ import { ErrorFallback } from '@arcblock/ux/lib/ErrorBoundary';
|
|
|
16
16
|
import cloneDeep from 'lodash/cloneDeep';
|
|
17
17
|
import { getQuery, withQuery } from 'ufo';
|
|
18
18
|
import type { AxiosError } from 'axios';
|
|
19
|
+
import type { UserPublicInfo } from '@blocklet/js-sdk';
|
|
19
20
|
|
|
20
|
-
import
|
|
21
|
+
import Footer from '../../Footer';
|
|
22
|
+
import Header from '../../Header';
|
|
21
23
|
import { translations } from '../libs/locales';
|
|
22
24
|
import UserInfo from './user-info';
|
|
23
25
|
import UserBasicInfo from './user-basic-info';
|
|
24
|
-
import type { SessionContext as TSessionContext, UserCenterTab } from '../../@types';
|
|
26
|
+
import type { SessionContext as TSessionContext, User, UserCenterTab } from '../../@types';
|
|
25
27
|
// @ts-ignore
|
|
26
28
|
import { formatBlockletInfo, getLocalizedNavigation } from '../../blocklets';
|
|
27
29
|
import Passport from './passport';
|
|
28
30
|
import Settings from './settings';
|
|
29
|
-
import { client } from '
|
|
31
|
+
import { client } from '../../libs/client';
|
|
30
32
|
|
|
31
33
|
export default function UserCenter({
|
|
32
34
|
children,
|
|
@@ -37,7 +39,7 @@ export default function UserCenter({
|
|
|
37
39
|
hideFooter = false,
|
|
38
40
|
headerProps = {},
|
|
39
41
|
footerProps = {},
|
|
40
|
-
userDid,
|
|
42
|
+
userDid = undefined,
|
|
41
43
|
stickySidebar = false,
|
|
42
44
|
}: {
|
|
43
45
|
readonly children: any;
|
|
@@ -46,8 +48,9 @@ export default function UserCenter({
|
|
|
46
48
|
readonly disableAutoRedirect?: boolean;
|
|
47
49
|
readonly autoPopupSetting?: boolean;
|
|
48
50
|
readonly hideFooter?: boolean;
|
|
49
|
-
|
|
50
|
-
readonly
|
|
51
|
+
// FIXME: @zhanghan 将 header 和 footer 改为 ts 后,去掉这个 any
|
|
52
|
+
readonly headerProps?: any;
|
|
53
|
+
readonly footerProps?: any;
|
|
51
54
|
readonly userDid?: string;
|
|
52
55
|
readonly stickySidebar?: boolean;
|
|
53
56
|
}) {
|
|
@@ -81,14 +84,15 @@ export default function UserCenter({
|
|
|
81
84
|
return false;
|
|
82
85
|
}, [currentDid, session?.user?.did]);
|
|
83
86
|
|
|
84
|
-
const userState = useRequest(
|
|
87
|
+
const userState = useRequest<UserPublicInfo | User | undefined, []>(
|
|
88
|
+
// eslint-disable-next-line consistent-return
|
|
85
89
|
async () => {
|
|
86
90
|
await pWaitFor(() => session?.initialized);
|
|
87
91
|
if (isMyself) {
|
|
88
|
-
return session.user;
|
|
92
|
+
return session.user as User;
|
|
89
93
|
}
|
|
90
94
|
if (currentDid) {
|
|
91
|
-
return
|
|
95
|
+
return client.user.getUserPublicInfo({ did: currentDid });
|
|
92
96
|
}
|
|
93
97
|
},
|
|
94
98
|
{
|
|
@@ -114,6 +118,7 @@ export default function UserCenter({
|
|
|
114
118
|
sx: {
|
|
115
119
|
'.MuiDialog-paper': {
|
|
116
120
|
borderRadius: 2,
|
|
121
|
+
maxWidth: 1200,
|
|
117
122
|
},
|
|
118
123
|
'.ux-dialog_title': {
|
|
119
124
|
fontWeight: 600,
|
|
@@ -174,7 +179,7 @@ export default function UserCenter({
|
|
|
174
179
|
title: t('settings'),
|
|
175
180
|
content: (
|
|
176
181
|
<Settings
|
|
177
|
-
user={userState.data}
|
|
182
|
+
user={userState.data as User}
|
|
178
183
|
settings={{
|
|
179
184
|
userCenterTabs,
|
|
180
185
|
}}
|
|
@@ -258,7 +263,7 @@ export default function UserCenter({
|
|
|
258
263
|
switchPassport={session.switchPassport}
|
|
259
264
|
switchProfile={session.switchProfile}
|
|
260
265
|
openSettings={openSettings}
|
|
261
|
-
user={userState.data}
|
|
266
|
+
user={userState.data as User}
|
|
262
267
|
showFullDid={false}
|
|
263
268
|
sx={{
|
|
264
269
|
display: {
|
|
@@ -294,12 +299,14 @@ export default function UserCenter({
|
|
|
294
299
|
<CircularProgress sx={{ color: colors.primary100 }} />
|
|
295
300
|
</Box>
|
|
296
301
|
) : (
|
|
302
|
+
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
297
303
|
<>
|
|
298
304
|
{currentActiveTab?.protected && !isMyself ? (
|
|
299
305
|
<Box>
|
|
300
306
|
<Empty>{t('underProtected')}</Empty>
|
|
301
307
|
</Box>
|
|
302
308
|
) : (
|
|
309
|
+
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
303
310
|
<>{children && <Box {...contentProps}>{children}</Box>}</>
|
|
304
311
|
)}
|
|
305
312
|
</>
|
|
@@ -355,7 +362,7 @@ export default function UserCenter({
|
|
|
355
362
|
switchPassport={session.switchPassport}
|
|
356
363
|
switchProfile={session.switchProfile}
|
|
357
364
|
openSettings={openSettings}
|
|
358
|
-
user={userState.data}
|
|
365
|
+
user={userState.data as User}
|
|
359
366
|
sx={{
|
|
360
367
|
display: {
|
|
361
368
|
xs: 'none',
|
|
@@ -368,11 +375,11 @@ export default function UserCenter({
|
|
|
368
375
|
<>
|
|
369
376
|
<Box>
|
|
370
377
|
<Typography sx={{ fontWeight: 600, mb: 1.5 }}>{t('myInfo')}</Typography>
|
|
371
|
-
<UserInfo user={userState.data} />
|
|
378
|
+
<UserInfo user={userState.data as User} />
|
|
372
379
|
</Box>
|
|
373
380
|
<Box>
|
|
374
381
|
<Typography sx={{ fontWeight: 600, mb: 1.5 }}>{t('passport')}</Typography>
|
|
375
|
-
<Passport user={userState.data} />
|
|
382
|
+
<Passport user={userState.data as User} />
|
|
376
383
|
</Box>
|
|
377
384
|
</>
|
|
378
385
|
) : null}
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
InputAdornment,
|
|
7
7
|
MenuItem,
|
|
8
8
|
Select,
|
|
9
|
-
Stack,
|
|
10
9
|
TextField,
|
|
11
10
|
Typography,
|
|
12
11
|
} from '@mui/material';
|
|
@@ -75,6 +74,7 @@ export default function WebhookItem({
|
|
|
75
74
|
onCancel();
|
|
76
75
|
});
|
|
77
76
|
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
78
78
|
const _onTest = useMemoizedFn(async (data) => {
|
|
79
79
|
currentState.loading = true;
|
|
80
80
|
await onTest(data);
|
|
@@ -41,6 +41,7 @@ export const translations = {
|
|
|
41
41
|
myInfo: '我的信息',
|
|
42
42
|
loginNow: '立即登录',
|
|
43
43
|
viewAfterLogin: '登录后才可以查看',
|
|
44
|
+
sessionManagement: '会话管理',
|
|
44
45
|
storage: {
|
|
45
46
|
spaces: {
|
|
46
47
|
tips: '提示',
|
|
@@ -110,6 +111,7 @@ export const translations = {
|
|
|
110
111
|
myInfo: 'My Info',
|
|
111
112
|
loginNow: 'Login',
|
|
112
113
|
viewAfterLogin: 'View after login',
|
|
114
|
+
sessionManagement: 'Session Management',
|
|
113
115
|
storage: {
|
|
114
116
|
spaces: {
|
|
115
117
|
tips: 'Tips',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Box, Chip, Typography } from '@mui/material';
|
|
2
|
+
import Avatar from '@arcblock/ux/lib/Avatar';
|
|
3
|
+
import { useCreation } from 'ahooks';
|
|
4
|
+
import { temp as colors } from '@arcblock/ux/lib/Colors';
|
|
5
|
+
|
|
6
|
+
import { User } from '../../@types';
|
|
7
|
+
|
|
8
|
+
export default function UserSessionInfo({ user, sessionUser }: { readonly user: User; readonly sessionUser: any }) {
|
|
9
|
+
const currentRole = useCreation(() => {
|
|
10
|
+
return (user?.passports || [])?.find((item) => item.name === sessionUser.role);
|
|
11
|
+
}, [user?.passports, sessionUser?.role]);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Box
|
|
15
|
+
sx={{
|
|
16
|
+
display: 'flex',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
gap: 1,
|
|
19
|
+
}}>
|
|
20
|
+
<Avatar size={40} variant="circle" shape="circle" src={sessionUser.avatar} did={sessionUser.did} />
|
|
21
|
+
<Box>
|
|
22
|
+
<Box
|
|
23
|
+
sx={{
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
gap: 1,
|
|
27
|
+
}}>
|
|
28
|
+
<Typography sx={{ whiteSpace: 'normal', wordBreak: 'break-all' }}>{sessionUser.fullName}</Typography>
|
|
29
|
+
<Chip
|
|
30
|
+
label={currentRole?.title || currentRole?.name || 'Guest'}
|
|
31
|
+
size="small"
|
|
32
|
+
variant="outlined"
|
|
33
|
+
sx={{
|
|
34
|
+
flexShrink: 0,
|
|
35
|
+
fontWeight: 'bold',
|
|
36
|
+
fontSize: '12px',
|
|
37
|
+
color: colors.textBase,
|
|
38
|
+
borderColor: colors.strokeBorderStrong,
|
|
39
|
+
backgroundColor: 'transparent',
|
|
40
|
+
textTransform: 'capitalize',
|
|
41
|
+
pr: 1,
|
|
42
|
+
pl: 0.5,
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
</Box>
|
|
46
|
+
<Typography variant="body2" color="grey">
|
|
47
|
+
{sessionUser.email}
|
|
48
|
+
</Typography>
|
|
49
|
+
</Box>
|
|
50
|
+
</Box>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/* eslint-disable react/no-unstable-nested-components */
|
|
2
|
+
import Datatable from '@arcblock/ux/lib/Datatable';
|
|
3
|
+
import { useCreation, useMemoizedFn, useRequest } from 'ahooks';
|
|
4
|
+
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
5
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
6
|
+
import RelativeTime from '@arcblock/ux/lib/RelativeTime';
|
|
7
|
+
import sortBy from 'lodash/sortBy';
|
|
8
|
+
import UAParser from 'ua-parser-js';
|
|
9
|
+
import { getVisitorId } from '@arcblock/ux/lib/Util';
|
|
10
|
+
import { useConfirm } from '@arcblock/ux/lib/Dialog';
|
|
11
|
+
import pAll from 'p-all';
|
|
12
|
+
import { Box, Button, Tooltip, Typography } from '@mui/material';
|
|
13
|
+
import { ReactElement } from 'react';
|
|
14
|
+
import { UserSession } from '@blocklet/js-sdk';
|
|
15
|
+
|
|
16
|
+
import UserSessionInfo from './user-session-info';
|
|
17
|
+
import { User } from '../../@types';
|
|
18
|
+
import { client } from '../../libs/client';
|
|
19
|
+
import { translations } from '../libs/locales';
|
|
20
|
+
import { batchIp2Region } from '../libs/utils';
|
|
21
|
+
|
|
22
|
+
const parseUa = (ua: string) => {
|
|
23
|
+
const parser = new UAParser(ua, {
|
|
24
|
+
// eslint-disable-next-line no-useless-escape
|
|
25
|
+
browser: [[/(ArcWallet)\/([\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION]],
|
|
26
|
+
});
|
|
27
|
+
const result = parser.getResult();
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default function UserSessions({
|
|
32
|
+
user,
|
|
33
|
+
showAction = true,
|
|
34
|
+
showUser = true,
|
|
35
|
+
}: {
|
|
36
|
+
readonly user: User & {
|
|
37
|
+
userSessions?: any[];
|
|
38
|
+
};
|
|
39
|
+
readonly showAction?: boolean;
|
|
40
|
+
readonly showUser?: boolean;
|
|
41
|
+
}) {
|
|
42
|
+
const currentVisitorId = getVisitorId();
|
|
43
|
+
const { locale } = useLocaleContext();
|
|
44
|
+
const { confirmApi, confirmHolder } = useConfirm();
|
|
45
|
+
const t = useMemoizedFn((key, data = {}) => {
|
|
46
|
+
return translate(translations, key, locale, 'en', data);
|
|
47
|
+
});
|
|
48
|
+
const getData: () => Promise<(UserSession & { ipRegion?: string })[]> = useMemoizedFn(async () => {
|
|
49
|
+
let data = user?.userSessions as (UserSession & { ipRegion?: string })[];
|
|
50
|
+
if (!data) {
|
|
51
|
+
data = await client.userSession.getUserSessions({ did: user.did });
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const ipIndexList = data?.map((x, index) => [index, x.lastLoginIp] as [number, string]).filter((x) => !!x[1]);
|
|
55
|
+
const ipList = ipIndexList?.map((x) => x[1]);
|
|
56
|
+
const result = await batchIp2Region(ipList);
|
|
57
|
+
for (let index = 0; index < result.length; index++) {
|
|
58
|
+
const x = result[index];
|
|
59
|
+
const ipIndexItem = ipIndexList[index];
|
|
60
|
+
const dataItem = data[ipIndexItem[0]];
|
|
61
|
+
dataItem.ipRegion = x;
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.warn('Failed to convert ip to region');
|
|
65
|
+
console.error(e);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const now = new Date().getTime();
|
|
69
|
+
return sortBy(data, (x) => {
|
|
70
|
+
if (x.visitorId === currentVisitorId) {
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
return now - new Date(x.updatedAt).getTime();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const pageState = useRequest(getData);
|
|
78
|
+
|
|
79
|
+
const safeData = useCreation(() => {
|
|
80
|
+
return pageState.data || [];
|
|
81
|
+
}, [pageState.data]);
|
|
82
|
+
|
|
83
|
+
const ipRegionMap = useCreation(() => {
|
|
84
|
+
return safeData.reduce((acc, x) => {
|
|
85
|
+
acc[x.lastLoginIp] = x.ipRegion;
|
|
86
|
+
return acc;
|
|
87
|
+
}, {} as { [key: string]: string | undefined });
|
|
88
|
+
}, [safeData]);
|
|
89
|
+
|
|
90
|
+
const logout = useMemoizedFn(({ visitorId }) => {
|
|
91
|
+
confirmApi.open({
|
|
92
|
+
title: t('logoutThisSession'),
|
|
93
|
+
content: t('logoutThisSessionConfirm'),
|
|
94
|
+
confirmButtonText: t('confirm'),
|
|
95
|
+
cancelButtonText: t('cancel'),
|
|
96
|
+
onConfirm: async () => {
|
|
97
|
+
await client.user.logout({ visitorId });
|
|
98
|
+
pageState.refresh();
|
|
99
|
+
confirmApi.close();
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
const otherUserSessions = useCreation(() => {
|
|
104
|
+
const list = safeData.filter((x) => x.visitorId !== currentVisitorId);
|
|
105
|
+
return list;
|
|
106
|
+
}, [safeData]);
|
|
107
|
+
const logoutAll = useMemoizedFn(() => {
|
|
108
|
+
confirmApi.open({
|
|
109
|
+
title: t('logoutAllSession'),
|
|
110
|
+
content: t('logoutAllSessionConfirm'),
|
|
111
|
+
confirmButtonText: t('confirm'),
|
|
112
|
+
cancelButtonText: t('cancel'),
|
|
113
|
+
onConfirm: async () => {
|
|
114
|
+
const list = otherUserSessions.map((x) => {
|
|
115
|
+
return () => client.user.logout({ visitorId: x.visitorId });
|
|
116
|
+
});
|
|
117
|
+
await pAll(list, {
|
|
118
|
+
concurrency: 3,
|
|
119
|
+
stopOnError: false,
|
|
120
|
+
});
|
|
121
|
+
pageState.refresh();
|
|
122
|
+
confirmApi.close();
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
const customButtons: ReactElement[] = [];
|
|
127
|
+
if (showAction) {
|
|
128
|
+
customButtons.push(
|
|
129
|
+
<Tooltip key="logoutAll" title={t('logoutAllTips')}>
|
|
130
|
+
<Button
|
|
131
|
+
sx={{ ml: 0.5 }}
|
|
132
|
+
size="small"
|
|
133
|
+
variant="contained"
|
|
134
|
+
color="error"
|
|
135
|
+
onClick={logoutAll}
|
|
136
|
+
disabled={otherUserSessions.length === 0}>
|
|
137
|
+
{t('logoutAll')}
|
|
138
|
+
</Button>
|
|
139
|
+
</Tooltip>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const tableOptions = useCreation(() => {
|
|
143
|
+
return {
|
|
144
|
+
// viewColumns: false,
|
|
145
|
+
search: false,
|
|
146
|
+
sort: false,
|
|
147
|
+
download: false,
|
|
148
|
+
filter: false,
|
|
149
|
+
print: false,
|
|
150
|
+
expandableRowsOnClick: false,
|
|
151
|
+
searchDebounceTime: 600,
|
|
152
|
+
};
|
|
153
|
+
}, []);
|
|
154
|
+
const columns = [
|
|
155
|
+
{
|
|
156
|
+
label: t('platform'),
|
|
157
|
+
name: 'platform',
|
|
158
|
+
options: {
|
|
159
|
+
customBodyRenderLite: (rawIndex: number) => {
|
|
160
|
+
const x = safeData[rawIndex];
|
|
161
|
+
const result = parseUa(x.ua);
|
|
162
|
+
return <Box>{[result.os?.name, result.os?.version].filter(Boolean).join('/') || t('unknown')}</Box>;
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
label: t('deviceType'),
|
|
168
|
+
name: 'deviceType',
|
|
169
|
+
options: {
|
|
170
|
+
customBodyRenderLite: (rawIndex: number) => {
|
|
171
|
+
const x = safeData[rawIndex];
|
|
172
|
+
const result = parseUa(x.ua);
|
|
173
|
+
return <Box>{[result.browser?.name, result.browser?.version].filter(Boolean).join('/') || t('unknown')}</Box>;
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
label: t('walletOS'),
|
|
179
|
+
name: 'walletOS',
|
|
180
|
+
options: {
|
|
181
|
+
customBodyRenderLite: (rawIndex: number) => {
|
|
182
|
+
const x = safeData[rawIndex];
|
|
183
|
+
return <Box>{x.extra?.walletOS || t('unknown')}</Box>;
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
showUser && {
|
|
188
|
+
label: t('user'),
|
|
189
|
+
name: 'user',
|
|
190
|
+
options: {
|
|
191
|
+
customBodyRenderLite: (rawIndex: number) => {
|
|
192
|
+
const x = safeData[rawIndex];
|
|
193
|
+
return <UserSessionInfo sessionUser={x.user} user={user} />;
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
label: t('updatedAt'),
|
|
199
|
+
name: 'updatedAt',
|
|
200
|
+
options: {
|
|
201
|
+
customBodyRenderLite: (rawIndex: number) => {
|
|
202
|
+
const x = safeData[rawIndex];
|
|
203
|
+
return <RelativeTime value={x.updatedAt} relativeRange={3 * 86400 * 1000} locale={locale} />;
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
label: t('lastLoginIp'),
|
|
209
|
+
name: 'lastLoginIp',
|
|
210
|
+
options: {
|
|
211
|
+
customBodyRenderLite: (rawIndex: number) => {
|
|
212
|
+
const x = safeData[rawIndex];
|
|
213
|
+
return (
|
|
214
|
+
<Box>
|
|
215
|
+
{ipRegionMap[x.lastLoginIp] ? (
|
|
216
|
+
<>
|
|
217
|
+
<Typography variant="body2">{ipRegionMap[x.lastLoginIp]}</Typography>
|
|
218
|
+
<Typography variant="body2" color="grey">
|
|
219
|
+
{x.lastLoginIp || t('unknown')}
|
|
220
|
+
</Typography>
|
|
221
|
+
</>
|
|
222
|
+
) : (
|
|
223
|
+
<Typography>{x.lastLoginIp || t('unknown')}</Typography>
|
|
224
|
+
)}
|
|
225
|
+
</Box>
|
|
226
|
+
);
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
showAction && {
|
|
231
|
+
label: t('actions'),
|
|
232
|
+
name: 'actions',
|
|
233
|
+
options: {
|
|
234
|
+
customBodyRenderLite: (rawIndex: number) => {
|
|
235
|
+
const x = safeData[rawIndex];
|
|
236
|
+
return (
|
|
237
|
+
<Box>
|
|
238
|
+
<Button
|
|
239
|
+
sx={{
|
|
240
|
+
whiteSpace: 'nowrap',
|
|
241
|
+
fontSize: '12px',
|
|
242
|
+
px: 1,
|
|
243
|
+
}}
|
|
244
|
+
disabled={currentVisitorId === x.visitorId}
|
|
245
|
+
variant="outlined"
|
|
246
|
+
size="small"
|
|
247
|
+
color="error"
|
|
248
|
+
onClick={() => logout({ visitorId: x.visitorId })}>
|
|
249
|
+
{currentVisitorId === x.visitorId ? t('currentSession') : t('logout')}
|
|
250
|
+
</Button>
|
|
251
|
+
</Box>
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
].filter(Boolean);
|
|
257
|
+
return (
|
|
258
|
+
<Box
|
|
259
|
+
sx={{
|
|
260
|
+
'.MuiTableCell-head': {
|
|
261
|
+
whiteSpace: 'nowrap',
|
|
262
|
+
fontWeight: 'bold',
|
|
263
|
+
},
|
|
264
|
+
}}>
|
|
265
|
+
{confirmHolder}
|
|
266
|
+
<Datatable
|
|
267
|
+
locale={locale}
|
|
268
|
+
data={safeData}
|
|
269
|
+
columns={columns}
|
|
270
|
+
customButtons={customButtons}
|
|
271
|
+
options={tableOptions}
|
|
272
|
+
loading={pageState.loading}
|
|
273
|
+
/>
|
|
274
|
+
</Box>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as UserSessions } from './components/user-sessions';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const translations = {
|
|
2
|
+
zh: {
|
|
3
|
+
confirm: '确认',
|
|
4
|
+
cancel: '取消',
|
|
5
|
+
visitorId: '设备标识',
|
|
6
|
+
deviceType: '设备类型',
|
|
7
|
+
platform: '操作系统',
|
|
8
|
+
walletOS: '钱包类型',
|
|
9
|
+
role: '角色',
|
|
10
|
+
fullname: '姓名',
|
|
11
|
+
email: '邮箱',
|
|
12
|
+
avatar: '头像',
|
|
13
|
+
user: '用户',
|
|
14
|
+
updatedAt: '最近活动时间',
|
|
15
|
+
lastLoginIp: '最近活动地址',
|
|
16
|
+
actions: '操作',
|
|
17
|
+
logoutAll: '注销所有会话',
|
|
18
|
+
logoutAllTips: '不会注销当前会话',
|
|
19
|
+
logout: '注销',
|
|
20
|
+
currentSession: '当前会话',
|
|
21
|
+
unknown: '未知',
|
|
22
|
+
logoutThisSession: '注销指定会话',
|
|
23
|
+
logoutThisSessionConfirm: '确定要注销此会话吗?',
|
|
24
|
+
logoutAllSession: '注销所有会话',
|
|
25
|
+
logoutAllSessionConfirm: '确定要注销所有会话吗?',
|
|
26
|
+
},
|
|
27
|
+
en: {
|
|
28
|
+
confirm: 'Confirm',
|
|
29
|
+
cancel: 'Cancel',
|
|
30
|
+
visitorId: 'Device ID',
|
|
31
|
+
deviceType: 'Device Type',
|
|
32
|
+
platform: 'Platform',
|
|
33
|
+
walletOS: 'Wallet OS',
|
|
34
|
+
role: 'Role',
|
|
35
|
+
fullname: 'Fullname',
|
|
36
|
+
email: 'Email',
|
|
37
|
+
avatar: 'Avatar',
|
|
38
|
+
user: 'User',
|
|
39
|
+
updatedAt: 'Last Active Time',
|
|
40
|
+
actions: 'Actions',
|
|
41
|
+
lastLoginIp: 'Last Login IP',
|
|
42
|
+
logoutAll: 'Logout all',
|
|
43
|
+
logoutAllTips: 'Will not logout current session',
|
|
44
|
+
logout: 'Logout',
|
|
45
|
+
currentSession: 'Current Session',
|
|
46
|
+
unknown: 'Unknown',
|
|
47
|
+
logoutThisSession: 'Logout this session',
|
|
48
|
+
logoutThisSessionConfirm: 'Are you sure to logout this session?',
|
|
49
|
+
logoutAllSession: 'Logout all sessions',
|
|
50
|
+
logoutAllSessionConfirm: 'Are you sure to logout all sessions?',
|
|
51
|
+
},
|
|
52
|
+
};
|