@blocklet/ui-react 2.12.64 → 2.12.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Footer/index.js +6 -14
- package/lib/Footer/links.js +5 -5
- package/lib/Footer/social-media.js +2 -2
- package/lib/Header/index.js +8 -8
- package/lib/UserCenter/components/editable-field.d.ts +2 -1
- package/lib/UserCenter/components/editable-field.js +3 -2
- package/lib/UserCenter/components/settings.js +11 -1
- package/lib/UserCenter/components/user-info/metadata.js +6 -4
- package/lib/UserSessions/components/user-sessions.d.ts +12 -1
- package/lib/UserSessions/components/user-sessions.js +139 -51
- package/lib/UserSessions/libs/locales.d.ts +2 -0
- package/lib/UserSessions/libs/locales.js +8 -6
- package/lib/common/notification-addon.js +0 -2
- package/package.json +5 -5
- package/src/Footer/index.jsx +8 -13
- package/src/Footer/links.jsx +5 -5
- package/src/Footer/social-media.jsx +2 -2
- package/src/Header/index.tsx +9 -9
- package/src/UserCenter/components/editable-field.tsx +3 -1
- package/src/UserCenter/components/settings.tsx +14 -1
- package/src/UserCenter/components/user-info/metadata.tsx +16 -12
- package/src/UserSessions/components/user-sessions.tsx +130 -43
- package/src/UserSessions/libs/locales.ts +8 -6
- package/src/common/notification-addon.jsx +0 -2
- package/lib/UserCenter/components/user-info/clock.d.ts +0 -3
- package/lib/UserCenter/components/user-info/clock.js +0 -50
- package/lib/hooks/use-clock.d.ts +0 -9
- package/lib/hooks/use-clock.js +0 -49
- package/src/UserCenter/components/user-info/clock.tsx +0 -43
- package/src/hooks/use-clock.tsx +0 -61
package/lib/Footer/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
|
-
import { styled } from "@arcblock/ux/lib/Theme";
|
|
4
|
+
import { styled, useTheme, deepmerge, ThemeProvider } from "@arcblock/ux/lib/Theme";
|
|
5
5
|
import { withErrorBoundary } from "react-error-boundary";
|
|
6
6
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
7
7
|
import { ErrorFallback } from "@arcblock/ux/lib/ErrorBoundary";
|
|
8
8
|
import { temp as colors } from "@arcblock/ux/lib/Colors";
|
|
9
9
|
import omit from "lodash/omit";
|
|
10
|
-
import OverridableThemeProvider from "../common/overridable-theme-provider.js";
|
|
11
10
|
import InternalFooter from "./internal-footer.js";
|
|
12
11
|
import { mapRecursive } from "../utils.js";
|
|
13
12
|
import { formatBlockletInfo, getLocalizedNavigation } from "../blocklets.js";
|
|
@@ -15,6 +14,7 @@ import { BlockletMetaProps } from "../types.js";
|
|
|
15
14
|
import withHideWhenEmbed from "../libs/with-hide-when-embed.js";
|
|
16
15
|
function Footer({ meta, theme: themeOverrides, ...rest }) {
|
|
17
16
|
const { locale } = useLocaleContext() || {};
|
|
17
|
+
const parentTheme = useTheme();
|
|
18
18
|
const formattedBlocklet = useMemo(() => {
|
|
19
19
|
const blocklet = Object.assign({}, window.blocklet, meta);
|
|
20
20
|
try {
|
|
@@ -24,10 +24,12 @@ function Footer({ meta, theme: themeOverrides, ...rest }) {
|
|
|
24
24
|
return blocklet;
|
|
25
25
|
}
|
|
26
26
|
}, [meta]);
|
|
27
|
+
const mergeTheme = useMemo(() => deepmerge(parentTheme, themeOverrides), [parentTheme, themeOverrides]);
|
|
27
28
|
if (!formattedBlocklet.appName) {
|
|
28
29
|
return null;
|
|
29
30
|
}
|
|
30
|
-
const { appLogo, appLogoRect, appName, appDescription, description,
|
|
31
|
+
const { appLogo, appLogoRect, appName, appDescription, description, copyright } = formattedBlocklet;
|
|
32
|
+
const $bgColor = mergeTheme.palette.background.default;
|
|
31
33
|
const localized = {
|
|
32
34
|
footerNav: getLocalizedNavigation(formattedBlocklet?.navigation?.footer, locale) || [],
|
|
33
35
|
socialMedia: getLocalizedNavigation(formattedBlocklet?.navigation?.social, locale) || [],
|
|
@@ -52,15 +54,7 @@ function Footer({ meta, theme: themeOverrides, ...rest }) {
|
|
|
52
54
|
socialMedia: localized.socialMedia,
|
|
53
55
|
links: localized.links.map((item) => ({ ...item, label: item.title }))
|
|
54
56
|
};
|
|
55
|
-
return /* @__PURE__ */ jsx(
|
|
56
|
-
StyledInternalFooter,
|
|
57
|
-
{
|
|
58
|
-
...props,
|
|
59
|
-
...omit(rest, ["bordered"]),
|
|
60
|
-
$bordered: rest?.bordered,
|
|
61
|
-
$bgcolor: theme?.background?.footer
|
|
62
|
-
}
|
|
63
|
-
) });
|
|
57
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { theme: mergeTheme, children: /* @__PURE__ */ jsx(StyledInternalFooter, { ...props, ...omit(rest, ["bordered"]), $bordered: rest?.bordered, $bgcolor: $bgColor }) });
|
|
64
58
|
}
|
|
65
59
|
Footer.propTypes = {
|
|
66
60
|
meta: BlockletMetaProps,
|
|
@@ -75,8 +69,6 @@ const StyledInternalFooter = styled(InternalFooter)`
|
|
|
75
69
|
${({ $bordered }) => `border-top: 1px solid ${$bordered ? colors.strokeSep : "#eee"};`}
|
|
76
70
|
color: ${(props) => props.theme.palette.grey[600]};
|
|
77
71
|
${({ $bgcolor }) => $bgcolor && `background-color: ${$bgcolor};`}
|
|
78
|
-
font-family: Inter, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
|
|
79
|
-
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
80
72
|
`;
|
|
81
73
|
export default withErrorBoundary(withHideWhenEmbed(Footer), {
|
|
82
74
|
FallbackComponent: ErrorFallback
|
package/lib/Footer/links.js
CHANGED
|
@@ -128,7 +128,7 @@ Links.defaultProps = {
|
|
|
128
128
|
};
|
|
129
129
|
const Root = styled("div")`
|
|
130
130
|
overflow: hidden;
|
|
131
|
-
color:
|
|
131
|
+
color: ${({ theme }) => theme.palette.grey[500]};
|
|
132
132
|
.footer-links-inner {
|
|
133
133
|
display: flex;
|
|
134
134
|
justify-content: space-between;
|
|
@@ -159,8 +159,8 @@ const Root = styled("div")`
|
|
|
159
159
|
font-size: 14px;
|
|
160
160
|
&--new::after {
|
|
161
161
|
content: 'New';
|
|
162
|
-
color:
|
|
163
|
-
background-color:
|
|
162
|
+
color: ${({ theme }) => theme.palette.info.main};
|
|
163
|
+
background-color: ${({ theme }) => theme.palette.info.light};
|
|
164
164
|
padding: 1px 8px;
|
|
165
165
|
border-radius: 10px/50%;
|
|
166
166
|
margin-left: 8px;
|
|
@@ -170,7 +170,7 @@ const Root = styled("div")`
|
|
|
170
170
|
.footer-links-group {
|
|
171
171
|
> .footer-links-item {
|
|
172
172
|
font-weight: 600;
|
|
173
|
-
color:
|
|
173
|
+
color: ${({ theme }) => theme.palette.text.primary};
|
|
174
174
|
}
|
|
175
175
|
.footer-links-sub {
|
|
176
176
|
margin-top: 8px;
|
|
@@ -184,7 +184,7 @@ const Root = styled("div")`
|
|
|
184
184
|
text-decoration: none;
|
|
185
185
|
transition: color 0.2s ease-in-out;
|
|
186
186
|
&:hover {
|
|
187
|
-
color:
|
|
187
|
+
color: ${({ theme }) => theme.palette.text.primary};
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
/* columns 布局 */
|
|
@@ -48,11 +48,11 @@ const Root = styled("div")`
|
|
|
48
48
|
justify-content: center;
|
|
49
49
|
gap: 20px;
|
|
50
50
|
a {
|
|
51
|
-
color: ${(props) => props.theme.palette.grey[
|
|
51
|
+
color: ${(props) => props.theme.palette.grey[500]};
|
|
52
52
|
text-decoration: none;
|
|
53
53
|
transition: color 0.2s ease-in-out;
|
|
54
54
|
&:hover {
|
|
55
|
-
color:
|
|
55
|
+
color: ${({ theme }) => theme.palette.text.primary};
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
${(props) => props.theme.breakpoints.down("md")} {
|
package/lib/Header/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useMemo } from "react";
|
|
|
3
3
|
import { useMemoizedFn } from "ahooks";
|
|
4
4
|
import { withErrorBoundary } from "react-error-boundary";
|
|
5
5
|
import { ErrorFallback } from "@arcblock/ux/lib/ErrorBoundary";
|
|
6
|
-
import { styled } from "@arcblock/ux/lib/Theme";
|
|
6
|
+
import { styled, useTheme, deepmerge, ThemeProvider } from "@arcblock/ux/lib/Theme";
|
|
7
7
|
import { ResponsiveHeader } from "@arcblock/ux/lib/Header";
|
|
8
8
|
import NavMenu, { Products } from "@arcblock/ux/lib/NavMenu";
|
|
9
9
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
@@ -13,7 +13,6 @@ import omit from "lodash/omit";
|
|
|
13
13
|
import isFinite from "lodash/isFinite";
|
|
14
14
|
import clsx from "clsx";
|
|
15
15
|
import Icon from "../Icon/index.js";
|
|
16
|
-
import OverridableThemeProvider from "../common/overridable-theme-provider.js";
|
|
17
16
|
import { mapRecursive, flatRecursive, matchPaths } from "../utils.js";
|
|
18
17
|
import { publicPath, formatBlockletInfo, getLocalizedNavigation } from "../blocklets.js";
|
|
19
18
|
import HeaderAddons from "../common/header-addons.js";
|
|
@@ -76,6 +75,7 @@ function Header({
|
|
|
76
75
|
...rest
|
|
77
76
|
}) {
|
|
78
77
|
useWalletHiddenTopbar();
|
|
78
|
+
const parentTheme = useTheme();
|
|
79
79
|
const { locale } = useLocaleContext() || {};
|
|
80
80
|
const t = useMemoizedFn((key, data = {}) => {
|
|
81
81
|
return translate(translations, key, locale, "en", data);
|
|
@@ -90,10 +90,11 @@ function Header({
|
|
|
90
90
|
}
|
|
91
91
|
}, [meta]);
|
|
92
92
|
const isMobileDevice = useMobile();
|
|
93
|
+
const mergeTheme = useMemo(() => deepmerge(parentTheme, themeOverrides), [parentTheme, themeOverrides]);
|
|
93
94
|
if (!formattedBlocklet.appName) {
|
|
94
95
|
return null;
|
|
95
96
|
}
|
|
96
|
-
const { appLogo, appLogoRect
|
|
97
|
+
const { appLogo, appLogoRect } = formattedBlocklet;
|
|
97
98
|
const navigation = getLocalizedNavigation(formattedBlocklet?.navigation?.header, locale);
|
|
98
99
|
const parsedNavigation = parseNavigation(navigation);
|
|
99
100
|
const { navItems, activeId } = parsedNavigation;
|
|
@@ -118,7 +119,7 @@ function Header({
|
|
|
118
119
|
}
|
|
119
120
|
)
|
|
120
121
|
);
|
|
121
|
-
return /* @__PURE__ */ jsx(
|
|
122
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { theme: mergeTheme, children: /* @__PURE__ */ jsx(
|
|
122
123
|
StyledUxHeader,
|
|
123
124
|
{
|
|
124
125
|
homeLink,
|
|
@@ -126,7 +127,7 @@ function Header({
|
|
|
126
127
|
addons: headerAddons,
|
|
127
128
|
...omit(rest, ["bordered"]),
|
|
128
129
|
$bordered: rest?.bordered,
|
|
129
|
-
$bgcolor:
|
|
130
|
+
$bgcolor: mergeTheme.palette.background.default,
|
|
130
131
|
className: clsx("blocklet__header", rest.className),
|
|
131
132
|
children: hideNavMenu || !navItems?.length ? null : ({ isMobile }) => (
|
|
132
133
|
// @ts-ignore
|
|
@@ -138,7 +139,8 @@ function Header({
|
|
|
138
139
|
items: navItems,
|
|
139
140
|
className: "header-nav",
|
|
140
141
|
bgColor: "transparent",
|
|
141
|
-
textColor:
|
|
142
|
+
textColor: mergeTheme.palette.grey[500],
|
|
143
|
+
activeTextColor: mergeTheme.palette.text.primary
|
|
142
144
|
}
|
|
143
145
|
)
|
|
144
146
|
)
|
|
@@ -147,8 +149,6 @@ function Header({
|
|
|
147
149
|
}
|
|
148
150
|
const StyledUxHeader = styled(ResponsiveHeader)`
|
|
149
151
|
${({ $bgcolor }) => `background-color: ${$bgcolor || "#fff"};`}
|
|
150
|
-
font-family: Inter, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
|
|
151
|
-
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
152
152
|
.header-logo {
|
|
153
153
|
min-width: 44px;
|
|
154
154
|
}
|
|
@@ -19,6 +19,7 @@ interface EditableFieldProps {
|
|
|
19
19
|
verified?: boolean;
|
|
20
20
|
errorMsg?: string;
|
|
21
21
|
canEdit?: boolean;
|
|
22
|
+
hidePreview?: boolean;
|
|
22
23
|
renderValue?: (value: string) => React.ReactNode;
|
|
23
24
|
}
|
|
24
25
|
export declare const commonInputStyle: {
|
|
@@ -47,5 +48,5 @@ export declare const inputFieldStyle: {
|
|
|
47
48
|
borderColor: string;
|
|
48
49
|
};
|
|
49
50
|
};
|
|
50
|
-
declare function EditableField({ value, onChange, onValueValidate, errorMsg, editable, component, placeholder, rows, maxLength, icon, label, children, tooltip, inline, style, verified, canEdit, renderValue, disabled, }: EditableFieldProps): JSX.Element | null;
|
|
51
|
+
declare function EditableField({ value, onChange, onValueValidate, errorMsg, editable, component, placeholder, rows, maxLength, icon, label, children, tooltip, inline, style, verified, canEdit, renderValue, disabled, hidePreview, }: EditableFieldProps): JSX.Element | null;
|
|
51
52
|
export default EditableField;
|
|
@@ -55,7 +55,8 @@ function EditableField({
|
|
|
55
55
|
verified = false,
|
|
56
56
|
canEdit = true,
|
|
57
57
|
renderValue,
|
|
58
|
-
disabled = false
|
|
58
|
+
disabled = false,
|
|
59
|
+
hidePreview = false
|
|
59
60
|
}) {
|
|
60
61
|
const { locale } = useLocaleContext();
|
|
61
62
|
const t = useMemoizedFn((key, data = {}) => {
|
|
@@ -145,7 +146,7 @@ function EditableField({
|
|
|
145
146
|
return null;
|
|
146
147
|
}
|
|
147
148
|
if (!editable) {
|
|
148
|
-
return value ? /* @__PURE__ */ jsx(
|
|
149
|
+
return value && !hidePreview ? /* @__PURE__ */ jsx(
|
|
149
150
|
Tooltip,
|
|
150
151
|
{
|
|
151
152
|
open: Boolean(mousePosition),
|
|
@@ -13,6 +13,7 @@ import { UserSessions } from "../../UserSessions/index.js";
|
|
|
13
13
|
import ThirdPartyLogin from "./third-party-login/index.js";
|
|
14
14
|
import ConfigProfile from "./config-profile.js";
|
|
15
15
|
import DangerZone from "./danger-zone.js";
|
|
16
|
+
import { client } from "../../libs/client.js";
|
|
16
17
|
export default function Settings({
|
|
17
18
|
user,
|
|
18
19
|
settings,
|
|
@@ -59,7 +60,16 @@ export default function Settings({
|
|
|
59
60
|
{
|
|
60
61
|
label: t("sessionManagement"),
|
|
61
62
|
value: "session",
|
|
62
|
-
content: /* @__PURE__ */ jsx(
|
|
63
|
+
content: /* @__PURE__ */ jsx(
|
|
64
|
+
UserSessions,
|
|
65
|
+
{
|
|
66
|
+
user,
|
|
67
|
+
showUser: false,
|
|
68
|
+
getUserSessions: (params) => {
|
|
69
|
+
return client.userSession.getMyLoginSessions({}, params);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
)
|
|
63
73
|
},
|
|
64
74
|
{
|
|
65
75
|
label: t("dangerZone.title"),
|
|
@@ -22,12 +22,12 @@ import isEmail from "validator/lib/isEmail";
|
|
|
22
22
|
import isPostalCode from "validator/lib/isPostalCode";
|
|
23
23
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
24
24
|
import { useBrowser } from "@arcblock/react-hooks";
|
|
25
|
+
import Clock from "@arcblock/ux/lib/UserCard/Content/clock";
|
|
25
26
|
import { translations } from "../../libs/locales.js";
|
|
26
27
|
import EditableField, { commonInputStyle, inputFieldStyle } from "../editable-field.js";
|
|
27
28
|
import { LinkPreviewInput } from "./link-preview-input.js";
|
|
28
29
|
import { currentTimezone, defaultButtonStyle, primaryButtonStyle } from "./utils.js";
|
|
29
30
|
import { TimezoneSelect } from "./timezone-select.js";
|
|
30
|
-
import Clock from "./clock.js";
|
|
31
31
|
import AddressEditor from "./address.js";
|
|
32
32
|
const LocationIcon = lazy(() => import("@arcblock/icons/lib/Location"));
|
|
33
33
|
const TimezoneIcon = lazy(() => import("@arcblock/icons/lib/Timezone"));
|
|
@@ -363,6 +363,7 @@ export default function UserMetadataComponent({
|
|
|
363
363
|
{
|
|
364
364
|
value: metadata.email ?? user?.email ?? "",
|
|
365
365
|
editable: editing,
|
|
366
|
+
hidePreview: !isMyself,
|
|
366
367
|
disabled: user?.sourceProvider === LOGIN_PROVIDER.EMAIL,
|
|
367
368
|
canEdit: !emailVerified,
|
|
368
369
|
verified: emailVerified,
|
|
@@ -384,7 +385,7 @@ export default function UserMetadataComponent({
|
|
|
384
385
|
] }),
|
|
385
386
|
onChange: (value) => onChange(value, "email"),
|
|
386
387
|
errorMsg: validateMsg.email,
|
|
387
|
-
renderValue: (value) => /* @__PURE__ */ jsx(
|
|
388
|
+
renderValue: (value) => isMyself ? /* @__PURE__ */ jsx(
|
|
388
389
|
"a",
|
|
389
390
|
{
|
|
390
391
|
href: `mailto:${value}`,
|
|
@@ -394,7 +395,7 @@ export default function UserMetadataComponent({
|
|
|
394
395
|
},
|
|
395
396
|
children: value
|
|
396
397
|
}
|
|
397
|
-
),
|
|
398
|
+
) : null,
|
|
398
399
|
onValueValidate: (value) => {
|
|
399
400
|
let msg = "";
|
|
400
401
|
if (!!value && !isEmail(value)) {
|
|
@@ -409,6 +410,7 @@ export default function UserMetadataComponent({
|
|
|
409
410
|
{
|
|
410
411
|
value: phoneValue.phone,
|
|
411
412
|
editable: editing,
|
|
413
|
+
hidePreview: !isMyself,
|
|
412
414
|
canEdit: !phoneVerified,
|
|
413
415
|
verified: phoneVerified,
|
|
414
416
|
placeholder: "Phone",
|
|
@@ -416,7 +418,7 @@ export default function UserMetadataComponent({
|
|
|
416
418
|
onChange: (value) => onChange(value, "phone"),
|
|
417
419
|
label: t("profile.phone"),
|
|
418
420
|
renderValue: () => {
|
|
419
|
-
return /* @__PURE__ */ jsx(PhoneInput, { value: phoneValue, preview: true });
|
|
421
|
+
return isMyself ? /* @__PURE__ */ jsx(PhoneInput, { value: phoneValue, preview: true }) : null;
|
|
420
422
|
},
|
|
421
423
|
children: /* @__PURE__ */ jsx(
|
|
422
424
|
PhoneInput,
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
import { UserSession } from '@blocklet/js-sdk';
|
|
1
2
|
import { User } from '../../@types';
|
|
2
|
-
export default function UserSessions({ user, showAction, showUser, }: {
|
|
3
|
+
export default function UserSessions({ user, showAction, showUser, getUserSessions, }: {
|
|
3
4
|
readonly user: User & {
|
|
4
5
|
userSessions?: any[];
|
|
5
6
|
};
|
|
6
7
|
readonly showAction?: boolean;
|
|
7
8
|
readonly showUser?: boolean;
|
|
9
|
+
readonly getUserSessions: (params: {
|
|
10
|
+
page: number;
|
|
11
|
+
pageSize: number;
|
|
12
|
+
status: string;
|
|
13
|
+
}) => Promise<{
|
|
14
|
+
paging: {
|
|
15
|
+
total: number;
|
|
16
|
+
};
|
|
17
|
+
list: UserSession[];
|
|
18
|
+
}>;
|
|
8
19
|
}): import("react").JSX.Element;
|
|
@@ -4,16 +4,23 @@ 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";
|
|
7
|
-
import sortBy from "lodash/sortBy";
|
|
8
7
|
import UAParser from "ua-parser-js";
|
|
9
8
|
import { getVisitorId } from "@arcblock/ux/lib/Util";
|
|
10
9
|
import { useConfirm } from "@arcblock/ux/lib/Dialog";
|
|
11
10
|
import { temp as colors } from "@arcblock/ux/lib/Colors";
|
|
12
|
-
import pAll from "p-all";
|
|
13
11
|
import PQueue from "p-queue";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
import {
|
|
13
|
+
Box,
|
|
14
|
+
Button,
|
|
15
|
+
CircularProgress,
|
|
16
|
+
FormControlLabel,
|
|
17
|
+
Radio,
|
|
18
|
+
RadioGroup,
|
|
19
|
+
Tooltip,
|
|
20
|
+
Typography,
|
|
21
|
+
useMediaQuery
|
|
22
|
+
} from "@mui/material";
|
|
23
|
+
import { memo, useEffect } from "react";
|
|
17
24
|
import useMobile from "../../hooks/use-mobile.js";
|
|
18
25
|
import UserSessionInfo from "./user-session-info.js";
|
|
19
26
|
import { client } from "../../libs/client.js";
|
|
@@ -54,9 +61,18 @@ const UserSessionIp = memo(({ userSession, isMobile = false }) => {
|
|
|
54
61
|
export default function UserSessions({
|
|
55
62
|
user,
|
|
56
63
|
showAction = true,
|
|
57
|
-
showUser = true
|
|
64
|
+
showUser = true,
|
|
65
|
+
getUserSessions
|
|
58
66
|
}) {
|
|
59
|
-
const
|
|
67
|
+
const filterParams = useReactive({
|
|
68
|
+
status: "online",
|
|
69
|
+
page: 1,
|
|
70
|
+
pageSize: 10
|
|
71
|
+
});
|
|
72
|
+
const userSessionsCountMap = useReactive({
|
|
73
|
+
online: 0,
|
|
74
|
+
expired: 0
|
|
75
|
+
});
|
|
60
76
|
const currentVisitorId = getVisitorId();
|
|
61
77
|
const { locale } = useLocaleContext();
|
|
62
78
|
const isMobile = useMobile({ key: "md" });
|
|
@@ -66,27 +82,40 @@ export default function UserSessions({
|
|
|
66
82
|
return translate(translations, key, locale, "en", data);
|
|
67
83
|
});
|
|
68
84
|
const getData = useMemoizedFn(async () => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
} catch (e) {
|
|
75
|
-
console.warn("Failed to convert ip to region");
|
|
76
|
-
console.error(e);
|
|
77
|
-
}
|
|
78
|
-
const now = (/* @__PURE__ */ new Date()).getTime();
|
|
79
|
-
return sortBy(data, (x) => {
|
|
80
|
-
if (x.visitorId === currentVisitorId) {
|
|
81
|
-
return -1;
|
|
82
|
-
}
|
|
83
|
-
return now - new Date(x.updatedAt).getTime();
|
|
85
|
+
const result = await getUserSessions({
|
|
86
|
+
page: filterParams.page,
|
|
87
|
+
pageSize: filterParams.pageSize,
|
|
88
|
+
status: filterParams.status
|
|
84
89
|
});
|
|
90
|
+
const total = result?.paging?.total || 0;
|
|
91
|
+
userSessionsCountMap[filterParams.status] = total;
|
|
92
|
+
return {
|
|
93
|
+
total,
|
|
94
|
+
list: result?.list || []
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
useRequest(async () => {
|
|
98
|
+
const result = await getUserSessions({
|
|
99
|
+
page: 1,
|
|
100
|
+
pageSize: 1,
|
|
101
|
+
status: "expired"
|
|
102
|
+
});
|
|
103
|
+
userSessionsCountMap.expired = result?.paging?.total || 0;
|
|
104
|
+
return result?.paging?.total || 0;
|
|
105
|
+
});
|
|
106
|
+
const pageState = useRequest(getData, {
|
|
107
|
+
refreshDeps: [filterParams.status, filterParams.page, filterParams.pageSize]
|
|
85
108
|
});
|
|
86
|
-
const pageState = useRequest(getData);
|
|
87
109
|
const safeData = useCreation(() => {
|
|
88
|
-
return pageState.data || [];
|
|
89
|
-
}, [pageState.data]);
|
|
110
|
+
return pageState.data?.list || [];
|
|
111
|
+
}, [pageState.data?.list]);
|
|
112
|
+
const disableLogout = useCreation(() => {
|
|
113
|
+
const status = filterParams.status;
|
|
114
|
+
if (status === "online") {
|
|
115
|
+
return userSessionsCountMap[status] <= 1;
|
|
116
|
+
}
|
|
117
|
+
return userSessionsCountMap[status] === 0;
|
|
118
|
+
}, [safeData, currentVisitorId]);
|
|
90
119
|
const logout = useMemoizedFn(({ visitorId }) => {
|
|
91
120
|
confirmApi.open({
|
|
92
121
|
title: t("logoutThisSession"),
|
|
@@ -103,26 +132,24 @@ export default function UserSessions({
|
|
|
103
132
|
}
|
|
104
133
|
});
|
|
105
134
|
});
|
|
106
|
-
const otherUserSessions = useCreation(() => {
|
|
107
|
-
const list = safeData.filter((x) => x.visitorId !== currentVisitorId);
|
|
108
|
-
return list;
|
|
109
|
-
}, [safeData]);
|
|
110
135
|
const logoutAll = useMemoizedFn(() => {
|
|
111
136
|
confirmApi.open({
|
|
112
|
-
title: t("logoutAllSession"
|
|
113
|
-
|
|
137
|
+
title: t("logoutAllSession", {
|
|
138
|
+
type: t(filterParams.status)
|
|
139
|
+
}),
|
|
140
|
+
content: t("logoutAllSessionConfirm", {
|
|
141
|
+
type: t(filterParams.status)
|
|
142
|
+
}),
|
|
114
143
|
confirmButtonText: t("confirm"),
|
|
115
144
|
confirmButtonProps: {
|
|
116
145
|
color: "error"
|
|
117
146
|
},
|
|
118
147
|
cancelButtonText: t("cancel"),
|
|
119
148
|
onConfirm: async () => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
concurrency: 3,
|
|
125
|
-
stopOnError: false
|
|
149
|
+
await client.user.logout({
|
|
150
|
+
// @ts-expect-error js-sdk 发了新版后,会有这个类型定义
|
|
151
|
+
status: filterParams.status,
|
|
152
|
+
visitorId: currentVisitorId
|
|
126
153
|
});
|
|
127
154
|
pageState.refresh();
|
|
128
155
|
confirmApi.close();
|
|
@@ -132,18 +159,29 @@ export default function UserSessions({
|
|
|
132
159
|
const customButtons = [];
|
|
133
160
|
if (showAction) {
|
|
134
161
|
customButtons.push(
|
|
135
|
-
/* @__PURE__ */ jsx(
|
|
136
|
-
|
|
162
|
+
/* @__PURE__ */ jsx(
|
|
163
|
+
Tooltip,
|
|
137
164
|
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
title: t("logoutAllTips", {
|
|
166
|
+
type: t(filterParams.status)
|
|
167
|
+
}),
|
|
168
|
+
children: /* @__PURE__ */ jsx(
|
|
169
|
+
Button,
|
|
170
|
+
{
|
|
171
|
+
sx: { ml: 0.5 },
|
|
172
|
+
size: "small",
|
|
173
|
+
variant: "contained",
|
|
174
|
+
color: "error",
|
|
175
|
+
onClick: logoutAll,
|
|
176
|
+
disabled: disableLogout,
|
|
177
|
+
children: t("logoutAll", {
|
|
178
|
+
type: t(filterParams.status)
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
},
|
|
183
|
+
"logoutAll"
|
|
184
|
+
)
|
|
147
185
|
);
|
|
148
186
|
}
|
|
149
187
|
const tableOptions = useCreation(() => {
|
|
@@ -155,9 +193,12 @@ export default function UserSessions({
|
|
|
155
193
|
filter: false,
|
|
156
194
|
print: false,
|
|
157
195
|
expandableRowsOnClick: false,
|
|
158
|
-
searchDebounceTime: 600
|
|
196
|
+
searchDebounceTime: 600,
|
|
197
|
+
page: filterParams.page - 1,
|
|
198
|
+
rowsPerPage: filterParams.pageSize,
|
|
199
|
+
count: pageState.data?.total || 0
|
|
159
200
|
};
|
|
160
|
-
}, []);
|
|
201
|
+
}, [pageState.data?.total, filterParams.page, filterParams.pageSize]);
|
|
161
202
|
const columns = [
|
|
162
203
|
{
|
|
163
204
|
label: t("platform"),
|
|
@@ -307,13 +348,60 @@ export default function UserSessions({
|
|
|
307
348
|
/* @__PURE__ */ jsx(
|
|
308
349
|
Datatable,
|
|
309
350
|
{
|
|
351
|
+
count: pageState.data?.total || 0,
|
|
310
352
|
locale,
|
|
311
353
|
data: safeData,
|
|
312
354
|
columns,
|
|
313
355
|
customButtons,
|
|
314
356
|
options: tableOptions,
|
|
315
357
|
loading: pageState.loading,
|
|
316
|
-
className: "pc-user-sessions-table"
|
|
358
|
+
className: "pc-user-sessions-table",
|
|
359
|
+
title: /* @__PURE__ */ jsxs(
|
|
360
|
+
RadioGroup,
|
|
361
|
+
{
|
|
362
|
+
row: true,
|
|
363
|
+
sx: { lineHeight: 1, ml: 1 },
|
|
364
|
+
onChange: (e) => {
|
|
365
|
+
filterParams.status = e.target.value;
|
|
366
|
+
},
|
|
367
|
+
children: [
|
|
368
|
+
/* @__PURE__ */ jsx(
|
|
369
|
+
FormControlLabel,
|
|
370
|
+
{
|
|
371
|
+
value: "online",
|
|
372
|
+
control: /* @__PURE__ */ jsx(Radio, { size: "small", sx: { lineHeight: 1, fontSize: 0 }, checked: filterParams.status === "online" }),
|
|
373
|
+
label: /* @__PURE__ */ jsxs(Typography, { sx: { display: "flex", alignItems: "center" }, children: [
|
|
374
|
+
t("online"),
|
|
375
|
+
/* @__PURE__ */ jsxs(Typography, { component: "span", sx: { ml: 0.5, color: "text.secondary" }, children: [
|
|
376
|
+
"(",
|
|
377
|
+
userSessionsCountMap.online,
|
|
378
|
+
")"
|
|
379
|
+
] })
|
|
380
|
+
] })
|
|
381
|
+
}
|
|
382
|
+
),
|
|
383
|
+
/* @__PURE__ */ jsx(
|
|
384
|
+
FormControlLabel,
|
|
385
|
+
{
|
|
386
|
+
value: "expired",
|
|
387
|
+
control: /* @__PURE__ */ jsx(Radio, { size: "small", sx: { lineHeight: 1, fontSize: 0 }, checked: filterParams.status === "expired" }),
|
|
388
|
+
label: /* @__PURE__ */ jsxs(Typography, { sx: { display: "flex", alignItems: "center" }, children: [
|
|
389
|
+
t("expired"),
|
|
390
|
+
/* @__PURE__ */ jsxs(Typography, { component: "span", sx: { ml: 0.5, color: "text.secondary" }, children: [
|
|
391
|
+
"(",
|
|
392
|
+
userSessionsCountMap.expired,
|
|
393
|
+
")"
|
|
394
|
+
] })
|
|
395
|
+
] })
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
),
|
|
401
|
+
onChange: (state) => {
|
|
402
|
+
filterParams.page = state.page + 1;
|
|
403
|
+
filterParams.pageSize = state.rowsPerPage;
|
|
404
|
+
}
|
|
317
405
|
}
|
|
318
406
|
)
|
|
319
407
|
]
|
|
@@ -26,6 +26,7 @@ export declare const translations: {
|
|
|
26
26
|
logoutThisSessionConfirm: string;
|
|
27
27
|
logoutAllSession: string;
|
|
28
28
|
logoutAllSessionConfirm: string;
|
|
29
|
+
online: string;
|
|
29
30
|
};
|
|
30
31
|
en: {
|
|
31
32
|
confirm: string;
|
|
@@ -54,5 +55,6 @@ export declare const translations: {
|
|
|
54
55
|
logoutThisSessionConfirm: string;
|
|
55
56
|
logoutAllSession: string;
|
|
56
57
|
logoutAllSessionConfirm: string;
|
|
58
|
+
online: string;
|
|
57
59
|
};
|
|
58
60
|
};
|
|
@@ -15,7 +15,7 @@ export const translations = {
|
|
|
15
15
|
updatedAt: "\u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4",
|
|
16
16
|
lastLoginIp: "\u6700\u8FD1\u6D3B\u52A8\u5730\u5740",
|
|
17
17
|
actions: "\u64CD\u4F5C",
|
|
18
|
-
logoutAll: "\u6CE8\u9500\u6240\u6709\u4F1A\u8BDD",
|
|
18
|
+
logoutAll: "\u6CE8\u9500\u6240\u6709{type}\u4F1A\u8BDD",
|
|
19
19
|
logoutAllTips: "\u4E0D\u4F1A\u6CE8\u9500\u5F53\u524D\u4F1A\u8BDD",
|
|
20
20
|
logout: "\u6CE8\u9500",
|
|
21
21
|
currentSession: "\u5F53\u524D\u4F1A\u8BDD",
|
|
@@ -24,8 +24,9 @@ export const translations = {
|
|
|
24
24
|
remove: "\u5220\u9664",
|
|
25
25
|
logoutThisSession: "\u6CE8\u9500\u6307\u5B9A\u4F1A\u8BDD",
|
|
26
26
|
logoutThisSessionConfirm: "\u786E\u5B9A\u8981\u6CE8\u9500\u6B64\u4F1A\u8BDD\u5417?",
|
|
27
|
-
logoutAllSession: "\u6CE8\u9500\u6240\u6709\u4F1A\u8BDD",
|
|
28
|
-
logoutAllSessionConfirm: "\u786E\u5B9A\u8981\u6CE8\u9500\u6240\u6709\u4F1A\u8BDD\u5417?"
|
|
27
|
+
logoutAllSession: "\u6CE8\u9500\u6240\u6709{type}\u4F1A\u8BDD",
|
|
28
|
+
logoutAllSessionConfirm: "\u786E\u5B9A\u8981\u6CE8\u9500\u6240\u6709{type}\u4F1A\u8BDD\u5417?",
|
|
29
|
+
online: "\u6D3B\u8DC3"
|
|
29
30
|
},
|
|
30
31
|
en: {
|
|
31
32
|
confirm: "Confirm",
|
|
@@ -43,7 +44,7 @@ export const translations = {
|
|
|
43
44
|
updatedAt: "Last Active Time",
|
|
44
45
|
actions: "Actions",
|
|
45
46
|
lastLoginIp: "Last Login IP",
|
|
46
|
-
logoutAll: "Logout all",
|
|
47
|
+
logoutAll: "Logout all {type} sessions",
|
|
47
48
|
logoutAllTips: "Will not logout current session",
|
|
48
49
|
logout: "Logout",
|
|
49
50
|
currentSession: "Current Session",
|
|
@@ -52,7 +53,8 @@ export const translations = {
|
|
|
52
53
|
remove: "Remove",
|
|
53
54
|
logoutThisSession: "Logout this session",
|
|
54
55
|
logoutThisSessionConfirm: "Are you sure to logout this session?",
|
|
55
|
-
logoutAllSession: "Logout all sessions",
|
|
56
|
-
logoutAllSessionConfirm: "Are you sure to logout all sessions?"
|
|
56
|
+
logoutAllSession: "Logout all {type} sessions",
|
|
57
|
+
logoutAllSessionConfirm: "Are you sure to logout all {type} sessions?",
|
|
58
|
+
online: "Active"
|
|
57
59
|
}
|
|
58
60
|
};
|
|
@@ -2,7 +2,6 @@ import { jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import Badge from "@mui/material/Badge";
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { useCallback, useEffect } from "react";
|
|
5
|
-
import { temp as colors } from "@arcblock/ux/lib/Colors";
|
|
6
5
|
import { IconButton } from "@mui/material";
|
|
7
6
|
import { useSnackbar } from "notistack";
|
|
8
7
|
import NotificationsOutlinedIcon from "@arcblock/icons/lib/Notification";
|
|
@@ -89,7 +88,6 @@ export default function NotificationAddon({ session = {} }) {
|
|
|
89
88
|
variant: "outlined",
|
|
90
89
|
href: viewAllUrl,
|
|
91
90
|
sx: {
|
|
92
|
-
borderColor: colors.lineBorderStrong,
|
|
93
91
|
"&:hover": {
|
|
94
92
|
borderRadius: "50%"
|
|
95
93
|
}
|