@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.
Files changed (104) hide show
  1. package/es/@types/index.d.ts +3 -5
  2. package/es/Dashboard/index.d.ts +5 -6
  3. package/es/Dashboard/index.js +5 -5
  4. package/es/Footer/index.js +2 -2
  5. package/es/Header/index.js +5 -5
  6. package/es/UserCenter/components/notification.js +1 -1
  7. package/es/UserCenter/components/passport.js +1 -2
  8. package/es/UserCenter/components/privacy.js +1 -1
  9. package/es/UserCenter/components/settings.js +9 -1
  10. package/es/UserCenter/components/storage/connect-to.d.ts +1 -1
  11. package/es/UserCenter/components/storage/connect-to.js +9 -3
  12. package/es/UserCenter/components/storage/delete.d.ts +1 -1
  13. package/es/UserCenter/components/storage/delete.js +4 -1
  14. package/es/UserCenter/components/storage/item.js +1 -1
  15. package/es/UserCenter/components/user-center.d.ts +2 -2
  16. package/es/UserCenter/components/user-center.js +15 -6
  17. package/es/UserCenter/libs/locales.d.ts +2 -0
  18. package/es/UserCenter/libs/locales.js +2 -0
  19. package/es/UserSessions/components/user-session-info.d.ts +6 -0
  20. package/es/UserSessions/components/user-session-info.js +58 -0
  21. package/es/UserSessions/components/user-sessions.d.ts +9 -0
  22. package/es/UserSessions/components/user-sessions.js +255 -0
  23. package/es/UserSessions/index.d.ts +1 -0
  24. package/es/UserSessions/index.js +1 -0
  25. package/es/UserSessions/libs/locales.d.ts +52 -0
  26. package/es/UserSessions/libs/locales.js +52 -0
  27. package/es/UserSessions/libs/utils.d.ts +2 -0
  28. package/es/UserSessions/libs/utils.js +73 -0
  29. package/es/blocklets.js +6 -6
  30. package/es/common/header-addons.d.ts +3 -4
  31. package/es/common/header-addons.js +4 -4
  32. package/es/contexts/config-user-space.js +2 -2
  33. package/es/index.d.ts +1 -0
  34. package/es/index.js +1 -0
  35. package/es/types.d.ts +2 -2
  36. package/es/types.js +2 -2
  37. package/lib/@types/index.d.ts +3 -5
  38. package/lib/Dashboard/index.d.ts +5 -6
  39. package/lib/Dashboard/index.js +4 -4
  40. package/lib/Footer/index.js +1 -1
  41. package/lib/Header/index.js +4 -4
  42. package/lib/UserCenter/components/notification.js +1 -1
  43. package/lib/UserCenter/components/passport.js +1 -2
  44. package/lib/UserCenter/components/privacy.js +1 -1
  45. package/lib/UserCenter/components/settings.js +10 -1
  46. package/lib/UserCenter/components/storage/connect-to.d.ts +1 -1
  47. package/lib/UserCenter/components/storage/connect-to.js +3 -3
  48. package/lib/UserCenter/components/storage/delete.d.ts +1 -1
  49. package/lib/UserCenter/components/storage/item.js +1 -1
  50. package/lib/UserCenter/components/user-center.d.ts +2 -2
  51. package/lib/UserCenter/components/user-center.js +20 -10
  52. package/lib/UserCenter/libs/locales.d.ts +2 -0
  53. package/lib/UserCenter/libs/locales.js +2 -0
  54. package/lib/UserSessions/components/user-session-info.d.ts +6 -0
  55. package/lib/UserSessions/components/user-session-info.js +68 -0
  56. package/lib/UserSessions/components/user-sessions.d.ts +9 -0
  57. package/lib/UserSessions/components/user-sessions.js +282 -0
  58. package/lib/UserSessions/index.d.ts +1 -0
  59. package/lib/UserSessions/index.js +13 -0
  60. package/lib/UserSessions/libs/locales.d.ts +52 -0
  61. package/lib/UserSessions/libs/locales.js +58 -0
  62. package/lib/UserSessions/libs/utils.d.ts +2 -0
  63. package/lib/UserSessions/libs/utils.js +80 -0
  64. package/lib/blocklets.js +6 -6
  65. package/lib/common/header-addons.d.ts +3 -4
  66. package/lib/common/header-addons.js +3 -3
  67. package/lib/contexts/config-user-space.js +1 -1
  68. package/lib/index.d.ts +1 -0
  69. package/lib/index.js +12 -0
  70. package/lib/types.d.ts +2 -2
  71. package/lib/types.js +3 -3
  72. package/package.json +14 -6
  73. package/src/@types/index.ts +3 -5
  74. package/src/Dashboard/index.jsx +7 -3
  75. package/src/Footer/index.jsx +2 -2
  76. package/src/Header/index.jsx +5 -3
  77. package/src/Icon/index.jsx +1 -0
  78. package/src/UserCenter/components/notification.tsx +2 -2
  79. package/src/UserCenter/components/passport.tsx +1 -2
  80. package/src/UserCenter/components/privacy.tsx +1 -1
  81. package/src/UserCenter/components/settings.tsx +15 -2
  82. package/src/UserCenter/components/storage/connect-to.tsx +17 -11
  83. package/src/UserCenter/components/storage/delete.tsx +8 -2
  84. package/src/UserCenter/components/storage/item.tsx +2 -3
  85. package/src/UserCenter/components/storage/preview-nft.tsx +1 -1
  86. package/src/UserCenter/components/user-center.tsx +21 -14
  87. package/src/UserCenter/components/webhook-item.tsx +1 -1
  88. package/src/UserCenter/libs/locales.ts +2 -0
  89. package/src/UserSessions/components/user-session-info.tsx +52 -0
  90. package/src/UserSessions/components/user-sessions.tsx +276 -0
  91. package/src/UserSessions/index.tsx +1 -0
  92. package/src/UserSessions/libs/locales.ts +52 -0
  93. package/src/UserSessions/libs/utils.ts +82 -0
  94. package/src/blocklets.js +6 -6
  95. package/src/common/header-addons.jsx +2 -2
  96. package/src/contexts/config-user-space.tsx +12 -11
  97. package/src/index.ts +1 -0
  98. package/src/{UserCenter/libs → libs}/client.ts +1 -0
  99. package/src/libs/spaces.tsx +2 -2
  100. package/src/types.js +2 -2
  101. /package/es/{UserCenter/libs → libs}/client.d.ts +0 -0
  102. /package/es/{UserCenter/libs → libs}/client.js +0 -0
  103. /package/lib/{UserCenter/libs → libs}/client.d.ts +0 -0
  104. /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({ onConnect, storageEndpoint, ...rest }: { onConnect: (spaceGateway: SpaceGateway) => Promise<void>, storageEndpoint: string}) {
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
- // @ts-expect-error
22
- checkFn: axios.create({ baseURL: joinURL(window.location.origin, window.env && window.env.apiPrefix ? window.env.apiPrefix : '/') }).get,
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: window.blocklet.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
- {storageEndpoint ? t('storage.spaces.connect.useWalletReconnect') : t('storage.spaces.connect.useWallet')}
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 React, { useState } from 'react';
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({ spaceGateway, onDeleteSpace }: {spaceGateway: SpaceGateway, onDeleteSpace: (spaceGateway: SpaceGateway) => Promise<void> }) {
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, alt: 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 { Header, Footer } from '../../';
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 '../libs/client';
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
- readonly headerProps?: object;
50
- readonly footerProps?: object;
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 await client.user.getUserPublicInfo({ did: currentDid });
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
+ };