@blocklet/ui-react 2.11.3 → 2.11.5

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.
@@ -57,7 +57,7 @@ export default function Passport({ user, ...rest }) {
57
57
  gridTemplateColumns: {
58
58
  xs: "repeat(2, 1fr)",
59
59
  sm: "repeat(3, 1fr)",
60
- md: "repeat(2, 1fr)"
60
+ md: "repeat(4, 1fr)"
61
61
  },
62
62
  gap: 2.5,
63
63
  ...rest.sx
@@ -55,6 +55,9 @@ export default function ThirdPartyItem({
55
55
  title: t("thirdPartyLogin.confirmUnbind", { name: title }),
56
56
  content: t("thirdPartyLogin.confirmUnbindDescription", { name: title }),
57
57
  confirmButtonText: t("common.confirm"),
58
+ confirmButtonProps: {
59
+ color: "error"
60
+ },
58
61
  cancelButtonText: t("common.cancel"),
59
62
  onConfirm(close) {
60
63
  unbindOAuth({
@@ -14,7 +14,7 @@ import { translate } from "@arcblock/ux/lib/Locale/util";
14
14
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
15
15
  import { ErrorFallback } from "@arcblock/ux/lib/ErrorBoundary";
16
16
  import cloneDeep from "lodash/cloneDeep";
17
- import { getQuery, withQuery, joinURL } from "ufo";
17
+ import { getQuery, withQuery, joinURL, withoutTrailingSlash } from "ufo";
18
18
  import { PROFILE_URL } from "@arcblock/ux/lib/Util/constant";
19
19
  import Footer from "../../Footer/index.js";
20
20
  import Header from "../../Header/index.js";
@@ -157,7 +157,7 @@ export default function UserCenter({
157
157
  }).filter((x) => isMyself || !x.isPrivate);
158
158
  }, [formattedBlocklet, userState.data, privacyState?.data, locale, defaultTabs, isMyself]);
159
159
  const currentActiveTab = useCreation(() => {
160
- return userCenterTabs.find((x) => x.value === currentTab);
160
+ return userCenterTabs.find((x) => withoutTrailingSlash(x.value) === withoutTrailingSlash(currentTab));
161
161
  }, [userCenterTabs]);
162
162
  const handleChangeTab = useMemoizedFn((value) => {
163
163
  const findTab = userCenterTabs.find((x) => x.value === value);
@@ -198,10 +198,6 @@ export default function UserCenter({
198
198
  Box,
199
199
  {
200
200
  sx: {
201
- width: {
202
- sx: "100%",
203
- md: 420
204
- },
205
201
  maxWidth: "100%",
206
202
  position: "relative"
207
203
  },
@@ -322,7 +318,7 @@ export default function UserCenter({
322
318
  orientation: isMobile ? "horizontal" : "vertical",
323
319
  variant: "line",
324
320
  tabs: userCenterTabs,
325
- current: currentTab,
321
+ current: currentActiveTab?.value ?? currentTab,
326
322
  onChange: handleChangeTab,
327
323
  sx: {
328
324
  width: isMobile ? "100%" : 180,
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import Datatable from "@arcblock/ux/lib/Datatable";
3
- import { useCreation, useMemoizedFn, useRequest } from "ahooks";
3
+ import { useCreation, useMemoizedFn, useReactive, useRequest } from "ahooks";
4
4
  import { translate } from "@arcblock/ux/lib/Locale/util";
5
5
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
6
6
  import RelativeTime from "@arcblock/ux/lib/RelativeTime";
@@ -9,13 +9,14 @@ import UAParser from "ua-parser-js";
9
9
  import { getVisitorId } from "@arcblock/ux/lib/Util";
10
10
  import { useConfirm } from "@arcblock/ux/lib/Dialog";
11
11
  import pAll from "p-all";
12
- import { Box, Button, Tooltip, Typography } from "@mui/material";
13
- import { useContext } from "react";
12
+ import PQueue from "p-queue";
13
+ import { Box, Button, CircularProgress, Tooltip, Typography } from "@mui/material";
14
+ import { memo, useContext, useEffect } from "react";
14
15
  import { SessionContext } from "@arcblock/did-connect/lib/Session";
15
16
  import UserSessionInfo from "./user-session-info.js";
16
17
  import { client } from "../../libs/client.js";
17
18
  import { translations } from "../libs/locales.js";
18
- import { batchIp2Region } from "../libs/utils.js";
19
+ import { ip2Region } from "../libs/utils.js";
19
20
  const parseUa = (ua) => {
20
21
  const parser = new UAParser(ua, {
21
22
  // eslint-disable-next-line no-useless-escape
@@ -24,6 +25,30 @@ const parseUa = (ua) => {
24
25
  const result = parser.getResult();
25
26
  return result;
26
27
  };
28
+ const queue = new PQueue({ concurrency: 1 });
29
+ const UserSessionIp = memo(({ userSession }) => {
30
+ const currentState = useReactive({
31
+ loading: true,
32
+ ipRegion: ""
33
+ });
34
+ const { t } = useLocaleContext();
35
+ useEffect(() => {
36
+ queue.add(async () => {
37
+ try {
38
+ currentState.ipRegion = await ip2Region(userSession.lastLoginIp);
39
+ } finally {
40
+ currentState.loading = false;
41
+ }
42
+ });
43
+ }, [currentState, userSession.lastLoginIp]);
44
+ return /* @__PURE__ */ jsx(Box, { children: currentState.ipRegion ? /* @__PURE__ */ jsxs(Fragment, { children: [
45
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", children: currentState.ipRegion }),
46
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "grey", children: userSession.lastLoginIp || t("unknown") })
47
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
48
+ /* @__PURE__ */ jsx(Typography, { children: userSession.lastLoginIp || t("unknown") }),
49
+ currentState.loading ? /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "grey", sx: { textAlign: "center" }, children: /* @__PURE__ */ jsx(CircularProgress, { size: 12, color: "inherit" }) }) : null
50
+ ] }) });
51
+ });
27
52
  export default function UserSessions({
28
53
  user,
29
54
  showAction = true,
@@ -39,18 +64,9 @@ export default function UserSessions({
39
64
  const getData = useMemoizedFn(async () => {
40
65
  let data = user?.userSessions || [];
41
66
  try {
42
- if (!data && session.user) {
67
+ if (!user?.userSessions && session.user) {
43
68
  data = await client.userSession.getMyLoginSessions();
44
69
  }
45
- const ipIndexList = data?.map((x, index) => [index, x.lastLoginIp]).filter((x) => !!x[1]);
46
- const ipList = ipIndexList?.map((x) => x[1]);
47
- const result = await batchIp2Region(ipList);
48
- for (let index = 0; index < result.length; index++) {
49
- const x = result[index];
50
- const ipIndexItem = ipIndexList[index];
51
- const dataItem = data[ipIndexItem[0]];
52
- dataItem.ipRegion = x;
53
- }
54
70
  } catch (e) {
55
71
  console.warn("Failed to convert ip to region");
56
72
  console.error(e);
@@ -67,20 +83,14 @@ export default function UserSessions({
67
83
  const safeData = useCreation(() => {
68
84
  return pageState.data || [];
69
85
  }, [pageState.data]);
70
- const ipRegionMap = useCreation(() => {
71
- return safeData.reduce(
72
- (acc, x) => {
73
- acc[x.lastLoginIp] = x.ipRegion;
74
- return acc;
75
- },
76
- {}
77
- );
78
- }, [safeData]);
79
86
  const logout = useMemoizedFn(({ visitorId }) => {
80
87
  confirmApi.open({
81
88
  title: t("logoutThisSession"),
82
89
  content: t("logoutThisSessionConfirm"),
83
90
  confirmButtonText: t("confirm"),
91
+ confirmButtonProps: {
92
+ color: "error"
93
+ },
84
94
  cancelButtonText: t("cancel"),
85
95
  onConfirm: async () => {
86
96
  await client.user.logout({ visitorId });
@@ -98,6 +108,9 @@ export default function UserSessions({
98
108
  title: t("logoutAllSession"),
99
109
  content: t("logoutAllSessionConfirm"),
100
110
  confirmButtonText: t("confirm"),
111
+ confirmButtonProps: {
112
+ color: "error"
113
+ },
101
114
  cancelButtonText: t("cancel"),
102
115
  onConfirm: async () => {
103
116
  const list = otherUserSessions.map((x) => {
@@ -210,10 +223,7 @@ export default function UserSessions({
210
223
  options: {
211
224
  customBodyRenderLite: (rawIndex) => {
212
225
  const x = safeData[rawIndex];
213
- return /* @__PURE__ */ jsx(Box, { children: ipRegionMap[x.lastLoginIp] ? /* @__PURE__ */ jsxs(Fragment, { children: [
214
- /* @__PURE__ */ jsx(Typography, { variant: "body2", children: ipRegionMap[x.lastLoginIp] }),
215
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "grey", children: x.lastLoginIp || t("unknown") })
216
- ] }) : /* @__PURE__ */ jsx(Typography, { children: x.lastLoginIp || t("unknown") }) });
226
+ return /* @__PURE__ */ jsx(UserSessionIp, { userSession: x });
217
227
  }
218
228
  }
219
229
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/ui-react",
3
- "version": "2.11.3",
3
+ "version": "2.11.5",
4
4
  "description": "Some useful front-end web components that can be used in Blocklets.",
5
5
  "keywords": [
6
6
  "react",
@@ -32,8 +32,8 @@
32
32
  "url": "https://github.com/ArcBlock/ux/issues"
33
33
  },
34
34
  "dependencies": {
35
- "@arcblock/bridge": "^2.11.3",
36
- "@arcblock/react-hooks": "^2.11.3",
35
+ "@arcblock/bridge": "^2.11.5",
36
+ "@arcblock/react-hooks": "^2.11.5",
37
37
  "@blocklet/did-space-react": "^0.5.83",
38
38
  "@iconify-icons/logos": "^1.2.36",
39
39
  "@iconify-icons/material-symbols": "^1.2.58",
@@ -47,6 +47,7 @@
47
47
  "is-url": "^1.2.4",
48
48
  "lodash": "^4.17.21",
49
49
  "p-all": "^5.0.0",
50
+ "p-queue": "^6.6.2",
50
51
  "p-wait-for": "^5.0.2",
51
52
  "prop-types": "^15.8.1",
52
53
  "react-error-boundary": "^3.1.4",
@@ -56,9 +57,9 @@
56
57
  "ufo": "^1.5.3"
57
58
  },
58
59
  "peerDependencies": {
59
- "@arcblock/did-connect": "^2.9.79",
60
- "@arcblock/ux": "^2.9.79",
61
- "@blocklet/js-sdk": "^1.16.33",
60
+ "@arcblock/did-connect": "^2.11.3",
61
+ "@arcblock/ux": "^2.11.3",
62
+ "@blocklet/js-sdk": "^1.16.36",
62
63
  "@emotion/react": "^11.10.4",
63
64
  "@emotion/styled": "^11.10.4",
64
65
  "@mui/icons-material": "^5.15.0",
@@ -80,5 +81,5 @@
80
81
  "jest": "^29.7.0",
81
82
  "unbuild": "^2.0.0"
82
83
  },
83
- "gitHead": "f17cdd23c703c834a348dffca7786dbd0b0b40c3"
84
+ "gitHead": "ad0f1851be1f8ee0f655131ad3734fceb807f9de"
84
85
  }
@@ -68,7 +68,7 @@ export default function Passport({ user, ...rest }: { user: User } & BoxProps) {
68
68
  gridTemplateColumns: {
69
69
  xs: 'repeat(2, 1fr)',
70
70
  sm: 'repeat(3, 1fr)',
71
- md: 'repeat(2, 1fr)',
71
+ md: 'repeat(4, 1fr)',
72
72
  },
73
73
  gap: 2.5,
74
74
  ...rest.sx,
@@ -71,6 +71,9 @@ export default function ThirdPartyItem({
71
71
  title: t('thirdPartyLogin.confirmUnbind', { name: title }),
72
72
  content: t('thirdPartyLogin.confirmUnbindDescription', { name: title }),
73
73
  confirmButtonText: t('common.confirm'),
74
+ confirmButtonProps: {
75
+ color: 'error',
76
+ },
74
77
  cancelButtonText: t('common.cancel'),
75
78
  onConfirm(close: () => void) {
76
79
  unbindOAuth({
@@ -15,7 +15,7 @@ import { translate } from '@arcblock/ux/lib/Locale/util';
15
15
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
16
16
  import { ErrorFallback } from '@arcblock/ux/lib/ErrorBoundary';
17
17
  import cloneDeep from 'lodash/cloneDeep';
18
- import { getQuery, withQuery, joinURL } from 'ufo';
18
+ import { getQuery, withQuery, joinURL, withoutTrailingSlash } from 'ufo';
19
19
  import type { AxiosError } from 'axios';
20
20
  import type { UserPublicInfo } from '@blocklet/js-sdk';
21
21
 
@@ -190,7 +190,7 @@ export default function UserCenter({
190
190
  }, [formattedBlocklet, userState.data, privacyState?.data, locale, defaultTabs, isMyself]);
191
191
 
192
192
  const currentActiveTab = useCreation(() => {
193
- return userCenterTabs.find((x) => x.value === currentTab);
193
+ return userCenterTabs.find((x) => withoutTrailingSlash(x.value) === withoutTrailingSlash(currentTab));
194
194
  }, [userCenterTabs]);
195
195
 
196
196
  const handleChangeTab = useMemoizedFn((value) => {
@@ -235,10 +235,6 @@ export default function UserCenter({
235
235
  return (
236
236
  <Box
237
237
  sx={{
238
- width: {
239
- sx: '100%',
240
- md: 420,
241
- },
242
238
  maxWidth: '100%',
243
239
  position: 'relative',
244
240
  }}>
@@ -364,7 +360,7 @@ export default function UserCenter({
364
360
  orientation={isMobile ? 'horizontal' : 'vertical'}
365
361
  variant="line"
366
362
  tabs={userCenterTabs}
367
- current={currentTab}
363
+ current={currentActiveTab?.value ?? currentTab}
368
364
  onChange={handleChangeTab}
369
365
  sx={{
370
366
  width: isMobile ? '100%' : 180,
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable no-nested-ternary */
2
2
  /* eslint-disable react/no-unstable-nested-components */
3
3
  import Datatable from '@arcblock/ux/lib/Datatable';
4
- import { useCreation, useMemoizedFn, useRequest } from 'ahooks';
4
+ import { useCreation, useMemoizedFn, useReactive, useRequest } from 'ahooks';
5
5
  import { translate } from '@arcblock/ux/lib/Locale/util';
6
6
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
7
7
  import RelativeTime from '@arcblock/ux/lib/RelativeTime';
@@ -10,8 +10,9 @@ import UAParser from 'ua-parser-js';
10
10
  import { getVisitorId } from '@arcblock/ux/lib/Util';
11
11
  import { useConfirm } from '@arcblock/ux/lib/Dialog';
12
12
  import pAll from 'p-all';
13
- import { Box, Button, Tooltip, Typography } from '@mui/material';
14
- import { ReactElement, useContext } from 'react';
13
+ import PQueue from 'p-queue';
14
+ import { Box, Button, CircularProgress, Tooltip, Typography } from '@mui/material';
15
+ import { memo, ReactElement, useContext, useEffect } from 'react';
15
16
  import { UserSession } from '@blocklet/js-sdk';
16
17
  import { SessionContext } from '@arcblock/did-connect/lib/Session';
17
18
 
@@ -19,7 +20,7 @@ import UserSessionInfo from './user-session-info';
19
20
  import { User, SessionContext as TSessionContext } from '../../@types';
20
21
  import { client } from '../../libs/client';
21
22
  import { translations } from '../libs/locales';
22
- import { batchIp2Region } from '../libs/utils';
23
+ import { ip2Region } from '../libs/utils';
23
24
 
24
25
  const parseUa = (ua: string) => {
25
26
  const parser = new UAParser(ua, {
@@ -30,6 +31,49 @@ const parseUa = (ua: string) => {
30
31
  return result;
31
32
  };
32
33
 
34
+ const queue = new PQueue({ concurrency: 1 });
35
+
36
+ const UserSessionIp = memo(({ userSession }: { userSession: any }) => {
37
+ const currentState = useReactive({
38
+ loading: true,
39
+ ipRegion: '',
40
+ });
41
+
42
+ const { t } = useLocaleContext();
43
+
44
+ useEffect(() => {
45
+ queue.add(async () => {
46
+ try {
47
+ currentState.ipRegion = await ip2Region(userSession.lastLoginIp);
48
+ } finally {
49
+ currentState.loading = false;
50
+ }
51
+ });
52
+ }, [currentState, userSession.lastLoginIp]);
53
+
54
+ return (
55
+ <Box>
56
+ {currentState.ipRegion ? (
57
+ <>
58
+ <Typography variant="body2">{currentState.ipRegion}</Typography>
59
+ <Typography variant="body2" color="grey">
60
+ {userSession.lastLoginIp || t('unknown')}
61
+ </Typography>
62
+ </>
63
+ ) : (
64
+ <>
65
+ <Typography>{userSession.lastLoginIp || t('unknown')}</Typography>
66
+ {currentState.loading ? (
67
+ <Typography variant="body2" color="grey" sx={{ textAlign: 'center' }}>
68
+ <CircularProgress size={12} color="inherit" />
69
+ </Typography>
70
+ ) : null}
71
+ </>
72
+ )}
73
+ </Box>
74
+ );
75
+ });
76
+
33
77
  export default function UserSessions({
34
78
  user,
35
79
  showAction = true,
@@ -51,20 +95,9 @@ export default function UserSessions({
51
95
  const getData: () => Promise<(UserSession & { ipRegion?: string })[]> = useMemoizedFn(async () => {
52
96
  let data = (user?.userSessions || []) as (UserSession & { ipRegion?: string })[];
53
97
  try {
54
- if (!data && session.user) {
55
- // 依赖新版 js-sdk
98
+ if (!user?.userSessions && session.user) {
56
99
  data = await client.userSession.getMyLoginSessions();
57
100
  }
58
- // FIXME: @zhanghan 优化获取 IP 信息的处理逻辑
59
- const ipIndexList = data?.map((x, index) => [index, x.lastLoginIp] as [number, string]).filter((x) => !!x[1]);
60
- const ipList = ipIndexList?.map((x) => x[1]);
61
- const result = await batchIp2Region(ipList);
62
- for (let index = 0; index < result.length; index++) {
63
- const x = result[index];
64
- const ipIndexItem = ipIndexList[index];
65
- const dataItem = data[ipIndexItem[0]];
66
- dataItem.ipRegion = x;
67
- }
68
101
  } catch (e) {
69
102
  console.warn('Failed to convert ip to region');
70
103
  console.error(e);
@@ -85,21 +118,14 @@ export default function UserSessions({
85
118
  return pageState.data || [];
86
119
  }, [pageState.data]);
87
120
 
88
- const ipRegionMap = useCreation(() => {
89
- return safeData.reduce(
90
- (acc, x) => {
91
- acc[x.lastLoginIp] = x.ipRegion;
92
- return acc;
93
- },
94
- {} as { [key: string]: string | undefined }
95
- );
96
- }, [safeData]);
97
-
98
121
  const logout = useMemoizedFn(({ visitorId }) => {
99
122
  confirmApi.open({
100
123
  title: t('logoutThisSession'),
101
124
  content: t('logoutThisSessionConfirm'),
102
125
  confirmButtonText: t('confirm'),
126
+ confirmButtonProps: {
127
+ color: 'error',
128
+ },
103
129
  cancelButtonText: t('cancel'),
104
130
  onConfirm: async () => {
105
131
  await client.user.logout({ visitorId });
@@ -117,6 +143,9 @@ export default function UserSessions({
117
143
  title: t('logoutAllSession'),
118
144
  content: t('logoutAllSessionConfirm'),
119
145
  confirmButtonText: t('confirm'),
146
+ confirmButtonProps: {
147
+ color: 'error',
148
+ },
120
149
  cancelButtonText: t('cancel'),
121
150
  onConfirm: async () => {
122
151
  const list = otherUserSessions.map((x) => {
@@ -240,20 +269,7 @@ export default function UserSessions({
240
269
  options: {
241
270
  customBodyRenderLite: (rawIndex: number) => {
242
271
  const x = safeData[rawIndex];
243
- return (
244
- <Box>
245
- {ipRegionMap[x.lastLoginIp] ? (
246
- <>
247
- <Typography variant="body2">{ipRegionMap[x.lastLoginIp]}</Typography>
248
- <Typography variant="body2" color="grey">
249
- {x.lastLoginIp || t('unknown')}
250
- </Typography>
251
- </>
252
- ) : (
253
- <Typography>{x.lastLoginIp || t('unknown')}</Typography>
254
- )}
255
- </Box>
256
- );
272
+ return <UserSessionIp userSession={x} />;
257
273
  },
258
274
  },
259
275
  },