@blocklet/ui-react 2.10.3 → 2.10.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.
- package/lib/@types/index.d.ts +35 -0
- package/lib/Header/index.d.ts +8 -7
- package/lib/Header/index.js +28 -36
- package/lib/Icon/index.d.ts +7 -20
- package/lib/Icon/index.js +11 -18
- package/lib/UserCenter/components/config-profile.d.ts +2 -1
- package/lib/UserCenter/components/config-profile.js +4 -2
- package/lib/UserCenter/components/privacy.d.ts +2 -2
- package/lib/UserCenter/components/privacy.js +31 -26
- package/lib/UserCenter/components/settings.d.ts +1 -1
- package/lib/UserCenter/components/settings.js +1 -1
- package/lib/UserCenter/components/user-center.js +17 -13
- package/lib/UserCenter/components/user-info/user-info.d.ts +2 -1
- package/lib/UserCenter/components/user-info/user-info.js +29 -20
- package/lib/UserCenter/libs/locales.d.ts +2 -0
- package/lib/UserCenter/libs/locales.js +4 -2
- package/package.json +4 -4
- package/src/@types/index.ts +37 -0
- package/src/Header/{index.jsx → index.tsx} +28 -33
- package/src/Icon/{index.jsx → index.tsx} +17 -21
- package/src/UserCenter/components/config-profile.tsx +4 -2
- package/src/UserCenter/components/privacy.tsx +33 -25
- package/src/UserCenter/components/settings.tsx +2 -2
- package/src/UserCenter/components/user-center.tsx +18 -14
- package/src/UserCenter/components/user-info/user-info.tsx +32 -24
- package/src/UserCenter/libs/locales.ts +4 -2
package/lib/@types/index.d.ts
CHANGED
|
@@ -100,3 +100,38 @@ export type CreatePassportProps = {
|
|
|
100
100
|
width?: string;
|
|
101
101
|
height?: string;
|
|
102
102
|
};
|
|
103
|
+
export type BlockletMetaProps = {
|
|
104
|
+
appLogo?: React.ReactNode;
|
|
105
|
+
appName?: string;
|
|
106
|
+
theme?: {
|
|
107
|
+
background?: string;
|
|
108
|
+
};
|
|
109
|
+
enableConnect?: boolean;
|
|
110
|
+
enableLocale?: boolean;
|
|
111
|
+
navigation?: Array<{
|
|
112
|
+
title?: string | object;
|
|
113
|
+
link?: string | object;
|
|
114
|
+
icon?: string;
|
|
115
|
+
items?: Array<{
|
|
116
|
+
title?: string | object;
|
|
117
|
+
link?: string | object;
|
|
118
|
+
}>;
|
|
119
|
+
}>;
|
|
120
|
+
};
|
|
121
|
+
export type SessionManagerProps = {
|
|
122
|
+
showText?: boolean;
|
|
123
|
+
showRole?: boolean;
|
|
124
|
+
switchDid?: boolean;
|
|
125
|
+
switchProfile?: boolean;
|
|
126
|
+
switchPassport?: boolean;
|
|
127
|
+
disableLogout?: boolean;
|
|
128
|
+
onLogin?: () => void;
|
|
129
|
+
onLogout?: () => void;
|
|
130
|
+
onSwitchDid?: () => void;
|
|
131
|
+
onSwitchProfile?: () => void;
|
|
132
|
+
onSwitchPassport?: () => void;
|
|
133
|
+
menu?: any[];
|
|
134
|
+
menuRender?: () => void;
|
|
135
|
+
dark?: boolean;
|
|
136
|
+
size?: number;
|
|
137
|
+
};
|
package/lib/Header/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { BlockletMetaProps, SessionManagerProps } from '../@types';
|
|
1
2
|
declare const _default: import("react").ComponentType<{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
meta: BlockletMetaProps;
|
|
4
|
+
addons: Function | JSX.Element;
|
|
5
|
+
sessionManagerProps: SessionManagerProps;
|
|
6
|
+
homeLink: string;
|
|
7
|
+
theme: object;
|
|
8
|
+
hideNavMenu: boolean;
|
|
9
|
+
bordered?: boolean;
|
|
9
10
|
}>;
|
|
10
11
|
export default _default;
|
package/lib/Header/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
|
-
import PropTypes from "prop-types";
|
|
4
3
|
import { withErrorBoundary } from "react-error-boundary";
|
|
5
4
|
import { ErrorFallback } from "@arcblock/ux/lib/ErrorBoundary";
|
|
6
5
|
import { styled } from "@arcblock/ux/lib/Theme";
|
|
@@ -11,7 +10,6 @@ import { temp as colors } from "@arcblock/ux/lib/Colors";
|
|
|
11
10
|
import omit from "lodash/omit";
|
|
12
11
|
import Icon from "../Icon/index.js";
|
|
13
12
|
import OverridableThemeProvider from "../common/overridable-theme-provider.js";
|
|
14
|
-
import { BlockletMetaProps, SessionManagerProps } from "../types.js";
|
|
15
13
|
import { mapRecursive, flatRecursive, matchPaths } from "../utils.js";
|
|
16
14
|
import { publicPath, formatBlockletInfo, getLocalizedNavigation } from "../blocklets.js";
|
|
17
15
|
import HeaderAddons from "../common/header-addons.js";
|
|
@@ -50,7 +48,17 @@ const parseNavigation = (navigation) => {
|
|
|
50
48
|
const matchedIndex = matchPaths(flattened.map((item) => item.link));
|
|
51
49
|
return { navItems, activeId: matchedIndex >= 0 ? flattened[matchedIndex].id : null };
|
|
52
50
|
};
|
|
53
|
-
function Header({
|
|
51
|
+
function Header({
|
|
52
|
+
meta = {},
|
|
53
|
+
addons,
|
|
54
|
+
sessionManagerProps = {
|
|
55
|
+
showRole: true
|
|
56
|
+
},
|
|
57
|
+
homeLink = publicPath,
|
|
58
|
+
theme: themeOverrides,
|
|
59
|
+
hideNavMenu = false,
|
|
60
|
+
...rest
|
|
61
|
+
}) {
|
|
54
62
|
useWalletHiddenTopbar();
|
|
55
63
|
const { locale } = useLocaleContext() || {};
|
|
56
64
|
const formattedBlocklet = useMemo(() => {
|
|
@@ -70,7 +78,10 @@ function Header({ meta, addons, sessionManagerProps, homeLink, theme: themeOverr
|
|
|
70
78
|
const parsedNavigation = parseNavigation(navigation);
|
|
71
79
|
const { navItems, activeId } = parsedNavigation;
|
|
72
80
|
const _addons = typeof addons === "function" ? (builtInAddons) => addons(builtInAddons, { navigation: parsedNavigation }) : addons;
|
|
73
|
-
const headerAddons =
|
|
81
|
+
const headerAddons = (
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
/* @__PURE__ */ jsx(HeaderAddons, { formattedBlocklet, addons: _addons, sessionManagerProps })
|
|
84
|
+
);
|
|
74
85
|
return /* @__PURE__ */ jsx(OverridableThemeProvider, { theme: themeOverrides, children: /* @__PURE__ */ jsx(
|
|
75
86
|
StyledUxHeader,
|
|
76
87
|
{
|
|
@@ -80,42 +91,23 @@ function Header({ meta, addons, sessionManagerProps, homeLink, theme: themeOverr
|
|
|
80
91
|
...omit(rest, ["bordered"]),
|
|
81
92
|
$bordered: rest?.bordered,
|
|
82
93
|
$bgcolor: theme?.background?.header,
|
|
83
|
-
children: hideNavMenu || !navItems?.length ? null : ({ isMobile }) =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
children: hideNavMenu || !navItems?.length ? null : ({ isMobile }) => (
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
/* @__PURE__ */ jsx(
|
|
97
|
+
NavMenu,
|
|
98
|
+
{
|
|
99
|
+
mode: isMobile ? "inline" : "horizontal",
|
|
100
|
+
activeId,
|
|
101
|
+
items: navItems,
|
|
102
|
+
className: "header-nav",
|
|
103
|
+
bgColor: "transparent",
|
|
104
|
+
textColor: "#777"
|
|
105
|
+
}
|
|
106
|
+
)
|
|
93
107
|
)
|
|
94
108
|
}
|
|
95
109
|
) });
|
|
96
110
|
}
|
|
97
|
-
Header.propTypes = {
|
|
98
|
-
meta: BlockletMetaProps,
|
|
99
|
-
// 需要考虑 定制的 addons 与内置的 连接钱包/选择语言 addons 共存的情况
|
|
100
|
-
// - PropTypes.func: 可以把自定义 addons 插在 session-manager 或 locale-selector (如果存在的话) 前/中/后
|
|
101
|
-
// - PropTypes.node: 将 addons 原样传给 UX Header 组件
|
|
102
|
-
addons: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
|
|
103
|
-
sessionManagerProps: SessionManagerProps,
|
|
104
|
-
homeLink: PropTypes.string,
|
|
105
|
-
// 允许覆盖 header 内置的 theme
|
|
106
|
-
theme: PropTypes.object,
|
|
107
|
-
hideNavMenu: PropTypes.bool
|
|
108
|
-
};
|
|
109
|
-
Header.defaultProps = {
|
|
110
|
-
meta: {},
|
|
111
|
-
addons: null,
|
|
112
|
-
sessionManagerProps: {
|
|
113
|
-
showRole: true
|
|
114
|
-
},
|
|
115
|
-
homeLink: publicPath,
|
|
116
|
-
theme: null,
|
|
117
|
-
hideNavMenu: false
|
|
118
|
-
};
|
|
119
111
|
const StyledUxHeader = styled(ResponsiveHeader)`
|
|
120
112
|
${({ $bgcolor }) => `background-color: ${$bgcolor || "#fff"};`}
|
|
121
113
|
font-family: Inter, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
|
package/lib/Icon/index.d.ts
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
|
+
import 'iconify-icon';
|
|
2
|
+
import type { AvatarProps, BoxProps } from '@mui/material';
|
|
1
3
|
/**
|
|
2
4
|
* Icon 组件, 基于 mui Avatar 组件扩展对 iconify 的支持
|
|
3
5
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}): import("react").JSX.Element | null;
|
|
10
|
-
declare namespace Icon {
|
|
11
|
-
namespace propTypes {
|
|
12
|
-
let icon: any;
|
|
13
|
-
let size: any;
|
|
14
|
-
let sx: any;
|
|
15
|
-
}
|
|
16
|
-
namespace defaultProps {
|
|
17
|
-
let size_1: null;
|
|
18
|
-
export { size_1 as size };
|
|
19
|
-
let sx_1: null;
|
|
20
|
-
export { sx_1 as sx };
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export default Icon;
|
|
6
|
+
export default function Icon({ icon, size, sx, ...rest }: {
|
|
7
|
+
icon: string;
|
|
8
|
+
size?: number;
|
|
9
|
+
sx?: BoxProps['sx'];
|
|
10
|
+
} & AvatarProps): import("react").JSX.Element | null;
|
package/lib/Icon/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import "iconify-icon";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import { Avatar } from "@mui/material";
|
|
4
|
+
import { Icon as IconifyIcon } from "@iconify/react";
|
|
5
5
|
import { isUrl, isIconifyString } from "../utils.js";
|
|
6
|
-
export default function Icon({
|
|
6
|
+
export default function Icon({
|
|
7
|
+
icon,
|
|
8
|
+
size,
|
|
9
|
+
sx,
|
|
10
|
+
...rest
|
|
11
|
+
}) {
|
|
7
12
|
const _sx = [...Array.isArray(sx) ? sx : [sx]];
|
|
8
13
|
if (size) {
|
|
9
14
|
_sx.push({ width: size, height: size });
|
|
@@ -24,11 +29,11 @@ export default function Icon({ icon, size, sx, ...rest }) {
|
|
|
24
29
|
});
|
|
25
30
|
}
|
|
26
31
|
if (isUrl(icon)) {
|
|
27
|
-
return /* @__PURE__ */ jsx(Avatar, {
|
|
32
|
+
return /* @__PURE__ */ jsx(Avatar, { component: "span", ...rest, src: icon, sx: _sx });
|
|
28
33
|
}
|
|
29
34
|
if (isIconifyString(icon)) {
|
|
30
35
|
const height = size ? 0.6 * size + 4 : 0;
|
|
31
|
-
return /* @__PURE__ */ jsx(Avatar, {
|
|
36
|
+
return /* @__PURE__ */ jsx(Avatar, { component: "span", ...rest, sx: _sx, children: /* @__PURE__ */ jsx(IconifyIcon, { icon, height: height || void 0 }) });
|
|
32
37
|
}
|
|
33
38
|
if (icon && typeof icon === "string") {
|
|
34
39
|
_sx.push({
|
|
@@ -37,19 +42,7 @@ export default function Icon({ icon, size, sx, ...rest }) {
|
|
|
37
42
|
...size && { fontSize: size - 2 }
|
|
38
43
|
}
|
|
39
44
|
});
|
|
40
|
-
return /* @__PURE__ */ jsx(Avatar, {
|
|
45
|
+
return /* @__PURE__ */ jsx(Avatar, { component: "span", ...rest, sx: _sx, children: Array.from(icon)[0] });
|
|
41
46
|
}
|
|
42
47
|
return null;
|
|
43
48
|
}
|
|
44
|
-
Icon.propTypes = {
|
|
45
|
-
// icon 支持 2 种形式:
|
|
46
|
-
// 1. iconify icon name: <prefix>:<name>
|
|
47
|
-
// 2. url
|
|
48
|
-
icon: PropTypes.string.isRequired,
|
|
49
|
-
size: PropTypes.number,
|
|
50
|
-
sx: PropTypes.oneOfType([PropTypes.array, PropTypes.func, PropTypes.object])
|
|
51
|
-
};
|
|
52
|
-
Icon.defaultProps = {
|
|
53
|
-
size: null,
|
|
54
|
-
sx: null
|
|
55
|
-
};
|
|
@@ -10,8 +10,8 @@ const languages = [
|
|
|
10
10
|
{ label: "English", value: "en" },
|
|
11
11
|
{ label: "\u4E2D\u6587", value: "zh" }
|
|
12
12
|
];
|
|
13
|
-
export default function ConfigProfile({ user }) {
|
|
14
|
-
const { locale } = useLocaleContext();
|
|
13
|
+
export default function ConfigProfile({ user, onSave }) {
|
|
14
|
+
const { locale, changeLocale } = useLocaleContext();
|
|
15
15
|
const t = useMemoizedFn((key, data = {}) => {
|
|
16
16
|
return translate(translations, key, locale, "en", data);
|
|
17
17
|
});
|
|
@@ -29,6 +29,8 @@ export default function ConfigProfile({ user }) {
|
|
|
29
29
|
}),
|
|
30
30
|
sleep(350)
|
|
31
31
|
]);
|
|
32
|
+
await onSave("profile");
|
|
33
|
+
changeLocale(value);
|
|
32
34
|
currentState.locale = value;
|
|
33
35
|
} finally {
|
|
34
36
|
currentState.loading = false;
|
|
@@ -3,8 +3,8 @@ type PrivacyConfig = {
|
|
|
3
3
|
name: string;
|
|
4
4
|
value: boolean;
|
|
5
5
|
};
|
|
6
|
-
export default function Privacy({ configList, onSave }: {
|
|
6
|
+
export default function Privacy({ configList, onSave, }: {
|
|
7
7
|
configList: PrivacyConfig[];
|
|
8
|
-
onSave: () => void;
|
|
8
|
+
onSave: (type: 'privacy') => void;
|
|
9
9
|
}): import("react").JSX.Element;
|
|
10
10
|
export {};
|
|
@@ -9,7 +9,10 @@ import Toast from "@arcblock/ux/lib/Toast";
|
|
|
9
9
|
import { translations } from "../libs/locales.js";
|
|
10
10
|
import { client } from "../../libs/client.js";
|
|
11
11
|
import { formatAxiosError } from "../libs/utils.js";
|
|
12
|
-
export default function Privacy({
|
|
12
|
+
export default function Privacy({
|
|
13
|
+
configList,
|
|
14
|
+
onSave
|
|
15
|
+
}) {
|
|
13
16
|
const [dataList, setDataList] = useState(configList);
|
|
14
17
|
const { locale } = useLocaleContext();
|
|
15
18
|
const t = useMemoizedFn((key, data = {}) => {
|
|
@@ -29,7 +32,7 @@ export default function Privacy({ configList, onSave }) {
|
|
|
29
32
|
})
|
|
30
33
|
);
|
|
31
34
|
Toast.success(t("saveSuccess"));
|
|
32
|
-
onSave();
|
|
35
|
+
onSave("privacy");
|
|
33
36
|
} catch (err) {
|
|
34
37
|
Toast.error(formatAxiosError(err));
|
|
35
38
|
}
|
|
@@ -69,31 +72,33 @@ export default function Privacy({ configList, onSave }) {
|
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
},
|
|
72
|
-
children: dataList.map((item) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
children: dataList.map((item) => {
|
|
76
|
+
return /* @__PURE__ */ jsx(
|
|
77
|
+
Switch,
|
|
78
|
+
{
|
|
79
|
+
checked: !item.value,
|
|
80
|
+
labelProps: {
|
|
81
|
+
label: /* @__PURE__ */ jsx(
|
|
82
|
+
Typography,
|
|
83
|
+
{
|
|
84
|
+
color: "text.primary",
|
|
85
|
+
sx: {
|
|
86
|
+
fontSize: 14,
|
|
87
|
+
display: "flex",
|
|
88
|
+
flexFlow: "wrap",
|
|
89
|
+
columnGap: 1,
|
|
90
|
+
flex: 1
|
|
91
|
+
},
|
|
92
|
+
children: t("toPublic", { name: item.name })
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
},
|
|
96
|
+
size: "small",
|
|
97
|
+
onChange: (event) => handleChangeSwitch(item.key, event.target.checked)
|
|
91
98
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
item.key
|
|
96
|
-
))
|
|
99
|
+
item.key
|
|
100
|
+
);
|
|
101
|
+
})
|
|
97
102
|
}
|
|
98
103
|
);
|
|
99
104
|
}
|
|
@@ -2,7 +2,7 @@ import type { BoxProps } from '@mui/material';
|
|
|
2
2
|
import { User, UserCenterTab } from '../../@types';
|
|
3
3
|
export default function Settings({ user, settings, onSave, ...rest }: {
|
|
4
4
|
user: User;
|
|
5
|
-
onSave: () => void;
|
|
5
|
+
onSave: (type: 'privacy' | 'profile') => void;
|
|
6
6
|
settings: {
|
|
7
7
|
userCenterTabs: UserCenterTab[];
|
|
8
8
|
};
|
|
@@ -37,7 +37,7 @@ export default function Settings({
|
|
|
37
37
|
{
|
|
38
38
|
label: t("commonSetting.title"),
|
|
39
39
|
value: "common",
|
|
40
|
-
content: /* @__PURE__ */ jsx(ConfigProfile, { user })
|
|
40
|
+
content: /* @__PURE__ */ jsx(ConfigProfile, { user, onSave })
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
label: t("notificationManagement"),
|
|
@@ -149,13 +149,19 @@ export default function UserCenter({
|
|
|
149
149
|
{
|
|
150
150
|
user: userState.data,
|
|
151
151
|
settings: { userCenterTabs },
|
|
152
|
-
onSave: async () => {
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
onSave: async (type) => {
|
|
153
|
+
if (type === "privacy") {
|
|
154
|
+
await privacyState.runAsync();
|
|
155
|
+
return privacyState.data;
|
|
156
|
+
}
|
|
157
|
+
if (type === "profile") {
|
|
158
|
+
await session.refresh();
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
);
|
|
158
|
-
}, [userState.data]);
|
|
164
|
+
}, [userState.data, userCenterTabs, privacyState.data, privacyState.runAsync]);
|
|
159
165
|
const openSettings = useMemoizedFn(() => {
|
|
160
166
|
confirmApi.open({
|
|
161
167
|
title: t("settings"),
|
|
@@ -352,15 +358,13 @@ export default function UserCenter({
|
|
|
352
358
|
}
|
|
353
359
|
}
|
|
354
360
|
),
|
|
355
|
-
|
|
356
|
-
/* @__PURE__ */
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
/* @__PURE__ */
|
|
361
|
-
|
|
362
|
-
/* @__PURE__ */ jsx(Passport, { user: userState.data })
|
|
363
|
-
] })
|
|
361
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
362
|
+
/* @__PURE__ */ jsx(Typography, { sx: { fontWeight: 600, mb: 1.5 }, children: isMyself ? t("myInfo") : t("hisInfo") }),
|
|
363
|
+
/* @__PURE__ */ jsx(UserInfo, { user: userState.data, isMySelf: isMyself })
|
|
364
|
+
] }),
|
|
365
|
+
isMyself ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
366
|
+
/* @__PURE__ */ jsx(Typography, { sx: { fontWeight: 600, mb: 1.5 }, children: t("passport") }),
|
|
367
|
+
/* @__PURE__ */ jsx(Passport, { user: userState.data })
|
|
364
368
|
] }) : null
|
|
365
369
|
]
|
|
366
370
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BoxProps } from '@mui/material';
|
|
2
2
|
import type { User } from '../../../@types';
|
|
3
|
-
export default function UserInfo({ user, ...rest }: {
|
|
3
|
+
export default function UserInfo({ user, isMySelf, ...rest }: {
|
|
4
4
|
user: User;
|
|
5
|
+
isMySelf: boolean;
|
|
5
6
|
} & BoxProps): import("react").JSX.Element;
|
|
@@ -15,6 +15,7 @@ import { translations } from "../../libs/locales.js";
|
|
|
15
15
|
import UserInfoItem from "./user-info-item.js";
|
|
16
16
|
export default function UserInfo({
|
|
17
17
|
user,
|
|
18
|
+
isMySelf = false,
|
|
18
19
|
...rest
|
|
19
20
|
}) {
|
|
20
21
|
const { locale } = useLocaleContext();
|
|
@@ -25,31 +26,39 @@ export default function UserInfo({
|
|
|
25
26
|
return user?.sourceProvider && LOGIN_PROVIDER_NAME[user.sourceProvider] || t("unknown");
|
|
26
27
|
}, [user?.sourceProvider]);
|
|
27
28
|
const userInfoListData = [];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
if (isMySelf) {
|
|
30
|
+
userInfoListData.push({
|
|
31
|
+
icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: MailOutlineRoundedIcon }),
|
|
32
|
+
title: t("email"),
|
|
33
|
+
content: user?.email || t("emptyField")
|
|
34
|
+
});
|
|
35
|
+
userInfoListData.push({
|
|
36
|
+
icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: ScheduleOutlineRoundedIcon }),
|
|
37
|
+
title: t("lastLoginAt"),
|
|
38
|
+
content: user?.lastLoginAt ? (
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
/* @__PURE__ */ jsx(RelativeTime, { locale, value: user?.lastLoginAt, relativeRange: 3 * 86400 * 1e3 })
|
|
41
|
+
) : t("unknown")
|
|
42
|
+
});
|
|
43
|
+
userInfoListData.push({
|
|
44
|
+
icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: SettingsInputAntennaRoundedIcon }),
|
|
45
|
+
title: t("lastLoginIp"),
|
|
46
|
+
content: user?.lastLoginIp || t("unknown")
|
|
47
|
+
});
|
|
48
|
+
}
|
|
43
49
|
userInfoListData.push({
|
|
44
50
|
icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: MoreTimeRoundedIcon }),
|
|
45
51
|
title: t("createdAt"),
|
|
52
|
+
// @ts-ignore
|
|
46
53
|
content: user?.createdAt ? /* @__PURE__ */ jsx(RelativeTime, { locale, value: user?.createdAt }) : t("unknown")
|
|
47
54
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
if (isMySelf) {
|
|
56
|
+
userInfoListData.push({
|
|
57
|
+
icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: CaptivePortalRoundedIcon }),
|
|
58
|
+
title: t("registerFrom"),
|
|
59
|
+
content: readableProvider
|
|
60
|
+
});
|
|
61
|
+
}
|
|
53
62
|
return /* @__PURE__ */ jsx(
|
|
54
63
|
Box,
|
|
55
64
|
{
|
|
@@ -42,6 +42,7 @@ export declare const translations: {
|
|
|
42
42
|
switchProfile: string;
|
|
43
43
|
userInfo: string;
|
|
44
44
|
myInfo: string;
|
|
45
|
+
hisInfo: string;
|
|
45
46
|
loginNow: string;
|
|
46
47
|
viewAfterLogin: string;
|
|
47
48
|
sessionManagement: string;
|
|
@@ -127,6 +128,7 @@ export declare const translations: {
|
|
|
127
128
|
switchProfile: string;
|
|
128
129
|
userInfo: string;
|
|
129
130
|
myInfo: string;
|
|
131
|
+
hisInfo: string;
|
|
130
132
|
loginNow: string;
|
|
131
133
|
viewAfterLogin: string;
|
|
132
134
|
sessionManagement: string;
|
|
@@ -8,7 +8,7 @@ export const translations = {
|
|
|
8
8
|
lastLogin: "\u4E0A\u6B21\u767B\u5F55",
|
|
9
9
|
lastLoginAt: "\u4E0A\u6B21\u767B\u5F55\u65F6\u95F4",
|
|
10
10
|
lastLoginIp: "\u4E0A\u6B21\u767B\u5F55\u5730\u5740",
|
|
11
|
-
createdAt: "\
|
|
11
|
+
createdAt: "\u52A0\u5165\u65F6\u95F4",
|
|
12
12
|
registerFrom: "\u6CE8\u518C\u6765\u6E90",
|
|
13
13
|
unknown: "\u672A\u77E5",
|
|
14
14
|
walletNotification: "\u94B1\u5305\u901A\u77E5",
|
|
@@ -42,6 +42,7 @@ export const translations = {
|
|
|
42
42
|
switchProfile: "\u5207\u6362",
|
|
43
43
|
userInfo: "\u4E2A\u4EBA\u4FE1\u606F",
|
|
44
44
|
myInfo: "\u6211\u7684\u4FE1\u606F",
|
|
45
|
+
hisInfo: "TA \u7684\u4FE1\u606F",
|
|
45
46
|
loginNow: "\u7ACB\u5373\u767B\u5F55",
|
|
46
47
|
viewAfterLogin: "\u767B\u5F55\u540E\u624D\u53EF\u4EE5\u67E5\u770B",
|
|
47
48
|
sessionManagement: "\u4F1A\u8BDD\u7BA1\u7406",
|
|
@@ -93,7 +94,7 @@ export const translations = {
|
|
|
93
94
|
lastLogin: "Last Login & IP",
|
|
94
95
|
lastLoginAt: "Last Login",
|
|
95
96
|
lastLoginIp: "Last IP",
|
|
96
|
-
createdAt: "
|
|
97
|
+
createdAt: "Member Since",
|
|
97
98
|
registerFrom: "Register From",
|
|
98
99
|
unknown: "Unknown",
|
|
99
100
|
walletNotification: "DID Wallet notification",
|
|
@@ -127,6 +128,7 @@ export const translations = {
|
|
|
127
128
|
switchProfile: "Switch",
|
|
128
129
|
userInfo: "User Info",
|
|
129
130
|
myInfo: "My Info",
|
|
131
|
+
hisInfo: "His/Her Info",
|
|
130
132
|
loginNow: "Login",
|
|
131
133
|
viewAfterLogin: "View after login",
|
|
132
134
|
sessionManagement: "Session Management",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/ui-react",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.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.10.
|
|
36
|
-
"@arcblock/react-hooks": "^2.10.
|
|
35
|
+
"@arcblock/bridge": "^2.10.5",
|
|
36
|
+
"@arcblock/react-hooks": "^2.10.5",
|
|
37
37
|
"@iconify-icons/logos": "^1.2.36",
|
|
38
38
|
"@iconify-icons/material-symbols": "^1.2.58",
|
|
39
39
|
"@iconify/react": "^4.1.1",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"jest": "^28.1.3",
|
|
79
79
|
"unbuild": "^2.0.0"
|
|
80
80
|
},
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "b5740a02ed3bae848a586abe9cd8b35d507f0152"
|
|
82
82
|
}
|
package/src/@types/index.ts
CHANGED
|
@@ -112,3 +112,40 @@ export type CreatePassportProps = {
|
|
|
112
112
|
width?: string;
|
|
113
113
|
height?: string;
|
|
114
114
|
};
|
|
115
|
+
|
|
116
|
+
export type BlockletMetaProps = {
|
|
117
|
+
appLogo?: React.ReactNode;
|
|
118
|
+
appName?: string;
|
|
119
|
+
theme?: {
|
|
120
|
+
background?: string;
|
|
121
|
+
};
|
|
122
|
+
enableConnect?: boolean;
|
|
123
|
+
enableLocale?: boolean;
|
|
124
|
+
navigation?: Array<{
|
|
125
|
+
title?: string | object;
|
|
126
|
+
link?: string | object;
|
|
127
|
+
icon?: string;
|
|
128
|
+
items?: Array<{
|
|
129
|
+
title?: string | object;
|
|
130
|
+
link?: string | object;
|
|
131
|
+
}>;
|
|
132
|
+
}>;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type SessionManagerProps = {
|
|
136
|
+
showText?: boolean;
|
|
137
|
+
showRole?: boolean;
|
|
138
|
+
switchDid?: boolean;
|
|
139
|
+
switchProfile?: boolean;
|
|
140
|
+
switchPassport?: boolean;
|
|
141
|
+
disableLogout?: boolean;
|
|
142
|
+
onLogin?: () => void;
|
|
143
|
+
onLogout?: () => void;
|
|
144
|
+
onSwitchDid?: () => void;
|
|
145
|
+
onSwitchProfile?: () => void;
|
|
146
|
+
onSwitchPassport?: () => void;
|
|
147
|
+
menu?: any[];
|
|
148
|
+
menuRender?: () => void;
|
|
149
|
+
dark?: boolean;
|
|
150
|
+
size?: number;
|
|
151
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
2
|
import { withErrorBoundary } from 'react-error-boundary';
|
|
4
3
|
import { ErrorFallback } from '@arcblock/ux/lib/ErrorBoundary';
|
|
5
4
|
import { styled } from '@arcblock/ux/lib/Theme';
|
|
@@ -11,19 +10,19 @@ import omit from 'lodash/omit';
|
|
|
11
10
|
|
|
12
11
|
import Icon from '../Icon';
|
|
13
12
|
import OverridableThemeProvider from '../common/overridable-theme-provider';
|
|
14
|
-
import { BlockletMetaProps, SessionManagerProps } from '../types';
|
|
15
13
|
import { mapRecursive, flatRecursive, matchPaths } from '../utils';
|
|
16
14
|
import { publicPath, formatBlockletInfo, getLocalizedNavigation } from '../blocklets';
|
|
17
15
|
import HeaderAddons from '../common/header-addons';
|
|
18
16
|
import { useWalletHiddenTopbar } from '../common/wallet-hidden-topbar';
|
|
17
|
+
import { BlockletMetaProps, SessionManagerProps } from '../@types';
|
|
19
18
|
|
|
20
19
|
// blocklet meta 中的 navigation 数据 => NavMenu 组件的 items
|
|
21
|
-
const parseNavigation = (navigation) => {
|
|
20
|
+
const parseNavigation = (navigation: any) => {
|
|
22
21
|
if (!navigation?.length) {
|
|
23
22
|
return { navItems: [], activeId: null };
|
|
24
23
|
}
|
|
25
24
|
let counter = 1;
|
|
26
|
-
const parseItem = (item) => {
|
|
25
|
+
const parseItem = (item: any) => {
|
|
27
26
|
const icon = item.icon ? <Icon icon={item.icon} /> : null;
|
|
28
27
|
if (item.items) {
|
|
29
28
|
return {
|
|
@@ -62,7 +61,25 @@ const parseNavigation = (navigation) => {
|
|
|
62
61
|
*/
|
|
63
62
|
// eslint-disable-next-line no-shadow
|
|
64
63
|
|
|
65
|
-
function Header({
|
|
64
|
+
function Header({
|
|
65
|
+
meta = {},
|
|
66
|
+
addons,
|
|
67
|
+
sessionManagerProps = {
|
|
68
|
+
showRole: true,
|
|
69
|
+
},
|
|
70
|
+
homeLink = publicPath,
|
|
71
|
+
theme: themeOverrides,
|
|
72
|
+
hideNavMenu = false,
|
|
73
|
+
...rest
|
|
74
|
+
}: {
|
|
75
|
+
meta: BlockletMetaProps;
|
|
76
|
+
addons: Function | JSX.Element;
|
|
77
|
+
sessionManagerProps: SessionManagerProps;
|
|
78
|
+
homeLink: string;
|
|
79
|
+
theme: object;
|
|
80
|
+
hideNavMenu: boolean;
|
|
81
|
+
bordered?: boolean;
|
|
82
|
+
}) {
|
|
66
83
|
useWalletHiddenTopbar();
|
|
67
84
|
const { locale } = useLocaleContext() || {};
|
|
68
85
|
const formattedBlocklet = useMemo(() => {
|
|
@@ -85,8 +102,11 @@ function Header({ meta, addons, sessionManagerProps, homeLink, theme: themeOverr
|
|
|
85
102
|
|
|
86
103
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
87
104
|
const _addons =
|
|
88
|
-
typeof addons === 'function'
|
|
105
|
+
typeof addons === 'function'
|
|
106
|
+
? (builtInAddons: any) => addons(builtInAddons, { navigation: parsedNavigation })
|
|
107
|
+
: addons;
|
|
89
108
|
const headerAddons = (
|
|
109
|
+
// @ts-ignore
|
|
90
110
|
<HeaderAddons formattedBlocklet={formattedBlocklet} addons={_addons} sessionManagerProps={sessionManagerProps} />
|
|
91
111
|
);
|
|
92
112
|
|
|
@@ -102,7 +122,8 @@ function Header({ meta, addons, sessionManagerProps, homeLink, theme: themeOverr
|
|
|
102
122
|
{/* blocklet.yml 没有配置 navigation 时, 则为 children 传入 null, 此时 ResponsiveHeader 会渲染普通的不带 menu 的 Header */}
|
|
103
123
|
{hideNavMenu || !navItems?.length
|
|
104
124
|
? null
|
|
105
|
-
: ({ isMobile }) => (
|
|
125
|
+
: ({ isMobile }: { isMobile: boolean }) => (
|
|
126
|
+
// @ts-ignore
|
|
106
127
|
<NavMenu
|
|
107
128
|
mode={isMobile ? 'inline' : 'horizontal'}
|
|
108
129
|
activeId={activeId}
|
|
@@ -117,32 +138,6 @@ function Header({ meta, addons, sessionManagerProps, homeLink, theme: themeOverr
|
|
|
117
138
|
);
|
|
118
139
|
}
|
|
119
140
|
|
|
120
|
-
Header.propTypes = {
|
|
121
|
-
meta: BlockletMetaProps,
|
|
122
|
-
|
|
123
|
-
// 需要考虑 定制的 addons 与内置的 连接钱包/选择语言 addons 共存的情况
|
|
124
|
-
// - PropTypes.func: 可以把自定义 addons 插在 session-manager 或 locale-selector (如果存在的话) 前/中/后
|
|
125
|
-
// - PropTypes.node: 将 addons 原样传给 UX Header 组件
|
|
126
|
-
addons: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
|
|
127
|
-
|
|
128
|
-
sessionManagerProps: SessionManagerProps,
|
|
129
|
-
homeLink: PropTypes.string,
|
|
130
|
-
// 允许覆盖 header 内置的 theme
|
|
131
|
-
theme: PropTypes.object,
|
|
132
|
-
hideNavMenu: PropTypes.bool,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
Header.defaultProps = {
|
|
136
|
-
meta: {},
|
|
137
|
-
addons: null,
|
|
138
|
-
sessionManagerProps: {
|
|
139
|
-
showRole: true,
|
|
140
|
-
},
|
|
141
|
-
homeLink: publicPath,
|
|
142
|
-
theme: null,
|
|
143
|
-
hideNavMenu: false,
|
|
144
|
-
};
|
|
145
|
-
|
|
146
141
|
const StyledUxHeader = styled(ResponsiveHeader)`
|
|
147
142
|
${({ $bgcolor }) => `background-color: ${$bgcolor || '#fff'};`}
|
|
148
143
|
font-family: Inter, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import 'iconify-icon';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import { Avatar } from '@mui/material';
|
|
4
|
+
import type { AvatarProps, BoxProps } from '@mui/material';
|
|
5
|
+
import { Icon as IconifyIcon } from '@iconify/react';
|
|
5
6
|
import { isUrl, isIconifyString } from '../utils';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Icon 组件, 基于 mui Avatar 组件扩展对 iconify 的支持
|
|
9
10
|
*/
|
|
10
|
-
export default function Icon({
|
|
11
|
+
export default function Icon({
|
|
12
|
+
icon,
|
|
13
|
+
size,
|
|
14
|
+
sx,
|
|
15
|
+
...rest
|
|
16
|
+
}: {
|
|
17
|
+
icon: string;
|
|
18
|
+
size?: number;
|
|
19
|
+
sx?: BoxProps['sx'];
|
|
20
|
+
} & AvatarProps) {
|
|
11
21
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
12
22
|
const _sx = [...(Array.isArray(sx) ? sx : [sx])];
|
|
13
23
|
if (size) {
|
|
@@ -30,14 +40,14 @@ export default function Icon({ icon, size, sx, ...rest }) {
|
|
|
30
40
|
});
|
|
31
41
|
}
|
|
32
42
|
if (isUrl(icon)) {
|
|
33
|
-
return <Avatar
|
|
43
|
+
return <Avatar component="span" {...rest} src={icon} sx={_sx} />;
|
|
34
44
|
}
|
|
35
45
|
if (isIconifyString(icon)) {
|
|
36
46
|
// y = 0.6 * x + 4
|
|
37
47
|
const height = size ? 0.6 * size + 4 : 0;
|
|
38
48
|
return (
|
|
39
|
-
<Avatar
|
|
40
|
-
<
|
|
49
|
+
<Avatar component="span" {...rest} sx={_sx}>
|
|
50
|
+
<IconifyIcon icon={icon} height={height || undefined} />
|
|
41
51
|
</Avatar>
|
|
42
52
|
);
|
|
43
53
|
}
|
|
@@ -50,24 +60,10 @@ export default function Icon({ icon, size, sx, ...rest }) {
|
|
|
50
60
|
},
|
|
51
61
|
});
|
|
52
62
|
return (
|
|
53
|
-
<Avatar
|
|
63
|
+
<Avatar component="span" {...rest} sx={_sx}>
|
|
54
64
|
{Array.from(icon)[0]}
|
|
55
65
|
</Avatar>
|
|
56
66
|
);
|
|
57
67
|
}
|
|
58
68
|
return null;
|
|
59
69
|
}
|
|
60
|
-
|
|
61
|
-
Icon.propTypes = {
|
|
62
|
-
// icon 支持 2 种形式:
|
|
63
|
-
// 1. iconify icon name: <prefix>:<name>
|
|
64
|
-
// 2. url
|
|
65
|
-
icon: PropTypes.string.isRequired,
|
|
66
|
-
size: PropTypes.number,
|
|
67
|
-
sx: PropTypes.oneOfType([PropTypes.array, PropTypes.func, PropTypes.object]),
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
Icon.defaultProps = {
|
|
71
|
-
size: null,
|
|
72
|
-
sx: null,
|
|
73
|
-
};
|
|
@@ -14,8 +14,8 @@ const languages = [
|
|
|
14
14
|
{ label: '中文', value: 'zh' },
|
|
15
15
|
];
|
|
16
16
|
|
|
17
|
-
export default function ConfigProfile({ user }: { user: User }) {
|
|
18
|
-
const { locale } = useLocaleContext();
|
|
17
|
+
export default function ConfigProfile({ user, onSave }: { user: User; onSave: (type: 'profile') => void }) {
|
|
18
|
+
const { locale, changeLocale } = useLocaleContext();
|
|
19
19
|
const t = useMemoizedFn((key, data = {}) => {
|
|
20
20
|
return translate(translations, key, locale, 'en', data);
|
|
21
21
|
});
|
|
@@ -34,6 +34,8 @@ export default function ConfigProfile({ user }: { user: User }) {
|
|
|
34
34
|
}),
|
|
35
35
|
sleep(350),
|
|
36
36
|
]);
|
|
37
|
+
await onSave('profile');
|
|
38
|
+
changeLocale(value);
|
|
37
39
|
currentState.locale = value;
|
|
38
40
|
} finally {
|
|
39
41
|
currentState.loading = false;
|
|
@@ -17,7 +17,13 @@ type PrivacyConfig = {
|
|
|
17
17
|
value: boolean;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
export default function Privacy({
|
|
20
|
+
export default function Privacy({
|
|
21
|
+
configList,
|
|
22
|
+
onSave,
|
|
23
|
+
}: {
|
|
24
|
+
configList: PrivacyConfig[];
|
|
25
|
+
onSave: (type: 'privacy') => void;
|
|
26
|
+
}) {
|
|
21
27
|
const [dataList, setDataList] = useState(configList);
|
|
22
28
|
const { locale } = useLocaleContext();
|
|
23
29
|
const t = useMemoizedFn((key, data = {}) => {
|
|
@@ -38,7 +44,7 @@ export default function Privacy({ configList, onSave }: { configList: PrivacyCon
|
|
|
38
44
|
})
|
|
39
45
|
);
|
|
40
46
|
Toast.success(t('saveSuccess'));
|
|
41
|
-
onSave();
|
|
47
|
+
onSave('privacy');
|
|
42
48
|
} catch (err) {
|
|
43
49
|
Toast.error(formatAxiosError(err as AxiosError));
|
|
44
50
|
}
|
|
@@ -79,29 +85,31 @@ export default function Privacy({ configList, onSave }: { configList: PrivacyCon
|
|
|
79
85
|
},
|
|
80
86
|
},
|
|
81
87
|
}}>
|
|
82
|
-
{dataList.map((item) =>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
88
|
+
{dataList.map((item) => {
|
|
89
|
+
return (
|
|
90
|
+
<Switch
|
|
91
|
+
key={item.key}
|
|
92
|
+
checked={!item.value}
|
|
93
|
+
labelProps={{
|
|
94
|
+
label: (
|
|
95
|
+
<Typography
|
|
96
|
+
color="text.primary"
|
|
97
|
+
sx={{
|
|
98
|
+
fontSize: 14,
|
|
99
|
+
display: 'flex',
|
|
100
|
+
flexFlow: 'wrap',
|
|
101
|
+
columnGap: 1,
|
|
102
|
+
flex: 1,
|
|
103
|
+
}}>
|
|
104
|
+
{t('toPublic', { name: item.name })}
|
|
105
|
+
</Typography>
|
|
106
|
+
),
|
|
107
|
+
}}
|
|
108
|
+
size="small"
|
|
109
|
+
onChange={(event: ChangeEvent<HTMLInputElement>) => handleChangeSwitch(item.key, event.target.checked)}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
})}
|
|
105
113
|
</Box>
|
|
106
114
|
);
|
|
107
115
|
}
|
|
@@ -22,7 +22,7 @@ export default function Settings({
|
|
|
22
22
|
...rest
|
|
23
23
|
}: {
|
|
24
24
|
user: User;
|
|
25
|
-
onSave: () => void;
|
|
25
|
+
onSave: (type: 'privacy' | 'profile') => void;
|
|
26
26
|
settings: {
|
|
27
27
|
userCenterTabs: UserCenterTab[];
|
|
28
28
|
};
|
|
@@ -46,7 +46,7 @@ export default function Settings({
|
|
|
46
46
|
{
|
|
47
47
|
label: t('commonSetting.title'),
|
|
48
48
|
value: 'common',
|
|
49
|
-
content: <ConfigProfile user={user} />,
|
|
49
|
+
content: <ConfigProfile user={user} onSave={onSave} />,
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
label: t('notificationManagement'),
|
|
@@ -180,13 +180,19 @@ export default function UserCenter({
|
|
|
180
180
|
<Settings
|
|
181
181
|
user={userState.data as User}
|
|
182
182
|
settings={{ userCenterTabs }}
|
|
183
|
-
onSave={async () => {
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
onSave={async (type: 'privacy' | 'profile') => {
|
|
184
|
+
if (type === 'privacy') {
|
|
185
|
+
await privacyState.runAsync();
|
|
186
|
+
return privacyState.data;
|
|
187
|
+
}
|
|
188
|
+
if (type === 'profile') {
|
|
189
|
+
await session.refresh();
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
186
192
|
}}
|
|
187
193
|
/>
|
|
188
194
|
);
|
|
189
|
-
}, [userState.data]);
|
|
195
|
+
}, [userState.data, userCenterTabs, privacyState.data, privacyState.runAsync]);
|
|
190
196
|
|
|
191
197
|
const openSettings = useMemoizedFn(() => {
|
|
192
198
|
confirmApi.open({
|
|
@@ -383,17 +389,15 @@ export default function UserCenter({
|
|
|
383
389
|
}}
|
|
384
390
|
/>
|
|
385
391
|
|
|
392
|
+
<Box>
|
|
393
|
+
<Typography sx={{ fontWeight: 600, mb: 1.5 }}>{isMyself ? t('myInfo') : t('hisInfo')}</Typography>
|
|
394
|
+
<UserInfo user={userState.data as User} isMySelf={isMyself} />
|
|
395
|
+
</Box>
|
|
386
396
|
{isMyself ? (
|
|
387
|
-
|
|
388
|
-
<
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
</Box>
|
|
392
|
-
<Box>
|
|
393
|
-
<Typography sx={{ fontWeight: 600, mb: 1.5 }}>{t('passport')}</Typography>
|
|
394
|
-
<Passport user={userState.data as User} />
|
|
395
|
-
</Box>
|
|
396
|
-
</>
|
|
397
|
+
<Box>
|
|
398
|
+
<Typography sx={{ fontWeight: 600, mb: 1.5 }}>{t('passport')}</Typography>
|
|
399
|
+
<Passport user={userState.data as User} />
|
|
400
|
+
</Box>
|
|
397
401
|
) : null}
|
|
398
402
|
</Box>
|
|
399
403
|
</Box>
|
|
@@ -18,9 +18,11 @@ import type { User } from '../../../@types';
|
|
|
18
18
|
|
|
19
19
|
export default function UserInfo({
|
|
20
20
|
user,
|
|
21
|
+
isMySelf = false,
|
|
21
22
|
...rest
|
|
22
23
|
}: {
|
|
23
24
|
user: User;
|
|
25
|
+
isMySelf: boolean;
|
|
24
26
|
} & BoxProps) {
|
|
25
27
|
const { locale } = useLocaleContext();
|
|
26
28
|
const t = useMemoizedFn((key, data = {}) => {
|
|
@@ -32,35 +34,41 @@ export default function UserInfo({
|
|
|
32
34
|
}, [user?.sourceProvider]);
|
|
33
35
|
|
|
34
36
|
const userInfoListData = [];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
if (isMySelf) {
|
|
38
|
+
userInfoListData.push({
|
|
39
|
+
icon: <Icon fontSize={16} icon={MailOutlineRoundedIcon} />,
|
|
40
|
+
title: t('email'),
|
|
41
|
+
content: user?.email || t('emptyField'),
|
|
42
|
+
});
|
|
43
|
+
userInfoListData.push({
|
|
44
|
+
icon: <Icon fontSize={16} icon={ScheduleOutlineRoundedIcon} />,
|
|
45
|
+
title: t('lastLoginAt'),
|
|
46
|
+
content: user?.lastLoginAt ? (
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
<RelativeTime locale={locale} value={user?.lastLoginAt} relativeRange={3 * 86400 * 1000} />
|
|
49
|
+
) : (
|
|
50
|
+
t('unknown')
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
userInfoListData.push({
|
|
54
|
+
icon: <Icon fontSize={16} icon={SettingsInputAntennaRoundedIcon} />,
|
|
55
|
+
title: t('lastLoginIp'),
|
|
56
|
+
content: user?.lastLoginIp || t('unknown'),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
54
59
|
userInfoListData.push({
|
|
55
60
|
icon: <Icon fontSize={16} icon={MoreTimeRoundedIcon} />,
|
|
56
61
|
title: t('createdAt'),
|
|
62
|
+
// @ts-ignore
|
|
57
63
|
content: user?.createdAt ? <RelativeTime locale={locale} value={user?.createdAt} /> : t('unknown'),
|
|
58
64
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
if (isMySelf) {
|
|
66
|
+
userInfoListData.push({
|
|
67
|
+
icon: <Icon fontSize={16} icon={CaptivePortalRoundedIcon} />,
|
|
68
|
+
title: t('registerFrom'),
|
|
69
|
+
content: readableProvider,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
64
72
|
|
|
65
73
|
return (
|
|
66
74
|
<Box
|
|
@@ -8,7 +8,7 @@ export const translations = {
|
|
|
8
8
|
lastLogin: '上次登录',
|
|
9
9
|
lastLoginAt: '上次登录时间',
|
|
10
10
|
lastLoginIp: '上次登录地址',
|
|
11
|
-
createdAt: '
|
|
11
|
+
createdAt: '加入时间',
|
|
12
12
|
registerFrom: '注册来源',
|
|
13
13
|
unknown: '未知',
|
|
14
14
|
walletNotification: '钱包通知',
|
|
@@ -42,6 +42,7 @@ export const translations = {
|
|
|
42
42
|
switchProfile: '切换',
|
|
43
43
|
userInfo: '个人信息',
|
|
44
44
|
myInfo: '我的信息',
|
|
45
|
+
hisInfo: 'TA 的信息',
|
|
45
46
|
loginNow: '立即登录',
|
|
46
47
|
viewAfterLogin: '登录后才可以查看',
|
|
47
48
|
sessionManagement: '会话管理',
|
|
@@ -94,7 +95,7 @@ export const translations = {
|
|
|
94
95
|
lastLogin: 'Last Login & IP',
|
|
95
96
|
lastLoginAt: 'Last Login',
|
|
96
97
|
lastLoginIp: 'Last IP',
|
|
97
|
-
createdAt: '
|
|
98
|
+
createdAt: 'Member Since',
|
|
98
99
|
registerFrom: 'Register From',
|
|
99
100
|
unknown: 'Unknown',
|
|
100
101
|
walletNotification: 'DID Wallet notification',
|
|
@@ -128,6 +129,7 @@ export const translations = {
|
|
|
128
129
|
switchProfile: 'Switch',
|
|
129
130
|
userInfo: 'User Info',
|
|
130
131
|
myInfo: 'My Info',
|
|
132
|
+
hisInfo: 'His/Her Info',
|
|
131
133
|
loginNow: 'Login',
|
|
132
134
|
viewAfterLogin: 'View after login',
|
|
133
135
|
sessionManagement: 'Session Management',
|