@blocklet/ui-react 2.13.33 → 2.13.35

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.
@@ -70,7 +70,7 @@ export default function ConfigProfile({ user, onSave }) {
70
70
  size: "small",
71
71
  IconComponent: (props) => /* @__PURE__ */ jsx(ArrowDownwardIcon, { ...props, width: 20, height: 20 }),
72
72
  sx: {
73
- minWidth: 300,
73
+ minWidth: 200,
74
74
  "&:hover": {
75
75
  "fieldset.MuiOutlinedInput-notchedOutline": {
76
76
  borderColor: "divider"
@@ -100,7 +100,8 @@ export default function DangerZone() {
100
100
  display: "flex",
101
101
  alignItems: "center",
102
102
  gap: 1,
103
- justifyContent: "space-between"
103
+ justifyContent: "space-between",
104
+ flexWrap: "wrap"
104
105
  },
105
106
  children: [
106
107
  /* @__PURE__ */ jsxs(Box, { children: [
@@ -203,6 +203,7 @@ function EditableField({
203
203
  whiteSpace: "pre-wrap",
204
204
  ...inline ? { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } : {}
205
205
  },
206
+ component: "div",
206
207
  onMouseEnter: handleMouseEnter,
207
208
  onMouseLeave: handleMouseLeave,
208
209
  children: renderValue ? renderValue(value) : value
@@ -1,4 +1,5 @@
1
1
  import { User } from '../../@types';
2
- export default function Notification({ user }: {
2
+ export default function Notification({ user, isMobile }: {
3
3
  user: User;
4
+ isMobile: boolean;
4
5
  }): import("react").JSX.Element;
@@ -9,6 +9,7 @@ import Toast from "@arcblock/ux/lib/Toast";
9
9
  import { getWalletDid } from "@arcblock/ux/lib/SessionUser/libs/utils";
10
10
  import { translate } from "@arcblock/ux/lib/Locale/util";
11
11
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
12
+ import DID from "@arcblock/ux/lib/DID";
12
13
  import { translations } from "../libs/locales.js";
13
14
  import WebhookItem from "./webhook-item.js";
14
15
  import { formatAxiosError } from "../libs/utils.js";
@@ -17,7 +18,8 @@ function NotificationItem({
17
18
  title,
18
19
  description,
19
20
  value,
20
- onChange
21
+ onChange,
22
+ isMobile
21
23
  }) {
22
24
  return /* @__PURE__ */ jsx(
23
25
  Switch,
@@ -31,9 +33,10 @@ function NotificationItem({
31
33
  sx: {
32
34
  fontSize: 14,
33
35
  display: "flex",
34
- flexFlow: "wrap",
36
+ flexFlow: isMobile ? "wrap" : "nowrap",
35
37
  columnGap: 1,
36
- flex: 1
38
+ flex: 1,
39
+ whiteSpace: "nowrap"
37
40
  },
38
41
  children: [
39
42
  title,
@@ -47,7 +50,7 @@ function NotificationItem({
47
50
  }
48
51
  );
49
52
  }
50
- export default function Notification({ user }) {
53
+ export default function Notification({ user, isMobile }) {
51
54
  const { locale } = useLocaleContext();
52
55
  const t = useMemoizedFn((key, data = {}) => {
53
56
  return translate(translations, key, locale, "en", data);
@@ -175,15 +178,29 @@ export default function Notification({ user }) {
175
178
  NotificationItem,
176
179
  {
177
180
  value: notifications.wallet,
181
+ isMobile,
178
182
  title: t("walletNotification"),
179
183
  onChange: (event) => handleChangeSwitch("wallet", event.target.checked),
180
- description: getWalletDid(user) && /* @__PURE__ */ jsx(Typography, { component: "span", color: "text.secondary", fontSize: 13, children: getWalletDid(user) })
184
+ description: getWalletDid(user) && /* @__PURE__ */ jsx(
185
+ DID,
186
+ {
187
+ did: getWalletDid(user),
188
+ showQrcode: false,
189
+ showAvatar: !isMobile,
190
+ copyable: true,
191
+ compact: true,
192
+ responsive: true,
193
+ locale,
194
+ startChars: isMobile ? 2 : 6
195
+ }
196
+ )
181
197
  }
182
198
  ),
183
199
  /* @__PURE__ */ jsx(
184
200
  NotificationItem,
185
201
  {
186
202
  value: notifications.email,
203
+ isMobile,
187
204
  onChange: (event) => handleChangeSwitch("email", event.target.checked),
188
205
  title: t("emailNotification"),
189
206
  description: user?.email && /* @__PURE__ */ jsx(Typography, { component: "span", color: "text.secondary", fontSize: 13, children: user?.email })
@@ -192,6 +209,7 @@ export default function Notification({ user }) {
192
209
  /* @__PURE__ */ jsx(
193
210
  NotificationItem,
194
211
  {
212
+ isMobile,
195
213
  value: notifications.push,
196
214
  title: t("pushNotification"),
197
215
  onChange: (event) => handleChangeSwitch("push", event.target.checked)
@@ -1,9 +1,10 @@
1
1
  import type { BoxProps } from '@mui/material';
2
2
  import { User, UserCenterTab } from '../../@types';
3
- export default function Settings({ user, settings, onSave, ...rest }: {
3
+ export default function Settings({ user, settings, onSave, isMobile, ...rest }: {
4
4
  user: User;
5
5
  onSave: (type: 'privacy' | 'profile') => void;
6
6
  settings: {
7
7
  userCenterTabs: UserCenterTab[];
8
8
  };
9
+ isMobile: boolean;
9
10
  } & BoxProps): import("react").JSX.Element;
@@ -18,6 +18,7 @@ export default function Settings({
18
18
  user,
19
19
  settings,
20
20
  onSave,
21
+ isMobile,
21
22
  ...rest
22
23
  }) {
23
24
  const { locale } = useLocaleContext();
@@ -47,7 +48,7 @@ export default function Settings({
47
48
  {
48
49
  label: t("notificationManagement"),
49
50
  value: "notification",
50
- content: /* @__PURE__ */ jsx(Notification, { user })
51
+ content: /* @__PURE__ */ jsx(Notification, { user, isMobile })
51
52
  },
52
53
  {
53
54
  label: t("thirdPartyLogin.title"),
@@ -82,7 +83,7 @@ export default function Settings({
82
83
  }
83
84
  }
84
85
  ].filter(Boolean);
85
- }, [user, privacyConfigList]);
86
+ }, [user, privacyConfigList, isMobile]);
86
87
  useEffect(() => {
87
88
  const id = window.location.hash.slice(1);
88
89
  if (id) {
@@ -238,7 +238,8 @@ export default function UserCenter({
238
238
  await session.refresh();
239
239
  }
240
240
  return null;
241
- }
241
+ },
242
+ isMobile
242
243
  }
243
244
  );
244
245
  }, [userState.data, userCenterTabs, privacyState.data, privacyState.runAsync]);
@@ -452,7 +453,7 @@ export default function UserCenter({
452
453
  fontWeight: 400
453
454
  }
454
455
  },
455
- ".MuiTabs-scroller": {
456
+ ".MuiTabs-root": {
456
457
  "&:after": {
457
458
  content: '""',
458
459
  display: "block",
@@ -1,15 +1,16 @@
1
1
  import { UserSession } from '@blocklet/js-sdk';
2
2
  import { User } from '../../@types';
3
- export default function UserSessions({ user, showAction, showUser, getUserSessions, }: {
3
+ export default function UserSessions({ user, showAction, showUser, getUserSessions, showOffline, }: {
4
4
  readonly user: User & {
5
5
  userSessions?: any[];
6
6
  };
7
7
  readonly showAction?: boolean;
8
8
  readonly showUser?: boolean;
9
+ readonly showOffline?: boolean;
9
10
  readonly getUserSessions: (params: {
10
11
  page: number;
11
12
  pageSize: number;
12
- status?: 'online' | 'expired';
13
+ status?: 'online' | 'expired' | 'offline';
13
14
  }) => Promise<{
14
15
  paging: {
15
16
  total: number;
@@ -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, useReactive, useRequest } from "ahooks";
3
+ import { useCreation, useMemoizedFn, useMount, 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";
@@ -20,6 +20,7 @@ import {
20
20
  useMediaQuery
21
21
  } from "@mui/material";
22
22
  import { memo, useEffect } from "react";
23
+ import { mergeSx } from "@arcblock/ux/lib/Util/style";
23
24
  import useMobile from "../../hooks/use-mobile.js";
24
25
  import UserSessionInfo from "./user-session-info.js";
25
26
  import { client } from "../../libs/client.js";
@@ -33,6 +34,11 @@ const parseUa = (ua) => {
33
34
  const result = parser.getResult();
34
35
  return result;
35
36
  };
37
+ const textRightStyle = {
38
+ display: "flex",
39
+ justifyContent: "flex-end",
40
+ textAlign: "right"
41
+ };
36
42
  const queue = new PQueue({ concurrency: 1 });
37
43
  const UserSessionIp = memo(({ userSession, isMobile = false }) => {
38
44
  const currentState = useReactive({
@@ -49,19 +55,32 @@ const UserSessionIp = memo(({ userSession, isMobile = false }) => {
49
55
  }
50
56
  });
51
57
  }, [currentState, userSession.lastLoginIp]);
52
- return /* @__PURE__ */ jsx(Box, { ...isMobile && { display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 1 }, children: currentState.ipRegion ? /* @__PURE__ */ jsxs(Fragment, { children: [
53
- /* @__PURE__ */ jsx(Typography, { variant: "body2", children: currentState.ipRegion }),
54
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "grey", children: userSession.lastLoginIp || t("unknown") })
55
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
56
- /* @__PURE__ */ jsx(Typography, { children: userSession.lastLoginIp || t("unknown") }),
57
- currentState.loading ? /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "grey", sx: { textAlign: "center" }, children: /* @__PURE__ */ jsx(CircularProgress, { size: 12, color: "inherit" }) }) : null
58
- ] }) });
58
+ return /* @__PURE__ */ jsx(
59
+ Box,
60
+ {
61
+ ...isMobile && {
62
+ display: "flex",
63
+ alignItems: "center",
64
+ justifyContent: "flex-end",
65
+ gap: 1,
66
+ flexWrap: "wrap"
67
+ },
68
+ children: currentState.ipRegion ? /* @__PURE__ */ jsxs(Fragment, { children: [
69
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", children: currentState.ipRegion }),
70
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "grey", children: userSession.lastLoginIp || t("unknown") })
71
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
72
+ /* @__PURE__ */ jsx(Typography, { children: userSession.lastLoginIp || t("unknown") }),
73
+ currentState.loading ? /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "grey", sx: { textAlign: "center" }, children: /* @__PURE__ */ jsx(CircularProgress, { size: 12, color: "inherit" }) }) : null
74
+ ] })
75
+ }
76
+ );
59
77
  });
60
78
  export default function UserSessions({
61
79
  user,
62
80
  showAction = true,
63
81
  showUser = true,
64
- getUserSessions
82
+ getUserSessions,
83
+ showOffline = false
65
84
  }) {
66
85
  const filterParams = useReactive({
67
86
  status: "online",
@@ -70,7 +89,8 @@ export default function UserSessions({
70
89
  });
71
90
  const userSessionsCountMap = useReactive({
72
91
  online: 0,
73
- expired: 0
92
+ expired: 0,
93
+ offline: 0
74
94
  });
75
95
  const currentVisitorId = getVisitorId();
76
96
  const { locale } = useLocaleContext();
@@ -93,14 +113,19 @@ export default function UserSessions({
93
113
  list: result?.list || []
94
114
  };
95
115
  });
96
- useRequest(async () => {
97
- const result = await getUserSessions({
116
+ useMount(async () => {
117
+ const expiredResult = await getUserSessions({
98
118
  page: 1,
99
119
  pageSize: 1,
100
120
  status: "expired"
101
121
  });
102
- userSessionsCountMap.expired = result?.paging?.total || 0;
103
- return result?.paging?.total || 0;
122
+ userSessionsCountMap.expired = expiredResult?.paging?.total || 0;
123
+ const offlineResult = await getUserSessions({
124
+ page: 1,
125
+ pageSize: 1,
126
+ status: "offline"
127
+ });
128
+ userSessionsCountMap.offline = offlineResult?.paging?.total || 0;
104
129
  });
105
130
  const pageState = useRequest(getData, {
106
131
  refreshDeps: [filterParams.status, filterParams.page, filterParams.pageSize]
@@ -115,6 +140,9 @@ export default function UserSessions({
115
140
  }
116
141
  return userSessionsCountMap[status] === 0;
117
142
  }, [safeData, currentVisitorId]);
143
+ const mergeStyle = useCreation(() => {
144
+ return mergeSx({}, isMobile ? textRightStyle : {});
145
+ }, [isMobile]);
118
146
  const logout = useMemoizedFn(({ visitorId }) => {
119
147
  confirmApi.open({
120
148
  title: t("logoutThisSession"),
@@ -127,7 +155,6 @@ export default function UserSessions({
127
155
  onConfirm: async () => {
128
156
  await client.user.logout({
129
157
  visitorId,
130
- // @ts-expect-error js-sdk 1.16.43 中会包含这个参数
131
158
  includeFederated: true
132
159
  });
133
160
  pageState.refresh();
@@ -152,7 +179,6 @@ export default function UserSessions({
152
179
  await client.user.logout({
153
180
  status: filterParams.status,
154
181
  visitorId: currentVisitorId,
155
- // @ts-expect-error js-sdk 1.16.43 中会包含这个参数
156
182
  includeFederated: true
157
183
  });
158
184
  pageState.refresh();
@@ -210,8 +236,8 @@ export default function UserSessions({
210
236
  options: {
211
237
  customBodyRenderLite: (rawIndex) => {
212
238
  const x = safeData[rawIndex];
213
- const result = parseUa(x.ua);
214
- return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: [result.os?.name, result.os?.version].filter(Boolean).join("/") || t("unknown") });
239
+ const result = parseUa(x?.ua);
240
+ return /* @__PURE__ */ jsx(Box, { sx: mergeStyle, children: [result.os?.name, result.os?.version].filter(Boolean).join("/") || t("unknown") });
215
241
  }
216
242
  }
217
243
  },
@@ -221,8 +247,8 @@ export default function UserSessions({
221
247
  options: {
222
248
  customBodyRenderLite: (rawIndex) => {
223
249
  const x = safeData[rawIndex];
224
- const result = parseUa(x.ua);
225
- return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: [result.browser?.name, result.browser?.version].filter(Boolean).join("/") || t("unknown") });
250
+ const result = parseUa(x?.ua);
251
+ return /* @__PURE__ */ jsx(Box, { sx: mergeStyle, children: [result.browser?.name, result.browser?.version].filter(Boolean).join("/") || t("unknown") });
226
252
  }
227
253
  }
228
254
  },
@@ -232,7 +258,7 @@ export default function UserSessions({
232
258
  options: {
233
259
  customBodyRenderLite: (rawIndex) => {
234
260
  const x = safeData[rawIndex];
235
- return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: x.extra?.walletOS || t("unknown") });
261
+ return /* @__PURE__ */ jsx(Box, { sx: mergeStyle, children: x.extra?.walletOS || t("unknown") });
236
262
  }
237
263
  }
238
264
  },
@@ -242,7 +268,7 @@ export default function UserSessions({
242
268
  options: {
243
269
  customBodyRenderLite: (rawIndex) => {
244
270
  const x = safeData[rawIndex];
245
- return /* @__PURE__ */ jsx(Box, { sx: { minWidth: 150, maxWidth: 250, ...isMobile && { textAlign: "right" } }, children: /* @__PURE__ */ jsx(UserSessionInfo, { sessionUser: x.user, user }) });
271
+ return /* @__PURE__ */ jsx(Box, { sx: mergeSx({ minWidth: 150, maxWidth: 250 }, isMobile ? textRightStyle : {}), children: /* @__PURE__ */ jsx(UserSessionInfo, { sessionUser: x.user, user }) });
246
272
  }
247
273
  }
248
274
  },
@@ -252,17 +278,17 @@ export default function UserSessions({
252
278
  options: {
253
279
  customBodyRenderLite: (rawIndex) => {
254
280
  const x = safeData[rawIndex];
255
- return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: x.createdAt ? /* @__PURE__ */ jsx(RelativeTime, { value: x.createdAt, relativeRange: 3 * 86400 * 1e3, locale }) : t("unknown") });
281
+ return /* @__PURE__ */ jsx(Box, { sx: mergeStyle, children: x.createdAt ? /* @__PURE__ */ jsx(RelativeTime, { value: x.createdAt, relativeRange: 3 * 86400 * 1e3, locale }) : t("unknown") });
256
282
  }
257
283
  }
258
284
  },
259
- {
285
+ !showOffline && {
260
286
  label: t("updatedAt"),
261
287
  name: "updatedAt",
262
288
  options: {
263
289
  customBodyRenderLite: (rawIndex) => {
264
290
  const x = safeData[rawIndex];
265
- return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: x.status === "expired" ? t("expired") : /* @__PURE__ */ jsx(RelativeTime, { value: x.updatedAt, relativeRange: 3 * 86400 * 1e3, locale }) });
291
+ return /* @__PURE__ */ jsx(Box, { sx: mergeStyle, children: x.status === "expired" ? t("expired") : /* @__PURE__ */ jsx(RelativeTime, { value: x.updatedAt, relativeRange: 3 * 86400 * 1e3, locale }) });
266
292
  }
267
293
  }
268
294
  },
@@ -282,7 +308,7 @@ export default function UserSessions({
282
308
  options: {
283
309
  customBodyRenderLite: (rawIndex) => {
284
310
  const x = safeData[rawIndex];
285
- return /* @__PURE__ */ jsx(Box, { sx: isMobile ? { textAlign: "right" } : {}, children: /* @__PURE__ */ jsx(
311
+ return /* @__PURE__ */ jsx(Box, { sx: mergeStyle, children: /* @__PURE__ */ jsx(
286
312
  Button,
287
313
  {
288
314
  sx: {
@@ -400,7 +426,22 @@ export default function UserSessions({
400
426
  ] })
401
427
  ] })
402
428
  }
403
- )
429
+ ),
430
+ showOffline ? /* @__PURE__ */ jsx(
431
+ FormControlLabel,
432
+ {
433
+ value: "offline",
434
+ control: /* @__PURE__ */ jsx(Radio, { size: "small", sx: { lineHeight: 1, fontSize: 0 }, checked: filterParams.status === "offline" }),
435
+ label: /* @__PURE__ */ jsxs(Typography, { sx: { display: "flex", alignItems: "center" }, children: [
436
+ t("offline"),
437
+ /* @__PURE__ */ jsxs(Typography, { component: "span", sx: { ml: 0.5, color: "text.secondary" }, children: [
438
+ "(",
439
+ userSessionsCountMap.offline,
440
+ ")"
441
+ ] })
442
+ ] })
443
+ }
444
+ ) : null
404
445
  ]
405
446
  }
406
447
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/ui-react",
3
- "version": "2.13.33",
3
+ "version": "2.13.35",
4
4
  "description": "Some useful front-end web components that can be used in Blocklets.",
5
5
  "keywords": [
6
6
  "react",
@@ -34,11 +34,11 @@
34
34
  "dependencies": {
35
35
  "@abtnode/constant": "^1.16.43",
36
36
  "@abtnode/util": "^1.16.43",
37
- "@arcblock/bridge": "^2.13.33",
38
- "@arcblock/react-hooks": "^2.13.33",
39
- "@arcblock/ws": "^1.20.8",
37
+ "@arcblock/bridge": "^2.13.35",
38
+ "@arcblock/react-hooks": "^2.13.35",
39
+ "@arcblock/ws": "^1.20.10",
40
40
  "@blocklet/constant": "^1.16.43",
41
- "@blocklet/did-space-react": "^1.0.51",
41
+ "@blocklet/did-space-react": "^1.0.53",
42
42
  "@iconify-icons/logos": "^1.2.36",
43
43
  "@iconify-icons/material-symbols": "^1.2.58",
44
44
  "@iconify-icons/tabler": "^1.2.95",
@@ -94,5 +94,5 @@
94
94
  "jest": "^29.7.0",
95
95
  "unbuild": "^2.0.0"
96
96
  },
97
- "gitHead": "b835dedcd8f2e473082e4c1aee0a8cb3ed0e78ec"
97
+ "gitHead": "0637c76e4c1036802b2fef273f785cc9bf00edd0"
98
98
  }
@@ -69,7 +69,7 @@ export default function ConfigProfile({ user, onSave }: { user: User; onSave: (t
69
69
  // eslint-disable-next-line react/no-unstable-nested-components
70
70
  IconComponent={(props) => <ArrowDownwardIcon {...props} width={20} height={20} />}
71
71
  sx={{
72
- minWidth: 300,
72
+ minWidth: 200,
73
73
  '&:hover': {
74
74
  'fieldset.MuiOutlinedInput-notchedOutline': {
75
75
  borderColor: 'divider',
@@ -110,6 +110,7 @@ export default function DangerZone() {
110
110
  alignItems: 'center',
111
111
  gap: 1,
112
112
  justifyContent: 'space-between',
113
+ flexWrap: 'wrap',
113
114
  }}>
114
115
  <Box>
115
116
  <Typography
@@ -230,6 +230,7 @@ function EditableField({
230
230
  whiteSpace: 'pre-wrap',
231
231
  ...(inline ? { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } : {}),
232
232
  }}
233
+ component="div"
233
234
  onMouseEnter={handleMouseEnter}
234
235
  onMouseLeave={handleMouseLeave}>
235
236
  {renderValue ? renderValue(value) : value}
@@ -10,6 +10,7 @@ import { getWalletDid } from '@arcblock/ux/lib/SessionUser/libs/utils';
10
10
  import { translate } from '@arcblock/ux/lib/Locale/util';
11
11
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
12
12
  import { AxiosError } from 'axios';
13
+ import DID from '@arcblock/ux/lib/DID';
13
14
 
14
15
  import { translations } from '../libs/locales';
15
16
  import WebhookItem from './webhook-item';
@@ -22,11 +23,13 @@ function NotificationItem({
22
23
  description,
23
24
  value,
24
25
  onChange,
26
+ isMobile,
25
27
  }: {
26
28
  title?: string;
27
29
  description?: React.ReactNode;
28
30
  value: boolean;
29
31
  onChange: (event: ChangeEvent<HTMLInputElement>) => void;
32
+ isMobile: boolean;
30
33
  }) {
31
34
  return (
32
35
  <Switch
@@ -38,9 +41,10 @@ function NotificationItem({
38
41
  sx={{
39
42
  fontSize: 14,
40
43
  display: 'flex',
41
- flexFlow: 'wrap',
44
+ flexFlow: isMobile ? 'wrap' : 'nowrap',
42
45
  columnGap: 1,
43
46
  flex: 1,
47
+ whiteSpace: 'nowrap',
44
48
  }}>
45
49
  {title}
46
50
  {description}
@@ -53,7 +57,7 @@ function NotificationItem({
53
57
  );
54
58
  }
55
59
 
56
- export default function Notification({ user }: { user: User }) {
60
+ export default function Notification({ user, isMobile }: { user: User; isMobile: boolean }) {
57
61
  const { locale } = useLocaleContext();
58
62
  const t = useMemoizedFn((key, data = {}) => {
59
63
  return translate(translations, key, locale, 'en', data);
@@ -184,18 +188,27 @@ export default function Notification({ user }: { user: User }) {
184
188
  }}>
185
189
  <NotificationItem
186
190
  value={notifications.wallet}
191
+ isMobile={isMobile}
187
192
  title={t('walletNotification')}
188
193
  onChange={(event: ChangeEvent<HTMLInputElement>) => handleChangeSwitch('wallet', event.target.checked)}
189
194
  description={
190
195
  getWalletDid(user) && (
191
- <Typography component="span" color="text.secondary" fontSize={13}>
192
- {getWalletDid(user)}
193
- </Typography>
196
+ <DID
197
+ did={getWalletDid(user)}
198
+ showQrcode={false}
199
+ showAvatar={!isMobile}
200
+ copyable
201
+ compact
202
+ responsive
203
+ locale={locale}
204
+ startChars={isMobile ? 2 : 6}
205
+ />
194
206
  )
195
207
  }
196
208
  />
197
209
  <NotificationItem
198
210
  value={notifications.email}
211
+ isMobile={isMobile}
199
212
  onChange={(event: ChangeEvent<HTMLInputElement>) => handleChangeSwitch('email', event.target.checked)}
200
213
  title={t('emailNotification')}
201
214
  description={
@@ -207,6 +220,7 @@ export default function Notification({ user }: { user: User }) {
207
220
  }
208
221
  />
209
222
  <NotificationItem
223
+ isMobile={isMobile}
210
224
  value={notifications.push}
211
225
  title={t('pushNotification')}
212
226
  onChange={(event: ChangeEvent<HTMLInputElement>) => handleChangeSwitch('push', event.target.checked)}
@@ -20,6 +20,7 @@ export default function Settings({
20
20
  user,
21
21
  settings,
22
22
  onSave,
23
+ isMobile,
23
24
  ...rest
24
25
  }: {
25
26
  user: User;
@@ -27,6 +28,7 @@ export default function Settings({
27
28
  settings: {
28
29
  userCenterTabs: UserCenterTab[];
29
30
  };
31
+ isMobile: boolean;
30
32
  } & BoxProps) {
31
33
  const { locale } = useLocaleContext();
32
34
  const browser = useBrowser();
@@ -57,7 +59,7 @@ export default function Settings({
57
59
  {
58
60
  label: t('notificationManagement'),
59
61
  value: 'notification',
60
- content: <Notification user={user} />,
62
+ content: <Notification user={user} isMobile={isMobile} />,
61
63
  },
62
64
  {
63
65
  label: t('thirdPartyLogin.title'),
@@ -91,7 +93,7 @@ export default function Settings({
91
93
  },
92
94
  },
93
95
  ].filter(Boolean);
94
- }, [user, privacyConfigList]);
96
+ }, [user, privacyConfigList, isMobile]);
95
97
 
96
98
  // 支持 hash 锚点定位
97
99
  useEffect(() => {
@@ -286,6 +286,7 @@ export default function UserCenter({
286
286
  }
287
287
  return null;
288
288
  }}
289
+ isMobile={isMobile}
289
290
  />
290
291
  );
291
292
  }, [userState.data, userCenterTabs, privacyState.data, privacyState.runAsync]);
@@ -533,7 +534,7 @@ export default function UserCenter({
533
534
  fontWeight: 400,
534
535
  },
535
536
  },
536
- '.MuiTabs-scroller': {
537
+ '.MuiTabs-root': {
537
538
  '&:after': {
538
539
  content: '""',
539
540
  display: 'block',
@@ -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, useReactive, useRequest } from 'ahooks';
4
+ import { useCreation, useMemoizedFn, useMount, 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';
@@ -22,6 +22,7 @@ import {
22
22
  } from '@mui/material';
23
23
  import { memo, ReactElement, useEffect } from 'react';
24
24
  import { UserSession } from '@blocklet/js-sdk';
25
+ import { mergeSx } from '@arcblock/ux/lib/Util/style';
25
26
 
26
27
  import useMobile from '../../hooks/use-mobile';
27
28
  import UserSessionInfo from './user-session-info';
@@ -39,6 +40,12 @@ const parseUa = (ua: string) => {
39
40
  return result;
40
41
  };
41
42
 
43
+ const textRightStyle = {
44
+ display: 'flex',
45
+ justifyContent: 'flex-end',
46
+ textAlign: 'right',
47
+ };
48
+
42
49
  const queue = new PQueue({ concurrency: 1 });
43
50
 
44
51
  const UserSessionIp = memo(({ userSession, isMobile = false }: { userSession: any; isMobile: boolean }) => {
@@ -60,7 +67,14 @@ const UserSessionIp = memo(({ userSession, isMobile = false }: { userSession: an
60
67
  }, [currentState, userSession.lastLoginIp]);
61
68
 
62
69
  return (
63
- <Box {...(isMobile && { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 1 })}>
70
+ <Box
71
+ {...(isMobile && {
72
+ display: 'flex',
73
+ alignItems: 'center',
74
+ justifyContent: 'flex-end',
75
+ gap: 1,
76
+ flexWrap: 'wrap',
77
+ })}>
64
78
  {currentState.ipRegion ? (
65
79
  <>
66
80
  <Typography variant="body2">{currentState.ipRegion}</Typography>
@@ -87,13 +101,19 @@ export default function UserSessions({
87
101
  showAction = true,
88
102
  showUser = true,
89
103
  getUserSessions,
104
+ showOffline = false,
90
105
  }: {
91
106
  readonly user: User & {
92
107
  userSessions?: any[];
93
108
  };
94
109
  readonly showAction?: boolean;
95
110
  readonly showUser?: boolean;
96
- readonly getUserSessions: (params: { page: number; pageSize: number; status?: 'online' | 'expired' }) => Promise<{
111
+ readonly showOffline?: boolean;
112
+ readonly getUserSessions: (params: {
113
+ page: number;
114
+ pageSize: number;
115
+ status?: 'online' | 'expired' | 'offline';
116
+ }) => Promise<{
97
117
  paging: {
98
118
  total: number;
99
119
  };
@@ -108,6 +128,7 @@ export default function UserSessions({
108
128
  const userSessionsCountMap = useReactive({
109
129
  online: 0,
110
130
  expired: 0,
131
+ offline: 0,
111
132
  });
112
133
  const currentVisitorId = getVisitorId();
113
134
  const { locale } = useLocaleContext();
@@ -121,7 +142,7 @@ export default function UserSessions({
121
142
  const result = await getUserSessions({
122
143
  page: filterParams.page,
123
144
  pageSize: filterParams.pageSize,
124
- status: filterParams.status as 'online' | 'expired',
145
+ status: filterParams.status as 'online' | 'expired' | 'offline',
125
146
  });
126
147
 
127
148
  const total = result?.paging?.total || 0;
@@ -133,14 +154,20 @@ export default function UserSessions({
133
154
  };
134
155
  });
135
156
 
136
- useRequest(async () => {
137
- const result = await getUserSessions({
157
+ useMount(async () => {
158
+ const expiredResult = await getUserSessions({
138
159
  page: 1,
139
160
  pageSize: 1,
140
161
  status: 'expired',
141
162
  });
142
- userSessionsCountMap.expired = result?.paging?.total || 0;
143
- return result?.paging?.total || 0;
163
+ userSessionsCountMap.expired = expiredResult?.paging?.total || 0;
164
+
165
+ const offlineResult = await getUserSessions({
166
+ page: 1,
167
+ pageSize: 1,
168
+ status: 'offline',
169
+ });
170
+ userSessionsCountMap.offline = offlineResult?.paging?.total || 0;
144
171
  });
145
172
 
146
173
  const pageState = useRequest(getData, {
@@ -160,6 +187,10 @@ export default function UserSessions({
160
187
  return userSessionsCountMap[status] === 0;
161
188
  }, [safeData, currentVisitorId]);
162
189
 
190
+ const mergeStyle = useCreation(() => {
191
+ return mergeSx({}, isMobile ? textRightStyle : {});
192
+ }, [isMobile]);
193
+
163
194
  const logout = useMemoizedFn(({ visitorId }) => {
164
195
  confirmApi.open({
165
196
  title: t('logoutThisSession'),
@@ -172,7 +203,6 @@ export default function UserSessions({
172
203
  onConfirm: async () => {
173
204
  await client.user.logout({
174
205
  visitorId,
175
- // @ts-expect-error js-sdk 1.16.43 中会包含这个参数
176
206
  includeFederated: true,
177
207
  });
178
208
  pageState.refresh();
@@ -197,7 +227,6 @@ export default function UserSessions({
197
227
  await client.user.logout({
198
228
  status: filterParams.status,
199
229
  visitorId: currentVisitorId as string,
200
- // @ts-expect-error js-sdk 1.16.43 中会包含这个参数
201
230
  includeFederated: true,
202
231
  });
203
232
  pageState.refresh();
@@ -249,11 +278,9 @@ export default function UserSessions({
249
278
  options: {
250
279
  customBodyRenderLite: (rawIndex: number) => {
251
280
  const x = safeData[rawIndex];
252
- const result = parseUa(x.ua);
281
+ const result = parseUa(x?.ua);
253
282
  return (
254
- <Box sx={isMobile ? { textAlign: 'right' } : {}}>
255
- {[result.os?.name, result.os?.version].filter(Boolean).join('/') || t('unknown')}
256
- </Box>
283
+ <Box sx={mergeStyle}>{[result.os?.name, result.os?.version].filter(Boolean).join('/') || t('unknown')}</Box>
257
284
  );
258
285
  },
259
286
  },
@@ -264,9 +291,9 @@ export default function UserSessions({
264
291
  options: {
265
292
  customBodyRenderLite: (rawIndex: number) => {
266
293
  const x = safeData[rawIndex];
267
- const result = parseUa(x.ua);
294
+ const result = parseUa(x?.ua);
268
295
  return (
269
- <Box sx={isMobile ? { textAlign: 'right' } : {}}>
296
+ <Box sx={mergeStyle}>
270
297
  {[result.browser?.name, result.browser?.version].filter(Boolean).join('/') || t('unknown')}
271
298
  </Box>
272
299
  );
@@ -279,7 +306,7 @@ export default function UserSessions({
279
306
  options: {
280
307
  customBodyRenderLite: (rawIndex: number) => {
281
308
  const x = safeData[rawIndex];
282
- return <Box sx={isMobile ? { textAlign: 'right' } : {}}>{x.extra?.walletOS || t('unknown')}</Box>;
309
+ return <Box sx={mergeStyle}>{x.extra?.walletOS || t('unknown')}</Box>;
283
310
  },
284
311
  },
285
312
  },
@@ -290,7 +317,7 @@ export default function UserSessions({
290
317
  customBodyRenderLite: (rawIndex: number) => {
291
318
  const x = safeData[rawIndex];
292
319
  return (
293
- <Box sx={{ minWidth: 150, maxWidth: 250, ...(isMobile && { textAlign: 'right' }) }}>
320
+ <Box sx={mergeSx({ minWidth: 150, maxWidth: 250 }, isMobile ? textRightStyle : {})}>
294
321
  <UserSessionInfo sessionUser={x.user} user={user} />
295
322
  </Box>
296
323
  );
@@ -304,7 +331,7 @@ export default function UserSessions({
304
331
  customBodyRenderLite: (rawIndex: number) => {
305
332
  const x = safeData[rawIndex];
306
333
  return (
307
- <Box sx={isMobile ? { textAlign: 'right' } : {}}>
334
+ <Box sx={mergeStyle}>
308
335
  {x.createdAt ? (
309
336
  <RelativeTime value={x.createdAt} relativeRange={3 * 86400 * 1000} locale={locale} />
310
337
  ) : (
@@ -315,14 +342,14 @@ export default function UserSessions({
315
342
  },
316
343
  },
317
344
  },
318
- {
345
+ !showOffline && {
319
346
  label: t('updatedAt'),
320
347
  name: 'updatedAt',
321
348
  options: {
322
349
  customBodyRenderLite: (rawIndex: number) => {
323
350
  const x = safeData[rawIndex];
324
351
  return (
325
- <Box sx={isMobile ? { textAlign: 'right' } : {}}>
352
+ <Box sx={mergeStyle}>
326
353
  {x.status === 'expired' ? (
327
354
  t('expired')
328
355
  ) : (
@@ -350,7 +377,7 @@ export default function UserSessions({
350
377
  customBodyRenderLite: (rawIndex: number) => {
351
378
  const x = safeData[rawIndex];
352
379
  return (
353
- <Box sx={isMobile ? { textAlign: 'right' } : {}}>
380
+ <Box sx={mergeStyle}>
354
381
  <Button
355
382
  sx={{
356
383
  whiteSpace: 'nowrap',
@@ -468,6 +495,22 @@ export default function UserSessions({
468
495
  </Typography>
469
496
  }
470
497
  />
498
+ {showOffline ? (
499
+ <FormControlLabel
500
+ value="offline"
501
+ control={
502
+ <Radio size="small" sx={{ lineHeight: 1, fontSize: 0 }} checked={filterParams.status === 'offline'} />
503
+ }
504
+ label={
505
+ <Typography sx={{ display: 'flex', alignItems: 'center' }}>
506
+ {t('offline')}
507
+ <Typography component="span" sx={{ ml: 0.5, color: 'text.secondary' }}>
508
+ ({userSessionsCountMap.offline})
509
+ </Typography>
510
+ </Typography>
511
+ }
512
+ />
513
+ ) : null}
471
514
  </RadioGroup>
472
515
  }
473
516
  onChange={(state) => {