@blocklet/ui-react 2.11.43 → 2.11.45
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/Dashboard/index.js +3 -1
- package/lib/Header/index.js +3 -1
- package/lib/common/domain-warning.d.ts +17 -0
- package/lib/common/domain-warning.js +157 -0
- package/lib/common/header-addons.d.ts +5 -1
- package/lib/common/header-addons.js +12 -9
- package/package.json +4 -4
- package/src/Dashboard/index.jsx +3 -1
- package/src/Header/index.tsx +3 -2
- package/src/common/domain-warning.jsx +184 -0
- package/src/common/header-addons.jsx +17 -8
package/lib/Dashboard/index.js
CHANGED
|
@@ -11,12 +11,14 @@ import { mapRecursive, flatRecursive, matchPaths } from "../utils.js";
|
|
|
11
11
|
import { publicPath, formatBlockletInfo, getLocalizedNavigation, filterNavByRole } from "../blocklets.js";
|
|
12
12
|
import HeaderAddons from "../common/header-addons.js";
|
|
13
13
|
import { useWalletHiddenTopbar } from "../common/wallet-hidden-topbar.js";
|
|
14
|
+
import useMobile from "../hooks/use-mobile.js";
|
|
14
15
|
function Dashboard({ meta, fallbackUrl, invalidPathFallback, headerAddons, sessionManagerProps, links, ...rest }) {
|
|
15
16
|
useWalletHiddenTopbar();
|
|
16
17
|
const sessionCtx = useContext(SessionContext);
|
|
17
18
|
const user = sessionCtx?.session?.user;
|
|
18
19
|
const userRole = user?.role;
|
|
19
20
|
const { locale } = useLocaleContext() || {};
|
|
21
|
+
const isMobile = useMobile();
|
|
20
22
|
const formattedBlocklet = useMemo(() => {
|
|
21
23
|
const blocklet = Object.assign({}, window.blocklet, meta);
|
|
22
24
|
try {
|
|
@@ -95,7 +97,7 @@ function Dashboard({ meta, fallbackUrl, invalidPathFallback, headerAddons, sessi
|
|
|
95
97
|
...rest,
|
|
96
98
|
headerProps: {
|
|
97
99
|
homeLink: publicPath,
|
|
98
|
-
logo: /* @__PURE__ */ jsx("img", { src: appLogoRect || appLogo, alt: "logo" }),
|
|
100
|
+
logo: /* @__PURE__ */ jsx("img", { src: isMobile ? appLogo : appLogoRect || appLogo, alt: "logo" }),
|
|
99
101
|
addons: _headerAddons,
|
|
100
102
|
...rest.headerProps
|
|
101
103
|
}
|
package/lib/Header/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { publicPath, formatBlockletInfo, getLocalizedNavigation } from "../block
|
|
|
16
16
|
import HeaderAddons from "../common/header-addons.js";
|
|
17
17
|
import { useWalletHiddenTopbar } from "../common/wallet-hidden-topbar.js";
|
|
18
18
|
import withHideWhenEmbed from "../libs/with-hide-when-embed.js";
|
|
19
|
+
import useMobile from "../hooks/use-mobile.js";
|
|
19
20
|
const parseNavigation = (navigation) => {
|
|
20
21
|
if (!navigation?.length) {
|
|
21
22
|
return { navItems: [], activeId: null };
|
|
@@ -72,6 +73,7 @@ function Header({
|
|
|
72
73
|
return blocklet;
|
|
73
74
|
}
|
|
74
75
|
}, [meta]);
|
|
76
|
+
const isMobileDevice = useMobile();
|
|
75
77
|
if (!formattedBlocklet.appName) {
|
|
76
78
|
return null;
|
|
77
79
|
}
|
|
@@ -88,7 +90,7 @@ function Header({
|
|
|
88
90
|
StyledUxHeader,
|
|
89
91
|
{
|
|
90
92
|
homeLink,
|
|
91
|
-
logo: /* @__PURE__ */ jsx("img", { src: appLogoRect || appLogo, alt: "logo" }),
|
|
93
|
+
logo: /* @__PURE__ */ jsx("img", { src: isMobileDevice ? appLogo : appLogoRect || appLogo, alt: "logo" }),
|
|
92
94
|
addons: headerAddons,
|
|
93
95
|
...omit(rest, ["bordered"]),
|
|
94
96
|
$bordered: rest?.bordered,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare function DomainWarning({ locale, session }: {
|
|
2
|
+
locale: any;
|
|
3
|
+
session: any;
|
|
4
|
+
}): import("react").JSX.Element | null;
|
|
5
|
+
declare namespace DomainWarning {
|
|
6
|
+
namespace propTypes {
|
|
7
|
+
let locale: any;
|
|
8
|
+
let session: any;
|
|
9
|
+
}
|
|
10
|
+
namespace defaultProps {
|
|
11
|
+
let locale_1: string;
|
|
12
|
+
export { locale_1 as locale };
|
|
13
|
+
let session_1: {};
|
|
14
|
+
export { session_1 as session };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export default DomainWarning;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState, useCallback } from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
import Typography from "@mui/material/Typography";
|
|
6
|
+
import Button from "@mui/material/Button";
|
|
7
|
+
import Dialog from "@mui/material/Dialog";
|
|
8
|
+
import DialogActions from "@mui/material/DialogActions";
|
|
9
|
+
import DialogContent from "@mui/material/DialogContent";
|
|
10
|
+
import { useMemoizedFn } from "ahooks";
|
|
11
|
+
import { translate } from "@arcblock/ux/lib/Locale/util";
|
|
12
|
+
import { joinURL } from "ufo";
|
|
13
|
+
import useMobile from "../hooks/use-mobile.js";
|
|
14
|
+
const isAdmin = ["admin", "owner"];
|
|
15
|
+
const isIpEcho = (hostname) => {
|
|
16
|
+
return hostname.endsWith(".ip.abtnet.io");
|
|
17
|
+
};
|
|
18
|
+
const isDidDomain = (hostname) => {
|
|
19
|
+
return hostname.endsWith(".did.abtnet.io");
|
|
20
|
+
};
|
|
21
|
+
const translations = {
|
|
22
|
+
en: {
|
|
23
|
+
guest: {
|
|
24
|
+
title: "Notice: You are using a temporary domain",
|
|
25
|
+
description: "You are accessing this site through a temporary domain. For a better experience, please contact the site administrator to bind a custom domain. Using a custom domain not only makes access more convenient but also ensures your access is more secure."
|
|
26
|
+
},
|
|
27
|
+
owner: {
|
|
28
|
+
title: "Enhance Website Security",
|
|
29
|
+
description: "Dear administrator, we recommend binding your custom domain soon, which will:",
|
|
30
|
+
benefits1: "Automatically obtain HTTPS certificates to ensure secure data transmission",
|
|
31
|
+
benefits2: "Create an exclusive brand image and increase website credibility",
|
|
32
|
+
benefits3: "Get a shorter, more memorable access address",
|
|
33
|
+
benefits4: "Provide visitors with a more professional experience",
|
|
34
|
+
benefits5: "Domain binding takes just minutes to complete, taking your website to the next level!"
|
|
35
|
+
},
|
|
36
|
+
skip: "Remind Me Later",
|
|
37
|
+
bindDomain: "Bind Domain"
|
|
38
|
+
},
|
|
39
|
+
zh: {
|
|
40
|
+
guest: {
|
|
41
|
+
title: "\u6E29\u99A8\u63D0\u793A\uFF1A\u5F53\u524D\u4F7F\u7528\u7684\u662F\u4E34\u65F6\u57DF\u540D",
|
|
42
|
+
description: "\u60A8\u6B63\u5728\u901A\u8FC7\u4E34\u65F6\u57DF\u540D\u8BBF\u95EE\u672C\u7AD9\u70B9\u3002\u4E3A\u4E86\u83B7\u5F97\u66F4\u597D\u7684\u8BBF\u95EE\u4F53\u9A8C\uFF0C\u8BF7\u8054\u7CFB\u7AD9\u70B9\u7BA1\u7406\u5458\u7ED1\u5B9A\u81EA\u5B9A\u4E49\u57DF\u540D\u3002\u4F7F\u7528\u81EA\u5B9A\u4E49\u57DF\u540D\u4E0D\u4EC5\u8BBF\u95EE\u66F4\u4FBF\u6377\uFF0C\u8FD8\u80FD\u786E\u4FDD\u60A8\u7684\u8BBF\u95EE\u66F4\u52A0\u5B89\u5168\u3002"
|
|
43
|
+
},
|
|
44
|
+
owner: {
|
|
45
|
+
title: "\u63D0\u5347\u7F51\u7AD9\u5B89\u5168\u6027\u4E0E\u4E13\u4E1A\u5EA6",
|
|
46
|
+
description: "\u5C0A\u656C\u7684\u7BA1\u7406\u5458\uFF0C\u6211\u4EEC\u5EFA\u8BAE\u60A8\u5C3D\u5FEB\u7ED1\u5B9A\u81EA\u5B9A\u4E49\u57DF\u540D\uFF0C\u8FD9\u6837\u53EF\u4EE5\uFF1A",
|
|
47
|
+
benefits1: "\u81EA\u52A8\u83B7\u53D6 HTTPS \u8BC1\u4E66\uFF0C\u786E\u4FDD\u6570\u636E\u4F20\u8F93\u5B89\u5168",
|
|
48
|
+
benefits2: "\u6253\u9020\u4E13\u5C5E\u54C1\u724C\u5F62\u8C61\uFF0C\u63D0\u5347\u7F51\u7AD9\u53EF\u4FE1\u5EA6",
|
|
49
|
+
benefits3: "\u83B7\u5F97\u66F4\u7B80\u77ED\u3001\u6613\u8BB0\u7684\u8BBF\u95EE\u5730\u5740",
|
|
50
|
+
benefits4: "\u4E3A\u8BBF\u5BA2\u63D0\u4F9B\u66F4\u4E13\u4E1A\u7684\u8BBF\u95EE\u4F53\u9A8C",
|
|
51
|
+
benefits5: "\u53EA\u9700\u51E0\u5206\u949F\u5373\u53EF\u5B8C\u6210\u57DF\u540D\u7ED1\u5B9A\uFF0C\u8BA9\u60A8\u7684\u7F51\u7AD9\u66F4\u4E0A\u4E00\u5C42\u697C\uFF01"
|
|
52
|
+
},
|
|
53
|
+
skip: "\u7A0D\u540E\u63D0\u9192",
|
|
54
|
+
bindDomain: "\u7ED1\u5B9A\u57DF\u540D"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const ONE_MONTH = 1e3 * 60 * 60 * 24 * 30;
|
|
58
|
+
export default function DomainWarning({ locale, session }) {
|
|
59
|
+
const user = session?.user;
|
|
60
|
+
const isMobile = useMobile();
|
|
61
|
+
const [open, setOpen] = useState(() => {
|
|
62
|
+
const skip = window.localStorage.getItem("domain-warning-skip");
|
|
63
|
+
if (!skip)
|
|
64
|
+
return true;
|
|
65
|
+
const now = +/* @__PURE__ */ new Date();
|
|
66
|
+
const skipTime = +new Date(skip);
|
|
67
|
+
return now - skipTime > ONE_MONTH;
|
|
68
|
+
});
|
|
69
|
+
const t = useMemoizedFn((key, data = {}) => {
|
|
70
|
+
return translate(translations, key, locale, "en", data);
|
|
71
|
+
});
|
|
72
|
+
const host = useMemo(() => {
|
|
73
|
+
try {
|
|
74
|
+
const { hostname } = new URL(window.location.href);
|
|
75
|
+
return hostname;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
}, []);
|
|
80
|
+
const benefits = useMemo(
|
|
81
|
+
() => [
|
|
82
|
+
t("owner.benefits1"),
|
|
83
|
+
t("owner.benefits2"),
|
|
84
|
+
t("owner.benefits3"),
|
|
85
|
+
t("owner.benefits4"),
|
|
86
|
+
t("owner.benefits5")
|
|
87
|
+
],
|
|
88
|
+
[t]
|
|
89
|
+
);
|
|
90
|
+
const handleSkip = useCallback(() => {
|
|
91
|
+
window.localStorage.setItem("domain-warning-skip", (/* @__PURE__ */ new Date()).toISOString());
|
|
92
|
+
setOpen(false);
|
|
93
|
+
}, []);
|
|
94
|
+
const handleDomainConfig = useCallback(() => {
|
|
95
|
+
const adminUrl = joinURL(window.location.origin, ".well-known/service/admin/domains");
|
|
96
|
+
if (adminUrl.startsWith("http")) {
|
|
97
|
+
window.open(adminUrl, "_blank");
|
|
98
|
+
}
|
|
99
|
+
setOpen(false);
|
|
100
|
+
}, []);
|
|
101
|
+
const isOwner = user?.role && isAdmin.includes(user.role);
|
|
102
|
+
if (isMobile) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
if (!isIpEcho(host) && !isDidDomain(host)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return /* @__PURE__ */ jsxs(Dialog, { open, disableEscapeKeyDown: true, fullWidth: true, maxWidth: "sm", onClose: () => setOpen(false), children: [
|
|
109
|
+
/* @__PURE__ */ jsxs(DialogContent, { sx: { padding: "20px !important" }, children: [
|
|
110
|
+
/* @__PURE__ */ jsx(
|
|
111
|
+
Typography,
|
|
112
|
+
{
|
|
113
|
+
sx: {
|
|
114
|
+
fontSize: "20px",
|
|
115
|
+
fontWeight: "500"
|
|
116
|
+
},
|
|
117
|
+
children: isOwner ? t("owner.title") : t("guest.title")
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
/* @__PURE__ */ jsx(
|
|
121
|
+
Typography,
|
|
122
|
+
{
|
|
123
|
+
sx: {
|
|
124
|
+
marginTop: "20px",
|
|
125
|
+
fontSize: "14px",
|
|
126
|
+
color: "#9397A1"
|
|
127
|
+
},
|
|
128
|
+
children: isOwner ? t("owner.description") : t("guest.description")
|
|
129
|
+
}
|
|
130
|
+
),
|
|
131
|
+
isOwner && /* @__PURE__ */ jsx(Box, { component: "ul", children: benefits.map((benefit) => /* @__PURE__ */ jsx(
|
|
132
|
+
Typography,
|
|
133
|
+
{
|
|
134
|
+
component: "li",
|
|
135
|
+
sx: {
|
|
136
|
+
fontSize: "14px",
|
|
137
|
+
color: "#9397A1"
|
|
138
|
+
},
|
|
139
|
+
children: benefit
|
|
140
|
+
},
|
|
141
|
+
benefit
|
|
142
|
+
)) })
|
|
143
|
+
] }),
|
|
144
|
+
/* @__PURE__ */ jsxs(DialogActions, { sx: { px: "12px !important" }, children: [
|
|
145
|
+
/* @__PURE__ */ jsx(Button, { onClick: handleSkip, children: t("skip") }),
|
|
146
|
+
isOwner && /* @__PURE__ */ jsx(Button, { variant: "contained", onClick: handleDomainConfig, children: t("bindDomain") })
|
|
147
|
+
] })
|
|
148
|
+
] });
|
|
149
|
+
}
|
|
150
|
+
DomainWarning.propTypes = {
|
|
151
|
+
locale: PropTypes.string,
|
|
152
|
+
session: PropTypes.object
|
|
153
|
+
};
|
|
154
|
+
DomainWarning.defaultProps = {
|
|
155
|
+
locale: "en",
|
|
156
|
+
session: {}
|
|
157
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
declare function HeaderAddons({ formattedBlocklet, addons, sessionManagerProps }: {
|
|
1
|
+
declare function HeaderAddons({ formattedBlocklet, addons, showDomainWarning, sessionManagerProps }: {
|
|
2
2
|
formattedBlocklet: any;
|
|
3
3
|
addons: any;
|
|
4
|
+
showDomainWarning: any;
|
|
4
5
|
sessionManagerProps: any;
|
|
5
6
|
}): import("react").FunctionComponentElement<{
|
|
6
7
|
children?: import("react").ReactNode | undefined;
|
|
@@ -10,6 +11,7 @@ declare namespace HeaderAddons {
|
|
|
10
11
|
export let formattedBlocklet: any;
|
|
11
12
|
export let addons: any;
|
|
12
13
|
export { SessionManagerProps as sessionManagerProps };
|
|
14
|
+
export let showDomainWarning: any;
|
|
13
15
|
}
|
|
14
16
|
namespace defaultProps {
|
|
15
17
|
let addons_1: null;
|
|
@@ -17,6 +19,8 @@ declare namespace HeaderAddons {
|
|
|
17
19
|
export namespace sessionManagerProps {
|
|
18
20
|
let showRole: boolean;
|
|
19
21
|
}
|
|
22
|
+
let showDomainWarning_1: boolean;
|
|
23
|
+
export { showDomainWarning_1 as showDomainWarning };
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
export default HeaderAddons;
|
|
@@ -11,11 +11,12 @@ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
|
11
11
|
import { SessionManagerProps } from "../types.js";
|
|
12
12
|
import { getLocalizedNavigation, filterNavByRole } from "../blocklets.js";
|
|
13
13
|
import NotificationAddon from "./notification-addon.js";
|
|
14
|
+
import DomainWarning from "./domain-warning.js";
|
|
14
15
|
const hasNotification = () => {
|
|
15
16
|
const navigations = window?.blocklet?.navigation ?? [];
|
|
16
17
|
return !!navigations.find((n) => n.id === "/userCenter/notification");
|
|
17
18
|
};
|
|
18
|
-
export default function HeaderAddons({ formattedBlocklet, addons, sessionManagerProps }) {
|
|
19
|
+
export default function HeaderAddons({ formattedBlocklet, addons, showDomainWarning, sessionManagerProps }) {
|
|
19
20
|
const sessionCtx = useContext(SessionContext);
|
|
20
21
|
const { locale, languages } = useLocaleContext() || {};
|
|
21
22
|
const { enableConnect = true, enableLocale = true } = formattedBlocklet;
|
|
@@ -68,12 +69,12 @@ export default function HeaderAddons({ formattedBlocklet, addons, sessionManager
|
|
|
68
69
|
return addonsArray;
|
|
69
70
|
};
|
|
70
71
|
const renderedAddons = renderAddons();
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
null,
|
|
74
|
-
...
|
|
75
|
-
);
|
|
76
|
-
return
|
|
72
|
+
const nodes = Array.isArray(renderedAddons) ? renderedAddons : [renderedAddons];
|
|
73
|
+
const mergedNodes = [
|
|
74
|
+
showDomainWarning ? /* @__PURE__ */ jsx(DomainWarning, { session: sessionCtx?.session, locale }) : null,
|
|
75
|
+
...nodes
|
|
76
|
+
].filter(Boolean);
|
|
77
|
+
return createElement(Fragment, null, ...mergedNodes);
|
|
77
78
|
}
|
|
78
79
|
HeaderAddons.propTypes = {
|
|
79
80
|
formattedBlocklet: PropTypes.object.isRequired,
|
|
@@ -81,11 +82,13 @@ HeaderAddons.propTypes = {
|
|
|
81
82
|
// - PropTypes.func: 可以把自定义 addons 插在 session-manager 或 locale-selector (如果存在的话) 前/中/后
|
|
82
83
|
// - PropTypes.node: 将 addons 原样传给 UX Header 组件
|
|
83
84
|
addons: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
|
|
84
|
-
sessionManagerProps: SessionManagerProps
|
|
85
|
+
sessionManagerProps: SessionManagerProps,
|
|
86
|
+
showDomainWarning: PropTypes.bool
|
|
85
87
|
};
|
|
86
88
|
HeaderAddons.defaultProps = {
|
|
87
89
|
addons: null,
|
|
88
90
|
sessionManagerProps: {
|
|
89
91
|
showRole: true
|
|
90
|
-
}
|
|
92
|
+
},
|
|
93
|
+
showDomainWarning: true
|
|
91
94
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/ui-react",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.45",
|
|
4
4
|
"description": "Some useful front-end web components that can be used in Blocklets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@abtnode/constant": "^1.16.38",
|
|
36
|
-
"@arcblock/bridge": "^2.11.
|
|
37
|
-
"@arcblock/react-hooks": "^2.11.
|
|
36
|
+
"@arcblock/bridge": "^2.11.45",
|
|
37
|
+
"@arcblock/react-hooks": "^2.11.45",
|
|
38
38
|
"@arcblock/ws": "^1.19.9",
|
|
39
39
|
"@blocklet/did-space-react": "^1.0.16",
|
|
40
40
|
"@iconify-icons/logos": "^1.2.36",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"jest": "^29.7.0",
|
|
85
85
|
"unbuild": "^2.0.0"
|
|
86
86
|
},
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "cefa442b421f25087eb67d982f2751d0c12e6020"
|
|
88
88
|
}
|
package/src/Dashboard/index.jsx
CHANGED
|
@@ -12,6 +12,7 @@ import { mapRecursive, flatRecursive, matchPaths } from '../utils';
|
|
|
12
12
|
import { publicPath, formatBlockletInfo, getLocalizedNavigation, filterNavByRole } from '../blocklets';
|
|
13
13
|
import HeaderAddons from '../common/header-addons';
|
|
14
14
|
import { useWalletHiddenTopbar } from '../common/wallet-hidden-topbar';
|
|
15
|
+
import useMobile from '../hooks/use-mobile';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* 专门用于 (composable) blocklet 的 Dashboard 组件, 解析 blocklet meta 中 section 为 dashboard 的 navigation 数据, 渲染一个 UX Dashboard
|
|
@@ -23,6 +24,7 @@ function Dashboard({ meta, fallbackUrl, invalidPathFallback, headerAddons, sessi
|
|
|
23
24
|
const user = sessionCtx?.session?.user;
|
|
24
25
|
const userRole = user?.role;
|
|
25
26
|
const { locale } = useLocaleContext() || {};
|
|
27
|
+
const isMobile = useMobile();
|
|
26
28
|
const formattedBlocklet = useMemo(() => {
|
|
27
29
|
const blocklet = Object.assign({}, window.blocklet, meta);
|
|
28
30
|
try {
|
|
@@ -121,7 +123,7 @@ function Dashboard({ meta, fallbackUrl, invalidPathFallback, headerAddons, sessi
|
|
|
121
123
|
{...rest}
|
|
122
124
|
headerProps={{
|
|
123
125
|
homeLink: publicPath,
|
|
124
|
-
logo: <img src={appLogoRect || appLogo} alt="logo" />,
|
|
126
|
+
logo: <img src={isMobile ? appLogo : appLogoRect || appLogo} alt="logo" />,
|
|
125
127
|
addons: _headerAddons,
|
|
126
128
|
...rest.headerProps,
|
|
127
129
|
}}
|
package/src/Header/index.tsx
CHANGED
|
@@ -18,6 +18,7 @@ import HeaderAddons from '../common/header-addons';
|
|
|
18
18
|
import { useWalletHiddenTopbar } from '../common/wallet-hidden-topbar';
|
|
19
19
|
import { BlockletMetaProps, SessionManagerProps } from '../@types';
|
|
20
20
|
import withHideWhenEmbed from '../libs/with-hide-when-embed';
|
|
21
|
+
import useMobile from '../hooks/use-mobile';
|
|
21
22
|
|
|
22
23
|
// blocklet meta 中的 navigation 数据 => NavMenu 组件的 items
|
|
23
24
|
const parseNavigation = (navigation: any) => {
|
|
@@ -104,12 +105,12 @@ function Header({
|
|
|
104
105
|
return blocklet;
|
|
105
106
|
}
|
|
106
107
|
}, [meta]);
|
|
108
|
+
const isMobileDevice = useMobile();
|
|
107
109
|
|
|
108
110
|
if (!formattedBlocklet.appName) {
|
|
109
111
|
return null;
|
|
110
112
|
}
|
|
111
113
|
const { appLogo, appLogoRect, theme } = formattedBlocklet;
|
|
112
|
-
|
|
113
114
|
const navigation = getLocalizedNavigation(formattedBlocklet?.navigation?.header, locale);
|
|
114
115
|
const parsedNavigation = parseNavigation(navigation);
|
|
115
116
|
const { navItems, activeId } = parsedNavigation;
|
|
@@ -129,7 +130,7 @@ function Header({
|
|
|
129
130
|
<StyledUxHeader
|
|
130
131
|
// @ts-ignore
|
|
131
132
|
homeLink={homeLink}
|
|
132
|
-
logo={<img src={appLogoRect || appLogo} alt="logo" />}
|
|
133
|
+
logo={<img src={isMobileDevice ? appLogo : appLogoRect || appLogo} alt="logo" />}
|
|
133
134
|
addons={headerAddons}
|
|
134
135
|
{...omit(rest, ['bordered'])}
|
|
135
136
|
$bordered={rest?.bordered}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { useMemo, useState, useCallback } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import Box from '@mui/material/Box';
|
|
5
|
+
import Typography from '@mui/material/Typography';
|
|
6
|
+
import Button from '@mui/material/Button';
|
|
7
|
+
import Dialog from '@mui/material/Dialog';
|
|
8
|
+
import DialogActions from '@mui/material/DialogActions';
|
|
9
|
+
import DialogContent from '@mui/material/DialogContent';
|
|
10
|
+
|
|
11
|
+
import { useMemoizedFn } from 'ahooks';
|
|
12
|
+
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
13
|
+
import { joinURL } from 'ufo';
|
|
14
|
+
|
|
15
|
+
import useMobile from '../hooks/use-mobile';
|
|
16
|
+
|
|
17
|
+
const isAdmin = ['admin', 'owner'];
|
|
18
|
+
|
|
19
|
+
const isIpEcho = (hostname) => {
|
|
20
|
+
return hostname.endsWith('.ip.abtnet.io');
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const isDidDomain = (hostname) => {
|
|
24
|
+
return hostname.endsWith('.did.abtnet.io');
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const translations = {
|
|
28
|
+
en: {
|
|
29
|
+
guest: {
|
|
30
|
+
title: 'Notice: You are using a temporary domain',
|
|
31
|
+
description:
|
|
32
|
+
'You are accessing this site through a temporary domain. For a better experience, please contact the site administrator to bind a custom domain. Using a custom domain not only makes access more convenient but also ensures your access is more secure.',
|
|
33
|
+
},
|
|
34
|
+
owner: {
|
|
35
|
+
title: 'Enhance Website Security',
|
|
36
|
+
description: 'Dear administrator, we recommend binding your custom domain soon, which will:',
|
|
37
|
+
benefits1: 'Automatically obtain HTTPS certificates to ensure secure data transmission',
|
|
38
|
+
benefits2: 'Create an exclusive brand image and increase website credibility',
|
|
39
|
+
benefits3: 'Get a shorter, more memorable access address',
|
|
40
|
+
benefits4: 'Provide visitors with a more professional experience',
|
|
41
|
+
benefits5: 'Domain binding takes just minutes to complete, taking your website to the next level!',
|
|
42
|
+
},
|
|
43
|
+
skip: 'Remind Me Later',
|
|
44
|
+
bindDomain: 'Bind Domain',
|
|
45
|
+
},
|
|
46
|
+
zh: {
|
|
47
|
+
guest: {
|
|
48
|
+
title: '温馨提示:当前使用的是临时域名',
|
|
49
|
+
description:
|
|
50
|
+
'您正在通过临时域名访问本站点。为了获得更好的访问体验,请联系站点管理员绑定自定义域名。使用自定义域名不仅访问更便捷,还能确保您的访问更加安全。',
|
|
51
|
+
},
|
|
52
|
+
owner: {
|
|
53
|
+
title: '提升网站安全性与专业度',
|
|
54
|
+
description: '尊敬的管理员,我们建议您尽快绑定自定义域名,这样可以:',
|
|
55
|
+
benefits1: '自动获取 HTTPS 证书,确保数据传输安全',
|
|
56
|
+
benefits2: '打造专属品牌形象,提升网站可信度',
|
|
57
|
+
benefits3: '获得更简短、易记的访问地址',
|
|
58
|
+
benefits4: '为访客提供更专业的访问体验',
|
|
59
|
+
benefits5: '只需几分钟即可完成域名绑定,让您的网站更上一层楼!',
|
|
60
|
+
},
|
|
61
|
+
skip: '稍后提醒',
|
|
62
|
+
bindDomain: '绑定域名',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const ONE_MONTH = 1000 * 60 * 60 * 24 * 30;
|
|
67
|
+
|
|
68
|
+
export default function DomainWarning({ locale, session }) {
|
|
69
|
+
const user = session?.user;
|
|
70
|
+
const isMobile = useMobile();
|
|
71
|
+
|
|
72
|
+
const [open, setOpen] = useState(() => {
|
|
73
|
+
const skip = window.localStorage.getItem('domain-warning-skip');
|
|
74
|
+
if (!skip) return true;
|
|
75
|
+
|
|
76
|
+
const now = +new Date();
|
|
77
|
+
const skipTime = +new Date(skip);
|
|
78
|
+
return now - skipTime > ONE_MONTH;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const t = useMemoizedFn((key, data = {}) => {
|
|
82
|
+
return translate(translations, key, locale, 'en', data);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const host = useMemo(() => {
|
|
86
|
+
try {
|
|
87
|
+
const { hostname } = new URL(window.location.href);
|
|
88
|
+
return hostname;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const benefits = useMemo(
|
|
95
|
+
() => [
|
|
96
|
+
t('owner.benefits1'),
|
|
97
|
+
t('owner.benefits2'),
|
|
98
|
+
t('owner.benefits3'),
|
|
99
|
+
t('owner.benefits4'),
|
|
100
|
+
t('owner.benefits5'),
|
|
101
|
+
],
|
|
102
|
+
[t]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const handleSkip = useCallback(() => {
|
|
106
|
+
window.localStorage.setItem('domain-warning-skip', new Date().toISOString());
|
|
107
|
+
setOpen(false);
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
const handleDomainConfig = useCallback(() => {
|
|
111
|
+
const adminUrl = joinURL(window.location.origin, '.well-known/service/admin/domains');
|
|
112
|
+
if (adminUrl.startsWith('http')) {
|
|
113
|
+
window.open(adminUrl, '_blank');
|
|
114
|
+
}
|
|
115
|
+
setOpen(false);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
const isOwner = user?.role && isAdmin.includes(user.role);
|
|
119
|
+
|
|
120
|
+
if (isMobile) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!isIpEcho(host) && !isDidDomain(host)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<Dialog open={open} disableEscapeKeyDown fullWidth maxWidth="sm" onClose={() => setOpen(false)}>
|
|
130
|
+
<DialogContent sx={{ padding: '20px !important' }}>
|
|
131
|
+
<Typography
|
|
132
|
+
sx={{
|
|
133
|
+
fontSize: '20px',
|
|
134
|
+
fontWeight: '500',
|
|
135
|
+
}}>
|
|
136
|
+
{isOwner ? t('owner.title') : t('guest.title')}
|
|
137
|
+
</Typography>
|
|
138
|
+
<Typography
|
|
139
|
+
sx={{
|
|
140
|
+
marginTop: '20px',
|
|
141
|
+
fontSize: '14px',
|
|
142
|
+
color: '#9397A1',
|
|
143
|
+
}}>
|
|
144
|
+
{isOwner ? t('owner.description') : t('guest.description')}
|
|
145
|
+
</Typography>
|
|
146
|
+
|
|
147
|
+
{isOwner && (
|
|
148
|
+
<Box component="ul">
|
|
149
|
+
{benefits.map((benefit) => (
|
|
150
|
+
<Typography
|
|
151
|
+
component="li"
|
|
152
|
+
key={benefit}
|
|
153
|
+
sx={{
|
|
154
|
+
fontSize: '14px',
|
|
155
|
+
color: '#9397A1',
|
|
156
|
+
}}>
|
|
157
|
+
{benefit}
|
|
158
|
+
</Typography>
|
|
159
|
+
))}
|
|
160
|
+
</Box>
|
|
161
|
+
)}
|
|
162
|
+
</DialogContent>
|
|
163
|
+
<DialogActions sx={{ px: '12px !important' }}>
|
|
164
|
+
<Button onClick={handleSkip}>{t('skip')}</Button>
|
|
165
|
+
|
|
166
|
+
{isOwner && (
|
|
167
|
+
<Button variant="contained" onClick={handleDomainConfig}>
|
|
168
|
+
{t('bindDomain')}
|
|
169
|
+
</Button>
|
|
170
|
+
)}
|
|
171
|
+
</DialogActions>
|
|
172
|
+
</Dialog>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
DomainWarning.propTypes = {
|
|
177
|
+
locale: PropTypes.string,
|
|
178
|
+
session: PropTypes.object,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
DomainWarning.defaultProps = {
|
|
182
|
+
locale: 'en',
|
|
183
|
+
session: {},
|
|
184
|
+
};
|
|
@@ -12,6 +12,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
12
12
|
import { SessionManagerProps } from '../types';
|
|
13
13
|
import { getLocalizedNavigation, filterNavByRole } from '../blocklets';
|
|
14
14
|
import NotificationAddon from './notification-addon';
|
|
15
|
+
import DomainWarning from './domain-warning';
|
|
15
16
|
|
|
16
17
|
const hasNotification = () => {
|
|
17
18
|
const navigations = window?.blocklet?.navigation ?? [];
|
|
@@ -19,7 +20,7 @@ const hasNotification = () => {
|
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
// eslint-disable-next-line no-shadow
|
|
22
|
-
export default function HeaderAddons({ formattedBlocklet, addons, sessionManagerProps }) {
|
|
23
|
+
export default function HeaderAddons({ formattedBlocklet, addons, showDomainWarning, sessionManagerProps }) {
|
|
23
24
|
const sessionCtx = useContext(SessionContext);
|
|
24
25
|
const { locale, languages } = useLocaleContext() || {};
|
|
25
26
|
const { enableConnect = true, enableLocale = true } = formattedBlocklet;
|
|
@@ -38,10 +39,12 @@ export default function HeaderAddons({ formattedBlocklet, addons, sessionManager
|
|
|
38
39
|
if (hasNotification()) {
|
|
39
40
|
addonsArray.push(<NotificationAddon key="notification-addon" session={sessionCtx.session} />);
|
|
40
41
|
}
|
|
42
|
+
|
|
41
43
|
// 启用了多语言,且检测到了 locale context,且有多种语言可以切换
|
|
42
44
|
if (enableLocale && locale && languages.length > 1) {
|
|
43
45
|
addonsArray.push(<LocaleSelector key="locale-selector" showText={false} />);
|
|
44
46
|
}
|
|
47
|
+
|
|
45
48
|
// 启用了连接钱包并且检测到了 session context
|
|
46
49
|
if (enableConnect && sessionCtx) {
|
|
47
50
|
const menu = [];
|
|
@@ -57,7 +60,9 @@ export default function HeaderAddons({ formattedBlocklet, addons, sessionManager
|
|
|
57
60
|
});
|
|
58
61
|
});
|
|
59
62
|
}
|
|
63
|
+
|
|
60
64
|
addonsArray.push(<SessionBlocklet key="session-blocklet" session={sessionCtx.session} locale={locale} />);
|
|
65
|
+
|
|
61
66
|
addonsArray.push(
|
|
62
67
|
<SessionUser
|
|
63
68
|
key="session-user"
|
|
@@ -69,20 +74,22 @@ export default function HeaderAddons({ formattedBlocklet, addons, sessionManager
|
|
|
69
74
|
/>
|
|
70
75
|
);
|
|
71
76
|
}
|
|
72
|
-
|
|
77
|
+
|
|
73
78
|
if (typeof addons === 'function') {
|
|
74
79
|
addonsArray = addons(addonsArray) || [];
|
|
75
80
|
}
|
|
81
|
+
|
|
76
82
|
return addonsArray;
|
|
77
83
|
};
|
|
78
84
|
|
|
79
85
|
const renderedAddons = renderAddons();
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
null,
|
|
83
|
-
...
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
+
const nodes = Array.isArray(renderedAddons) ? renderedAddons : [renderedAddons];
|
|
87
|
+
const mergedNodes = [
|
|
88
|
+
showDomainWarning ? <DomainWarning session={sessionCtx?.session} locale={locale} /> : null,
|
|
89
|
+
...nodes,
|
|
90
|
+
].filter(Boolean);
|
|
91
|
+
|
|
92
|
+
return createElement(Fragment, null, ...mergedNodes);
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
HeaderAddons.propTypes = {
|
|
@@ -92,6 +99,7 @@ HeaderAddons.propTypes = {
|
|
|
92
99
|
// - PropTypes.node: 将 addons 原样传给 UX Header 组件
|
|
93
100
|
addons: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
|
|
94
101
|
sessionManagerProps: SessionManagerProps,
|
|
102
|
+
showDomainWarning: PropTypes.bool,
|
|
95
103
|
};
|
|
96
104
|
|
|
97
105
|
HeaderAddons.defaultProps = {
|
|
@@ -99,4 +107,5 @@ HeaderAddons.defaultProps = {
|
|
|
99
107
|
sessionManagerProps: {
|
|
100
108
|
showRole: true,
|
|
101
109
|
},
|
|
110
|
+
showDomainWarning: true,
|
|
102
111
|
};
|