@arcblock/ux 2.13.56 → 2.13.58

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.
@@ -0,0 +1,14 @@
1
+ export type RequestAppInfo = {
2
+ appLogo: string;
3
+ appName: string;
4
+ appUrl: string;
5
+ };
6
+ export type CurrentAppInfo = {
7
+ appLogo: string;
8
+ appName: string;
9
+ appUrl: string;
10
+ };
11
+ export default function AuthApps({ requestAppInfo, currentAppInfo, }: {
12
+ requestAppInfo: RequestAppInfo;
13
+ currentAppInfo: CurrentAppInfo;
14
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Typography } from '@mui/material';
3
+ import Img from '../../Img';
4
+ export default function AuthApps({
5
+ requestAppInfo,
6
+ currentAppInfo
7
+ }) {
8
+ return /*#__PURE__*/_jsxs(Box, {
9
+ sx: {
10
+ textAlign: 'center',
11
+ display: 'flex',
12
+ flexDirection: 'column',
13
+ alignItems: 'center',
14
+ gap: 2
15
+ },
16
+ children: [/*#__PURE__*/_jsxs(Box, {
17
+ sx: {
18
+ display: 'flex',
19
+ alignItems: 'center',
20
+ justifyContent: 'center'
21
+ },
22
+ children: [/*#__PURE__*/_jsx(Img, {
23
+ src: currentAppInfo.appLogo,
24
+ alt: "Server",
25
+ width: 48,
26
+ height: 48
27
+ }), /*#__PURE__*/_jsxs(Box, {
28
+ sx: {
29
+ mx: 2,
30
+ display: 'flex',
31
+ alignItems: 'center',
32
+ '& .dot': {
33
+ width: 8,
34
+ height: 8,
35
+ borderRadius: '50%',
36
+ bgcolor: 'divider',
37
+ mx: 0.5
38
+ }
39
+ },
40
+ children: [/*#__PURE__*/_jsx(Box, {
41
+ className: "dot"
42
+ }), /*#__PURE__*/_jsx(Box, {
43
+ className: "dot"
44
+ }), /*#__PURE__*/_jsx(Box, {
45
+ className: "dot"
46
+ })]
47
+ }), /*#__PURE__*/_jsx(Img, {
48
+ src: requestAppInfo.appLogo,
49
+ alt: requestAppInfo.appName,
50
+ width: 48,
51
+ height: 48
52
+ })]
53
+ }), /*#__PURE__*/_jsxs(Typography, {
54
+ sx: {
55
+ mb: 1,
56
+ fontSize: '1.2rem'
57
+ },
58
+ children: ["Authorize", ' ', /*#__PURE__*/_jsx(Box, {
59
+ component: "span",
60
+ sx: {
61
+ color: 'primary.main'
62
+ },
63
+ children: requestAppInfo.appName
64
+ })]
65
+ })]
66
+ });
67
+ }
@@ -0,0 +1,24 @@
1
+ import type { UserPublicInfo } from '@blocklet/js-sdk';
2
+ import { type RequestAppInfo, type CurrentAppInfo } from './auth-apps-info';
3
+ /**
4
+ * @example app/connect-to-did-space?appPid=zNKp8tdZUanWKnvhwZWvF2hKRutwM9ykLaY8&appDid=zNKp8tdZUanWKnvhwZWvF2hKRutwM9ykLaY8&appName=233&appDescription=456&appUrl=https://bbqaw5mgxc6fnihrwqcejcxvukkdgkk4anwxwk5msvm.did.abtnet.io/zh
5
+ */
6
+ export default function AuthApps({ session, requestAppInfo, currentAppInfo, children, userInfo, hideSwitchConnect, hideAuthorize, hideCancel, notThisText, authorizeText, connectText, cancelText, useAnotherText, onConnect, onAuthorize, onSwitchConnect, onCancel, }: {
7
+ session: any;
8
+ requestAppInfo: RequestAppInfo;
9
+ currentAppInfo: CurrentAppInfo;
10
+ children?: React.ReactNode;
11
+ userInfo?: UserPublicInfo;
12
+ hideSwitchConnect?: boolean;
13
+ hideAuthorize?: boolean;
14
+ hideCancel?: boolean;
15
+ notThisText?: string;
16
+ authorizeText?: string;
17
+ connectText?: string;
18
+ cancelText?: string;
19
+ useAnotherText?: string;
20
+ onConnect?: (done: () => void) => void;
21
+ onAuthorize?: (done: () => void) => void;
22
+ onSwitchConnect?: (done: () => void) => void;
23
+ onCancel?: () => void;
24
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,191 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Button, CircularProgress, Link, Stack, Typography } from '@mui/material';
3
+ import { useMemoizedFn, useReactive } from 'ahooks';
4
+ import noop from 'lodash/noop';
5
+ import DIDConnectContainer from '../did-connect-container';
6
+ import AuthAppsInfo from './auth-apps-info';
7
+ import Center from '../../Center';
8
+ import LandingPage from '../landing-page';
9
+ import { translate } from '../../Locale/util';
10
+ import { useLocaleContext } from '../../Locale/context';
11
+ import UserCard from '../../UserCard';
12
+ import { CardType, InfoType } from '../../UserCard/types';
13
+ const translations = {
14
+ en: {
15
+ notThis: 'Not this?',
16
+ useAnother: 'Use another',
17
+ authorize: 'Authorize',
18
+ connect: 'Connect',
19
+ cancel: 'Cancel'
20
+ },
21
+ zh: {
22
+ notThis: '不是这个?',
23
+ useAnother: '使用其他',
24
+ authorize: '授权',
25
+ connect: 'Connect',
26
+ cancel: '取消'
27
+ }
28
+ };
29
+
30
+ /**
31
+ * @example app/connect-to-did-space?appPid=zNKp8tdZUanWKnvhwZWvF2hKRutwM9ykLaY8&appDid=zNKp8tdZUanWKnvhwZWvF2hKRutwM9ykLaY8&appName=233&appDescription=456&appUrl=https://bbqaw5mgxc6fnihrwqcejcxvukkdgkk4anwxwk5msvm.did.abtnet.io/zh
32
+ */
33
+ export default function AuthApps({
34
+ session,
35
+ requestAppInfo,
36
+ currentAppInfo,
37
+ children,
38
+ userInfo,
39
+ hideSwitchConnect = false,
40
+ hideAuthorize = false,
41
+ hideCancel = false,
42
+ notThisText,
43
+ authorizeText,
44
+ connectText,
45
+ cancelText,
46
+ useAnotherText,
47
+ onConnect = noop,
48
+ onAuthorize = noop,
49
+ onSwitchConnect = noop,
50
+ onCancel = noop
51
+ }) {
52
+ const currentState = useReactive({
53
+ loading: false
54
+ });
55
+ const {
56
+ locale = 'en'
57
+ } = useLocaleContext() || {};
58
+ const t = useMemoizedFn((key, data = {}) => translate(translations, key, locale, 'en', data));
59
+ const handleCloseLoading = useMemoizedFn(() => {
60
+ currentState.loading = false;
61
+ });
62
+ const handleConnect = useMemoizedFn(() => {
63
+ currentState.loading = true;
64
+ onConnect?.(handleCloseLoading);
65
+ });
66
+ const handleSwitchConnect = useMemoizedFn(() => {
67
+ currentState.loading = true;
68
+ onSwitchConnect?.(handleCloseLoading);
69
+ });
70
+ const handleAuthorize = useMemoizedFn(() => {
71
+ currentState.loading = true;
72
+ onAuthorize?.(handleCloseLoading);
73
+ });
74
+ const handleCancel = useMemoizedFn(() => {
75
+ onCancel?.();
76
+ });
77
+ if (session.loading || !session.initialized) {
78
+ return /*#__PURE__*/_jsx(Center, {
79
+ children: /*#__PURE__*/_jsx(CircularProgress, {})
80
+ });
81
+ }
82
+ const user = userInfo ?? session?.user;
83
+ return /*#__PURE__*/_jsx(LandingPage, {
84
+ did: window.blocklet?.appPid,
85
+ standalone: true,
86
+ children: /*#__PURE__*/_jsx(Box, {
87
+ sx: {
88
+ width: 420,
89
+ maxWidth: '100%'
90
+ },
91
+ children: /*#__PURE__*/_jsx(DIDConnectContainer, {
92
+ hideCloseButton: true,
93
+ children: /*#__PURE__*/_jsxs(Box, {
94
+ sx: {
95
+ display: 'flex',
96
+ flexDirection: 'column',
97
+ gap: 2,
98
+ p: {
99
+ xs: 2,
100
+ lg: 3,
101
+ xl: 4
102
+ },
103
+ pt: {
104
+ xs: 3,
105
+ xl: 4
106
+ },
107
+ bgcolor: 'background.paper'
108
+ },
109
+ children: [/*#__PURE__*/_jsx(AuthAppsInfo, {
110
+ requestAppInfo: requestAppInfo,
111
+ currentAppInfo: currentAppInfo
112
+ }), user ? /*#__PURE__*/_jsxs(_Fragment, {
113
+ children: [/*#__PURE__*/_jsx(UserCard, {
114
+ user: user,
115
+ showDid: true,
116
+ cardType: CardType.Detailed,
117
+ infoType: InfoType.Minimal
118
+ }), /*#__PURE__*/_jsx(Box, {})]
119
+ }) : null, children, /*#__PURE__*/_jsx(Box, {
120
+ sx: {
121
+ border: '1px solid',
122
+ borderColor: 'divider',
123
+ borderRadius: 2,
124
+ overflow: 'hidden',
125
+ my: 1
126
+ }
127
+ }), /*#__PURE__*/_jsxs(Stack, {
128
+ direction: "row",
129
+ spacing: 2,
130
+ children: [!hideCancel ? /*#__PURE__*/_jsx(Button, {
131
+ color: "inherit",
132
+ variant: "outlined",
133
+ size: "large",
134
+ fullWidth: true,
135
+ onClick: handleCancel,
136
+ sx: {
137
+ color: 'grey.500'
138
+ },
139
+ children: cancelText || t('cancel')
140
+ }) : null, user && !hideAuthorize ? /*#__PURE__*/_jsxs(Button, {
141
+ variant: "contained",
142
+ size: "large",
143
+ fullWidth: true,
144
+ onClick: handleAuthorize,
145
+ disabled: currentState.loading,
146
+ children: [currentState.loading ? /*#__PURE__*/_jsx(CircularProgress, {
147
+ size: 20,
148
+ sx: {
149
+ mr: 1
150
+ }
151
+ }) : null, authorizeText || t('authorize')]
152
+ }) : /*#__PURE__*/_jsxs(Button, {
153
+ variant: "contained",
154
+ size: "large",
155
+ fullWidth: true,
156
+ disabled: currentState.loading,
157
+ onClick: handleConnect,
158
+ children: [currentState.loading ? /*#__PURE__*/_jsx(CircularProgress, {
159
+ size: 20,
160
+ sx: {
161
+ mr: 1
162
+ }
163
+ }) : null, connectText || t('connect')]
164
+ })]
165
+ }), hideSwitchConnect || !user ? null : /*#__PURE__*/_jsx(Box, {
166
+ sx: {
167
+ textAlign: 'center'
168
+ },
169
+ children: /*#__PURE__*/_jsxs(Typography, {
170
+ variant: "body2",
171
+ sx: {
172
+ display: 'flex',
173
+ alignItems: 'center',
174
+ justifyContent: 'center',
175
+ gap: 1
176
+ },
177
+ children: [notThisText || t('notThis'), " ", /*#__PURE__*/_jsx(Link, {
178
+ underline: "hover",
179
+ onClick: handleSwitchConnect,
180
+ sx: {
181
+ cursor: 'pointer'
182
+ },
183
+ children: useAnotherText || t('useAnother')
184
+ })]
185
+ })
186
+ })]
187
+ })
188
+ })
189
+ })
190
+ });
191
+ }
@@ -7,3 +7,5 @@ export { default as withUxTheme } from './with-ux-theme';
7
7
  export { default as DIDConnectLogo } from './did-connect-logo';
8
8
  export { default as DIDConnectContainer } from './did-connect-container';
9
9
  export { default as RequestStorageAccessApiDialog } from './request-storage-access-api-dialog';
10
+ export { default as AuthApps } from './auth-apps';
11
+ export type { RequestAppInfo, CurrentAppInfo } from './auth-apps/auth-apps-info';
@@ -6,4 +6,5 @@ export { default as withContainer } from './with-container';
6
6
  export { default as withUxTheme } from './with-ux-theme';
7
7
  export { default as DIDConnectLogo } from './did-connect-logo';
8
8
  export { default as DIDConnectContainer } from './did-connect-container';
9
- export { default as RequestStorageAccessApiDialog } from './request-storage-access-api-dialog';
9
+ export { default as RequestStorageAccessApiDialog } from './request-storage-access-api-dialog';
10
+ export { default as AuthApps } from './auth-apps';
@@ -0,0 +1,14 @@
1
+ type LandingPageProps = {
2
+ did: string;
3
+ children: React.ReactNode;
4
+ title?: any;
5
+ description?: any;
6
+ actions?: any;
7
+ logo?: any;
8
+ logoSize?: number;
9
+ poweredBy?: any;
10
+ termsOfUse?: any;
11
+ standalone?: boolean;
12
+ };
13
+ export default function LandingPage({ did, children, title, description, actions, logo, logoSize, poweredBy, termsOfUse, standalone, }: LandingPageProps): string | number | boolean | Iterable<import("react").ReactNode> | import("react/jsx-runtime").JSX.Element | null | undefined;
14
+ export {};
@@ -0,0 +1,203 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Typography, useMediaQuery } from '@mui/material';
3
+ import { isEthereumDid } from '@arcblock/did';
4
+ import { getDIDMotifInfo } from '@arcblock/did-motif';
5
+ import { useCreation } from 'ahooks';
6
+ import { useTheme } from '../Theme';
7
+ import PoweredByArcBlock from '../PoweredByArcBlock';
8
+ import Avatar from '../Avatar';
9
+ import { getDIDColor, hexToRgba } from '../Util';
10
+ import useBlockletLogo from '../hooks/use-blocklet-logo';
11
+ export default function LandingPage({
12
+ did,
13
+ children,
14
+ title,
15
+ description,
16
+ actions,
17
+ logo,
18
+ logoSize = 50,
19
+ poweredBy,
20
+ termsOfUse,
21
+ standalone = false
22
+ }) {
23
+ const blockletData = globalThis?.blocklet || globalThis?.env || {};
24
+ const {
25
+ palette
26
+ } = useTheme();
27
+ const isMd = useMediaQuery(theme => theme.breakpoints.down('lg'));
28
+ const appLogo = useBlockletLogo({
29
+ meta: blockletData
30
+ });
31
+ const currentAppColor = useCreation(() => {
32
+ const _did = blockletData.appPid;
33
+ const isEthDid = isEthereumDid(_did);
34
+ const didMotifInfo = isEthDid ? undefined : getDIDMotifInfo(_did);
35
+ if (isEthDid) {
36
+ return getDIDColor(_did);
37
+ }
38
+ return didMotifInfo.color;
39
+ }, [blockletData?.appPid]);
40
+ if (!did) {
41
+ return children;
42
+ }
43
+ if (title === undefined) {
44
+ // eslint-disable-next-line no-param-reassign
45
+ title = blockletData.appName;
46
+ }
47
+ if (description === undefined) {
48
+ // eslint-disable-next-line no-param-reassign
49
+ description = blockletData.appDescription;
50
+ }
51
+ if (logo === undefined) {
52
+ // eslint-disable-next-line no-param-reassign
53
+ logo = appLogo || '';
54
+ }
55
+ if (typeof logo === 'string') {
56
+ if (logo) {
57
+ // eslint-disable-next-line no-param-reassign
58
+ logo = /*#__PURE__*/_jsx(Box, {
59
+ sx: {
60
+ height: logoSize,
61
+ maxHeight: logoSize,
62
+ objectFit: 'contain'
63
+ },
64
+ component: "img",
65
+ src: logo,
66
+ alt: `${blockletData.appName}'s logo`
67
+ });
68
+ } else {
69
+ // eslint-disable-next-line no-param-reassign
70
+ logo = /*#__PURE__*/_jsx(Avatar, {
71
+ size: logoSize,
72
+ did: did,
73
+ src: logo,
74
+ variant: "rounded"
75
+ });
76
+ }
77
+ }
78
+ if (poweredBy === undefined) {
79
+ // eslint-disable-next-line no-param-reassign
80
+ poweredBy = /*#__PURE__*/_jsx(PoweredByArcBlock, {
81
+ linkProps: {
82
+ color: palette.text.secondary,
83
+ sx: {
84
+ '&:hover': {
85
+ textDecoration: 'underline'
86
+ }
87
+ }
88
+ },
89
+ showExtra: !isMd,
90
+ sx: {
91
+ textAlign: 'center',
92
+ fontSize: '12px',
93
+ color: 'text.secondary'
94
+ }
95
+ });
96
+ }
97
+ let content = children;
98
+ if (!children && !isMd) {
99
+ content = /*#__PURE__*/_jsx(Box, {
100
+ sx: {
101
+ filter: 'blur(180px)',
102
+ display: 'flex'
103
+ },
104
+ children: /*#__PURE__*/_jsx(Avatar, {
105
+ size: 210,
106
+ did: did,
107
+ variant: "rounded"
108
+ })
109
+ });
110
+ }
111
+ const showChildren = Boolean(children);
112
+ let showInfo = !standalone;
113
+ if (isMd) {
114
+ showInfo = !showChildren;
115
+ }
116
+ return /*#__PURE__*/_jsxs(Box, {
117
+ sx: {
118
+ width: '100vw',
119
+ minHeight: '100vh',
120
+ display: 'flex',
121
+ justifyContent: 'center',
122
+ alignItems: 'center',
123
+ px: {
124
+ xs: 2,
125
+ sm: 4,
126
+ md: 8
127
+ },
128
+ pt: {
129
+ xs: 2,
130
+ sm: 4,
131
+ md: 6
132
+ },
133
+ pb: {
134
+ xs: 5,
135
+ md: 8
136
+ },
137
+ background: `radial-gradient(circle at bottom right, ${hexToRgba(currentAppColor, 0.3)} 10%, ${palette.background.default} 50%)`,
138
+ position: 'relative'
139
+ },
140
+ children: [/*#__PURE__*/_jsxs(Box, {
141
+ sx: {
142
+ maxWidth: 1200,
143
+ display: 'flex',
144
+ alignItems: 'center',
145
+ justifyContent: [showInfo, content].filter(Boolean).length > 1 ? 'space-between' : 'center',
146
+ width: '100%',
147
+ margin: 'auto'
148
+ },
149
+ children: [showInfo ? /*#__PURE__*/_jsxs(Box, {
150
+ sx: {
151
+ display: 'flex',
152
+ flexDirection: 'column',
153
+ gap: 1.5
154
+ },
155
+ children: [/*#__PURE__*/_jsx(Box, {
156
+ sx: {
157
+ display: 'flex',
158
+ alignItems: 'center'
159
+ },
160
+ children: logo
161
+ }), /*#__PURE__*/_jsx(Typography, {
162
+ variant: "h2",
163
+ sx: {
164
+ fontWeight: 'bold',
165
+ fontSize: {
166
+ sm: '2.25rem',
167
+ md: '2.75rem',
168
+ xl: '3.5rem'
169
+ }
170
+ },
171
+ children: title
172
+ }), description ? /*#__PURE__*/_jsx(Box, {
173
+ sx: {
174
+ mb: 2,
175
+ color: 'text.secondary'
176
+ },
177
+ children: description
178
+ }) : null, actions ? /*#__PURE__*/_jsx(Box, {
179
+ sx: {
180
+ mt: 8
181
+ },
182
+ children: actions
183
+ }) : null, termsOfUse ? /*#__PURE__*/_jsx(Box, {
184
+ sx: {
185
+ mt: -1,
186
+ color: 'grey'
187
+ },
188
+ children: termsOfUse
189
+ }) : null]
190
+ }) : null, content]
191
+ }), /*#__PURE__*/_jsx(Box, {
192
+ sx: {
193
+ position: 'absolute',
194
+ zIndex: 10,
195
+ bottom: 10,
196
+ left: 10,
197
+ right: 10,
198
+ textAlign: 'center'
199
+ },
200
+ children: poweredBy
201
+ })]
202
+ });
203
+ }
@@ -166,11 +166,13 @@ function ColorSchemeProvider({
166
166
  const toggleMode = useCallback(() => {
167
167
  const newMode = mode === 'light' ? 'dark' : 'light';
168
168
  setMode(newMode);
169
+ sessionStorage.removeItem(BLOCKLET_THEME_PREFER_KEY);
169
170
  localStorage.setItem(BLOCKLET_THEME_PREFER_KEY, newMode);
170
171
  }, [mode, setMode]);
171
172
  const changeMode = useCallback(newMode => {
172
173
  if (mode !== newMode) {
173
174
  setMode(newMode);
175
+ sessionStorage.removeItem(BLOCKLET_THEME_PREFER_KEY);
174
176
  localStorage.setItem(BLOCKLET_THEME_PREFER_KEY, newMode);
175
177
  }
176
178
  }, [mode, setMode]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.13.56",
3
+ "version": "2.13.58",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -71,14 +71,14 @@
71
71
  "react": ">=18.2.0",
72
72
  "react-router-dom": ">=6.22.3"
73
73
  },
74
- "gitHead": "ebf7d863313f1ce95571b81df01182756395197e",
74
+ "gitHead": "a1251dcb82603ceef7a4a4ab58efb3f53b103a4e",
75
75
  "dependencies": {
76
76
  "@arcblock/did-motif": "^1.1.13",
77
- "@arcblock/icons": "^2.13.56",
78
- "@arcblock/nft-display": "^2.13.56",
79
- "@arcblock/react-hooks": "^2.13.56",
77
+ "@arcblock/icons": "^2.13.58",
78
+ "@arcblock/nft-display": "^2.13.58",
79
+ "@arcblock/react-hooks": "^2.13.58",
80
80
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
81
- "@blocklet/theme": "^2.13.56",
81
+ "@blocklet/theme": "^2.13.58",
82
82
  "@fontsource/roboto": "~5.1.1",
83
83
  "@fontsource/ubuntu-mono": "^5.0.18",
84
84
  "@iconify-icons/logos": "^1.2.36",
@@ -0,0 +1,75 @@
1
+ import { Box, Typography } from '@mui/material';
2
+ import Img from '../../Img';
3
+
4
+ export type RequestAppInfo = {
5
+ appLogo: string;
6
+ appName: string;
7
+ appUrl: string;
8
+ };
9
+
10
+ export type CurrentAppInfo = {
11
+ appLogo: string;
12
+ appName: string;
13
+ appUrl: string;
14
+ };
15
+
16
+ export default function AuthApps({
17
+ requestAppInfo,
18
+ currentAppInfo,
19
+ }: {
20
+ requestAppInfo: RequestAppInfo;
21
+ currentAppInfo: CurrentAppInfo;
22
+ }) {
23
+ return (
24
+ <Box
25
+ sx={{
26
+ textAlign: 'center',
27
+ display: 'flex',
28
+ flexDirection: 'column',
29
+ alignItems: 'center',
30
+ gap: 2,
31
+ }}>
32
+ <Box
33
+ sx={{
34
+ display: 'flex',
35
+ alignItems: 'center',
36
+ justifyContent: 'center',
37
+ }}>
38
+ {/* FIXME: @zhanghan 增加 hover 的效果 */}
39
+ <Img src={currentAppInfo.appLogo} alt="Server" width={48} height={48} />
40
+
41
+ <Box
42
+ sx={{
43
+ mx: 2,
44
+ display: 'flex',
45
+ alignItems: 'center',
46
+ '& .dot': {
47
+ width: 8,
48
+ height: 8,
49
+ borderRadius: '50%',
50
+ bgcolor: 'divider',
51
+ mx: 0.5,
52
+ },
53
+ }}>
54
+ <Box className="dot" />
55
+ <Box className="dot" />
56
+ <Box className="dot" />
57
+ </Box>
58
+
59
+ {/* FIXME: @zhanghan 增加 hover 的效果 */}
60
+ <Img src={requestAppInfo.appLogo} alt={requestAppInfo.appName} width={48} height={48} />
61
+ </Box>
62
+
63
+ <Typography
64
+ sx={{
65
+ mb: 1,
66
+ fontSize: '1.2rem',
67
+ }}>
68
+ Authorize{' '}
69
+ <Box component="span" sx={{ color: 'primary.main' }}>
70
+ {requestAppInfo.appName}
71
+ </Box>
72
+ </Typography>
73
+ </Box>
74
+ );
75
+ }
@@ -0,0 +1,223 @@
1
+ import { Box, Button, CircularProgress, Link, Stack, Typography } from '@mui/material';
2
+ import { useMemoizedFn, useReactive } from 'ahooks';
3
+ import noop from 'lodash/noop';
4
+ import type { UserPublicInfo } from '@blocklet/js-sdk';
5
+
6
+ import DIDConnectContainer from '../did-connect-container';
7
+ import AuthAppsInfo, { type RequestAppInfo, type CurrentAppInfo } from './auth-apps-info';
8
+ import Center from '../../Center';
9
+ import LandingPage from '../landing-page';
10
+ import { translate } from '../../Locale/util';
11
+ import { useLocaleContext } from '../../Locale/context';
12
+ import UserCard from '../../UserCard';
13
+ import { CardType, InfoType } from '../../UserCard/types';
14
+
15
+ const translations = {
16
+ en: {
17
+ notThis: 'Not this?',
18
+ useAnother: 'Use another',
19
+ authorize: 'Authorize',
20
+ connect: 'Connect',
21
+ cancel: 'Cancel',
22
+ },
23
+ zh: {
24
+ notThis: '不是这个?',
25
+ useAnother: '使用其他',
26
+ authorize: '授权',
27
+ connect: 'Connect',
28
+ cancel: '取消',
29
+ },
30
+ };
31
+
32
+ /**
33
+ * @example app/connect-to-did-space?appPid=zNKp8tdZUanWKnvhwZWvF2hKRutwM9ykLaY8&appDid=zNKp8tdZUanWKnvhwZWvF2hKRutwM9ykLaY8&appName=233&appDescription=456&appUrl=https://bbqaw5mgxc6fnihrwqcejcxvukkdgkk4anwxwk5msvm.did.abtnet.io/zh
34
+ */
35
+ export default function AuthApps({
36
+ session,
37
+ requestAppInfo,
38
+ currentAppInfo,
39
+ children,
40
+ userInfo,
41
+
42
+ hideSwitchConnect = false,
43
+ hideAuthorize = false,
44
+ hideCancel = false,
45
+
46
+ notThisText,
47
+ authorizeText,
48
+ connectText,
49
+ cancelText,
50
+ useAnotherText,
51
+
52
+ onConnect = noop,
53
+ onAuthorize = noop,
54
+ onSwitchConnect = noop,
55
+ onCancel = noop,
56
+ }: {
57
+ session: any;
58
+ requestAppInfo: RequestAppInfo;
59
+ currentAppInfo: CurrentAppInfo;
60
+ children?: React.ReactNode;
61
+ userInfo?: UserPublicInfo;
62
+
63
+ hideSwitchConnect?: boolean;
64
+ hideAuthorize?: boolean;
65
+ hideCancel?: boolean;
66
+
67
+ notThisText?: string;
68
+ authorizeText?: string;
69
+ connectText?: string;
70
+ cancelText?: string;
71
+ useAnotherText?: string;
72
+
73
+ onConnect?: (done: () => void) => void;
74
+ onAuthorize?: (done: () => void) => void;
75
+ onSwitchConnect?: (done: () => void) => void;
76
+ onCancel?: () => void;
77
+ }) {
78
+ const currentState = useReactive({
79
+ loading: false,
80
+ });
81
+ const { locale = 'en' } = useLocaleContext() || {};
82
+
83
+ const t = useMemoizedFn((key, data = {}) => translate(translations, key, locale, 'en', data));
84
+
85
+ const handleCloseLoading = useMemoizedFn(() => {
86
+ currentState.loading = false;
87
+ });
88
+ const handleConnect = useMemoizedFn(() => {
89
+ currentState.loading = true;
90
+ onConnect?.(handleCloseLoading);
91
+ });
92
+ const handleSwitchConnect = useMemoizedFn(() => {
93
+ currentState.loading = true;
94
+ onSwitchConnect?.(handleCloseLoading);
95
+ });
96
+ const handleAuthorize = useMemoizedFn(() => {
97
+ currentState.loading = true;
98
+ onAuthorize?.(handleCloseLoading);
99
+ });
100
+ const handleCancel = useMemoizedFn(() => {
101
+ onCancel?.();
102
+ });
103
+
104
+ if (session.loading || !session.initialized) {
105
+ return (
106
+ <Center>
107
+ <CircularProgress />
108
+ </Center>
109
+ );
110
+ }
111
+
112
+ const user = userInfo ?? session?.user;
113
+
114
+ return (
115
+ <LandingPage did={window.blocklet?.appPid} standalone>
116
+ <Box
117
+ sx={{
118
+ width: 420,
119
+ maxWidth: '100%',
120
+ }}>
121
+ <DIDConnectContainer hideCloseButton>
122
+ <Box
123
+ sx={{
124
+ display: 'flex',
125
+ flexDirection: 'column',
126
+ gap: 2,
127
+ p: {
128
+ xs: 2,
129
+ lg: 3,
130
+ xl: 4,
131
+ },
132
+ pt: {
133
+ xs: 3,
134
+ xl: 4,
135
+ },
136
+ bgcolor: 'background.paper',
137
+ }}>
138
+ <AuthAppsInfo requestAppInfo={requestAppInfo} currentAppInfo={currentAppInfo} />
139
+
140
+ {user ? (
141
+ <>
142
+ <UserCard user={user} showDid cardType={CardType.Detailed} infoType={InfoType.Minimal} />
143
+ <Box />
144
+ </>
145
+ ) : null}
146
+
147
+ {children}
148
+
149
+ <Box
150
+ sx={{
151
+ border: '1px solid',
152
+ borderColor: 'divider',
153
+ borderRadius: 2,
154
+ overflow: 'hidden',
155
+ my: 1,
156
+ }}
157
+ />
158
+
159
+ <Stack direction="row" spacing={2}>
160
+ {!hideCancel ? (
161
+ <Button
162
+ color="inherit"
163
+ variant="outlined"
164
+ size="large"
165
+ fullWidth
166
+ onClick={handleCancel}
167
+ sx={{
168
+ color: 'grey.500',
169
+ }}>
170
+ {cancelText || t('cancel')}
171
+ </Button>
172
+ ) : null}
173
+ {user && !hideAuthorize ? (
174
+ <Button
175
+ variant="contained"
176
+ size="large"
177
+ fullWidth
178
+ onClick={handleAuthorize}
179
+ disabled={currentState.loading}>
180
+ {currentState.loading ? <CircularProgress size={20} sx={{ mr: 1 }} /> : null}
181
+ {authorizeText || t('authorize')}
182
+ </Button>
183
+ ) : (
184
+ <Button
185
+ variant="contained"
186
+ size="large"
187
+ fullWidth
188
+ disabled={currentState.loading}
189
+ onClick={handleConnect}>
190
+ {currentState.loading ? <CircularProgress size={20} sx={{ mr: 1 }} /> : null}
191
+ {connectText || t('connect')}
192
+ </Button>
193
+ )}
194
+ </Stack>
195
+
196
+ {hideSwitchConnect || !user ? null : (
197
+ <Box sx={{ textAlign: 'center' }}>
198
+ <Typography
199
+ variant="body2"
200
+ sx={{
201
+ display: 'flex',
202
+ alignItems: 'center',
203
+ justifyContent: 'center',
204
+ gap: 1,
205
+ }}>
206
+ {notThisText || t('notThis')} {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
207
+ <Link
208
+ underline="hover"
209
+ onClick={handleSwitchConnect}
210
+ sx={{
211
+ cursor: 'pointer',
212
+ }}>
213
+ {useAnotherText || t('useAnother')}
214
+ </Link>
215
+ </Typography>
216
+ </Box>
217
+ )}
218
+ </Box>
219
+ </DIDConnectContainer>
220
+ </Box>
221
+ </LandingPage>
222
+ );
223
+ }
@@ -7,3 +7,5 @@ export { default as withUxTheme } from './with-ux-theme';
7
7
  export { default as DIDConnectLogo } from './did-connect-logo';
8
8
  export { default as DIDConnectContainer } from './did-connect-container';
9
9
  export { default as RequestStorageAccessApiDialog } from './request-storage-access-api-dialog';
10
+ export { default as AuthApps } from './auth-apps';
11
+ export type { RequestAppInfo, CurrentAppInfo } from './auth-apps/auth-apps-info';
@@ -0,0 +1,218 @@
1
+ import { Box, Theme, Typography, useMediaQuery } from '@mui/material';
2
+ import { isEthereumDid } from '@arcblock/did';
3
+ import { getDIDMotifInfo } from '@arcblock/did-motif';
4
+ import { useCreation } from 'ahooks';
5
+ import { useTheme } from '../Theme';
6
+
7
+ import PoweredByArcBlock from '../PoweredByArcBlock';
8
+ import Avatar from '../Avatar';
9
+ import { getDIDColor, hexToRgba } from '../Util';
10
+ import useBlockletLogo from '../hooks/use-blocklet-logo';
11
+
12
+ type LandingPageProps = {
13
+ did: string;
14
+ children: React.ReactNode;
15
+ title?: any;
16
+ description?: any;
17
+ actions?: any;
18
+ logo?: any;
19
+ logoSize?: number;
20
+ poweredBy?: any;
21
+ termsOfUse?: any;
22
+ standalone?: boolean;
23
+ };
24
+
25
+ export default function LandingPage({
26
+ did,
27
+ children,
28
+ title,
29
+ description,
30
+ actions,
31
+ logo,
32
+ logoSize = 50,
33
+ poweredBy,
34
+ termsOfUse,
35
+ standalone = false,
36
+ }: LandingPageProps) {
37
+ const blockletData = globalThis?.blocklet || globalThis?.env || {};
38
+ const { palette } = useTheme();
39
+ const isMd = useMediaQuery((theme: Theme) => theme.breakpoints.down('lg'));
40
+ const appLogo = useBlockletLogo({
41
+ meta: blockletData,
42
+ });
43
+ const currentAppColor = useCreation(() => {
44
+ const _did = blockletData.appPid;
45
+ const isEthDid = isEthereumDid(_did);
46
+ const didMotifInfo = isEthDid ? undefined : getDIDMotifInfo(_did);
47
+ if (isEthDid) {
48
+ return getDIDColor(_did);
49
+ }
50
+
51
+ return didMotifInfo.color;
52
+ }, [blockletData?.appPid]);
53
+
54
+ if (!did) {
55
+ return children;
56
+ }
57
+
58
+ if (title === undefined) {
59
+ // eslint-disable-next-line no-param-reassign
60
+ title = blockletData.appName;
61
+ }
62
+ if (description === undefined) {
63
+ // eslint-disable-next-line no-param-reassign
64
+ description = blockletData.appDescription;
65
+ }
66
+
67
+ if (logo === undefined) {
68
+ // eslint-disable-next-line no-param-reassign
69
+ logo = appLogo || '';
70
+ }
71
+
72
+ if (typeof logo === 'string') {
73
+ if (logo) {
74
+ // eslint-disable-next-line no-param-reassign
75
+ logo = (
76
+ <Box
77
+ sx={{
78
+ height: logoSize,
79
+ maxHeight: logoSize,
80
+ objectFit: 'contain',
81
+ }}
82
+ component="img"
83
+ src={logo}
84
+ alt={`${blockletData.appName}'s logo`}
85
+ />
86
+ );
87
+ } else {
88
+ // eslint-disable-next-line no-param-reassign
89
+ logo = <Avatar size={logoSize} did={did} src={logo} variant="rounded" />;
90
+ }
91
+ }
92
+
93
+ if (poweredBy === undefined) {
94
+ // eslint-disable-next-line no-param-reassign
95
+ poweredBy = (
96
+ <PoweredByArcBlock
97
+ linkProps={{
98
+ color: palette.text.secondary,
99
+ sx: {
100
+ '&:hover': {
101
+ textDecoration: 'underline',
102
+ },
103
+ },
104
+ }}
105
+ showExtra={!isMd}
106
+ sx={{
107
+ textAlign: 'center',
108
+ fontSize: '12px',
109
+ color: 'text.secondary',
110
+ }}
111
+ />
112
+ );
113
+ }
114
+
115
+ let content = children;
116
+ if (!children && !isMd) {
117
+ content = (
118
+ <Box
119
+ sx={{
120
+ filter: 'blur(180px)',
121
+ display: 'flex',
122
+ }}>
123
+ {/* 这里只能使用 did-avatar,用于加强页面的层次感 */}
124
+ <Avatar size={210} did={did} variant="rounded" />
125
+ </Box>
126
+ );
127
+ }
128
+
129
+ const showChildren = Boolean(children);
130
+ let showInfo = !standalone;
131
+ if (isMd) {
132
+ showInfo = !showChildren;
133
+ }
134
+
135
+ return (
136
+ <Box
137
+ sx={{
138
+ width: '100vw',
139
+ minHeight: '100vh',
140
+ display: 'flex',
141
+ justifyContent: 'center',
142
+ alignItems: 'center',
143
+ px: {
144
+ xs: 2,
145
+ sm: 4,
146
+ md: 8,
147
+ },
148
+ pt: {
149
+ xs: 2,
150
+ sm: 4,
151
+ md: 6,
152
+ },
153
+ pb: {
154
+ xs: 5,
155
+ md: 8,
156
+ },
157
+ background: `radial-gradient(circle at bottom right, ${hexToRgba(currentAppColor, 0.3)} 10%, ${palette.background.default} 50%)`,
158
+ position: 'relative',
159
+ }}>
160
+ <Box
161
+ sx={{
162
+ maxWidth: 1200,
163
+ display: 'flex',
164
+ alignItems: 'center',
165
+ justifyContent: [showInfo, content].filter(Boolean).length > 1 ? 'space-between' : 'center',
166
+ width: '100%',
167
+ margin: 'auto',
168
+ }}>
169
+ {showInfo ? (
170
+ <Box
171
+ sx={{
172
+ display: 'flex',
173
+ flexDirection: 'column',
174
+ gap: 1.5,
175
+ }}>
176
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>{logo}</Box>
177
+ <Typography
178
+ variant="h2"
179
+ sx={{
180
+ fontWeight: 'bold',
181
+ fontSize: {
182
+ sm: '2.25rem',
183
+ md: '2.75rem',
184
+ xl: '3.5rem',
185
+ },
186
+ }}>
187
+ {title}
188
+ </Typography>
189
+ {description ? (
190
+ <Box
191
+ sx={{
192
+ mb: 2,
193
+ color: 'text.secondary',
194
+ }}>
195
+ {description}
196
+ </Box>
197
+ ) : null}
198
+ {actions ? <Box sx={{ mt: 8 }}>{actions}</Box> : null}
199
+ {termsOfUse ? <Box sx={{ mt: -1, color: 'grey' }}>{termsOfUse}</Box> : null}
200
+ </Box>
201
+ ) : null}
202
+
203
+ {content}
204
+ </Box>
205
+ <Box
206
+ sx={{
207
+ position: 'absolute',
208
+ zIndex: 10,
209
+ bottom: 10,
210
+ left: 10,
211
+ right: 10,
212
+ textAlign: 'center',
213
+ }}>
214
+ {poweredBy}
215
+ </Box>
216
+ </Box>
217
+ );
218
+ }
@@ -195,6 +195,7 @@ function ColorSchemeProvider({
195
195
  const toggleMode = useCallback(() => {
196
196
  const newMode = mode === 'light' ? 'dark' : 'light';
197
197
  setMode(newMode);
198
+ sessionStorage.removeItem(BLOCKLET_THEME_PREFER_KEY);
198
199
  localStorage.setItem(BLOCKLET_THEME_PREFER_KEY, newMode);
199
200
  }, [mode, setMode]);
200
201
 
@@ -202,6 +203,7 @@ function ColorSchemeProvider({
202
203
  (newMode: PaletteMode) => {
203
204
  if (mode !== newMode) {
204
205
  setMode(newMode);
206
+ sessionStorage.removeItem(BLOCKLET_THEME_PREFER_KEY);
205
207
  localStorage.setItem(BLOCKLET_THEME_PREFER_KEY, newMode);
206
208
  }
207
209
  },