@blocklet/ui-react 2.11.16 → 2.11.18

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.
@@ -77,6 +77,7 @@ export type Session = {
77
77
  switchPassport: any;
78
78
  refresh: Function;
79
79
  useOAuth: Function;
80
+ usePasskey: Function;
80
81
  };
81
82
  export type WebhookType = 'slack' | 'api';
82
83
  export type WebhookItemData = {
@@ -1,5 +1,5 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { WELLKNOWN_SERVICE_PATH_PREFIX } from "@arcblock/ux/lib/Util/constant";
2
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from "@arcblock/ux/lib/Util/constant";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { joinURL } from "ufo";
5
5
  function parseUrl(uri, params) {
@@ -71,7 +71,7 @@ function BlockletStudio({
71
71
  return null;
72
72
  }
73
73
  const src = parseUrl(
74
- joinURL(window.location.origin, WELLKNOWN_SERVICE_PATH_PREFIX, "/embed/resources", componentDid, "/publish"),
74
+ joinURL(window.location.origin, BLOCKLET_SERVICE_PATH_PREFIX, "/embed/resources", componentDid, "/publish"),
75
75
  {
76
76
  title,
77
77
  logo,
@@ -1,4 +1,4 @@
1
- import { AUTH_SERVICE_PREFIX } from '@arcblock/did-connect/lib/constant';
1
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from '@arcblock/did-connect/lib/constant';
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { joinURL } from 'ufo';
4
4
 
@@ -39,7 +39,7 @@ function useComponentInstalled({ did, onInstalled, onError }) {
39
39
  item.storeUrl = joinURL(item.meta.homepage, 'blocklets', item.meta.did);
40
40
  item.installUrl = joinURL(
41
41
  window.blocklet.appUrl,
42
- AUTH_SERVICE_PREFIX,
42
+ BLOCKLET_SERVICE_PATH_PREFIX,
43
43
  `/admin/components?install-component=${item.meta.did}`
44
44
  );
45
45
  });
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useContext } from "react";
3
3
  import Button from "@arcblock/ux/lib/Button";
4
4
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
5
- import { WELLKNOWN_SERVICE_PATH_PREFIX } from "@arcblock/ux/lib/Util/constant";
5
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from "@arcblock/ux/lib/Util/constant";
6
6
  import { useMemoizedFn, useReactive } from "ahooks";
7
7
  import { translate } from "@arcblock/ux/lib/Locale/util";
8
8
  import { joinURL, withQuery } from "ufo";
@@ -32,7 +32,7 @@ export default function AddComponent({
32
32
  loading: false,
33
33
  showDialog: false
34
34
  });
35
- const importUrl = withQuery(joinURL(WELLKNOWN_SERVICE_PATH_PREFIX, "embed/resources", componentDid, "add"), {
35
+ const importUrl = withQuery(joinURL(BLOCKLET_SERVICE_PATH_PREFIX, "embed/resources", componentDid, "add"), {
36
36
  resourceDid,
37
37
  resourceType,
38
38
  mode: "dialog",
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useContext } from "react";
3
3
  import Button from "@arcblock/ux/lib/Button";
4
4
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
5
- import { WELLKNOWN_SERVICE_PATH_PREFIX } from "@arcblock/ux/lib/Util/constant";
5
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from "@arcblock/ux/lib/Util/constant";
6
6
  import { useMemoizedFn, useReactive } from "ahooks";
7
7
  import { translate } from "@arcblock/ux/lib/Locale/util";
8
8
  import { SessionContext } from "@arcblock/did-connect/lib/Session";
@@ -24,7 +24,7 @@ export default function PublishComponent({
24
24
  showDialog: false,
25
25
  loading: false
26
26
  });
27
- const exportUrl = `${WELLKNOWN_SERVICE_PATH_PREFIX}/embed/resources/${componentDid}/publish?mode=dialog`;
27
+ const exportUrl = `${BLOCKLET_SERVICE_PATH_PREFIX}/embed/resources/${componentDid}/publish?mode=dialog`;
28
28
  const handleClose = useMemoizedFn(() => {
29
29
  currentState.showDialog = false;
30
30
  onClose?.();
@@ -1,4 +1,4 @@
1
- import { SpaceGateway, SpaceStatus } from '@blocklet/did-space-react';
1
+ import { SpaceGateway } from '@blocklet/did-space-react';
2
2
  import { Session } from '../../../@types';
3
3
  declare function Action({ session, spaceGateway, spaceStatus, refresh, }: {
4
4
  session: Session;
@@ -1,11 +1,12 @@
1
- import { Fragment, jsx } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import noop from "lodash/noop";
2
3
  import { useContext, useState } from "react";
3
4
  import { Box } from "@mui/material";
4
5
  import { useAsyncEffect, useCreation } from "ahooks";
5
6
  import { getConnectedAccounts, getSourceProvider } from "@arcblock/ux/lib/SessionUser/libs/utils";
6
7
  import { LOGIN_PROVIDER } from "@arcblock/ux/lib/Util/constant";
7
8
  import { SessionContext } from "@arcblock/did-connect/lib/Session";
8
- import Empty from "@arcblock/ux/lib/Empty";
9
+ import { PasskeyActions } from "@arcblock/did-connect/lib/Passkey";
9
10
  import ThirdPartyItem from "./third-party-item.js";
10
11
  export default function ThirdPartyLogin({ user }) {
11
12
  const { session } = useContext(SessionContext);
@@ -26,7 +27,7 @@ export default function ThirdPartyLogin({ user }) {
26
27
  }).filter((x) => x.enabled);
27
28
  return oauthList;
28
29
  }, [oauthConfigs]);
29
- const normalizedAccounts = useCreation(() => {
30
+ const oauthAccounts = useCreation(() => {
30
31
  const connectedAccounts = getConnectedAccounts(user);
31
32
  let removeAuth0 = false;
32
33
  let patchProvider = "";
@@ -95,6 +96,30 @@ export default function ThirdPartyLogin({ user }) {
95
96
  });
96
97
  return transformedProviders;
97
98
  }, [user?.connectedAccounts, availableProviders]);
99
+ const passkeyAccounts = useCreation(() => {
100
+ const connectedAccounts = getConnectedAccounts(user);
101
+ const passkeyConnectedAccount = connectedAccounts.filter(
102
+ (x) => x.provider === LOGIN_PROVIDER.PASSKEY && x.did !== user?.did
103
+ );
104
+ return passkeyConnectedAccount.map((x) => {
105
+ return {
106
+ ...x,
107
+ _bind: true
108
+ };
109
+ });
110
+ }, [user]);
111
+ const walletAccounts = useCreation(() => {
112
+ const connectedAccounts = getConnectedAccounts(user);
113
+ const walletConnectedAccount = connectedAccounts.filter(
114
+ (x) => x.provider === LOGIN_PROVIDER.WALLET && x.did !== user?.did
115
+ );
116
+ return walletConnectedAccount.map((x) => {
117
+ return {
118
+ ...x,
119
+ _mainProvider: true
120
+ };
121
+ });
122
+ }, [user]);
98
123
  return /* @__PURE__ */ jsx(
99
124
  Box,
100
125
  {
@@ -103,9 +128,40 @@ export default function ThirdPartyLogin({ user }) {
103
128
  display: "grid",
104
129
  gridTemplateColumns: "1fr min-content"
105
130
  },
106
- children: !normalizedAccounts.length ? /* @__PURE__ */ jsx(Empty, {}) : /* @__PURE__ */ jsx(Fragment, { children: normalizedAccounts.map((account) => {
107
- return /* @__PURE__ */ jsx(ThirdPartyItem, { item: account }, account?.provider);
108
- }) })
131
+ children: !oauthAccounts.length && !passkeyAccounts.length && !walletAccounts.length ? /* @__PURE__ */ jsx(
132
+ PasskeyActions,
133
+ {
134
+ behavior: "only-new",
135
+ action: "connect",
136
+ createMode: "connect",
137
+ createButtonText: "Add Passkey",
138
+ onSuccess: noop,
139
+ onError: noop,
140
+ dense: true
141
+ }
142
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
143
+ oauthAccounts.map((account) => {
144
+ return /* @__PURE__ */ jsx(ThirdPartyItem, { item: account }, account?.provider);
145
+ }),
146
+ passkeyAccounts.map((account) => {
147
+ return /* @__PURE__ */ jsx(ThirdPartyItem, { item: account }, account.id);
148
+ }),
149
+ walletAccounts.map((account) => {
150
+ return /* @__PURE__ */ jsx(ThirdPartyItem, { item: account }, account.id);
151
+ }),
152
+ /* @__PURE__ */ jsx(
153
+ PasskeyActions,
154
+ {
155
+ behavior: "only-new",
156
+ action: "connect",
157
+ createMode: "connect",
158
+ createButtonText: "Add Passkey",
159
+ onSuccess: noop,
160
+ onError: noop,
161
+ dense: true
162
+ }
163
+ )
164
+ ] })
109
165
  }
110
166
  );
111
167
  }
@@ -5,6 +5,7 @@ export default function ThirdPartyItem({ item, }: {
5
5
  did: string;
6
6
  pk: string;
7
7
  userInfo?: OAuthAccount['userInfo'];
8
+ id: string;
8
9
  _bind?: boolean;
9
10
  _rawProvider?: string;
10
11
  _mainProvider?: boolean;
@@ -11,6 +11,8 @@ import { useConfirm } from "@arcblock/ux/lib/Dialog";
11
11
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
12
12
  import MailOutlineRoundedIcon from "@iconify-icons/material-symbols/mail-outline-rounded";
13
13
  import InfoOutlineRoundedIcon from "@iconify-icons/material-symbols/info-outline-rounded";
14
+ import PasskeyIcon from "@iconify-icons/material-symbols/passkey";
15
+ import WalletIcon from "@iconify-icons/material-symbols/wallet-sharp";
14
16
  import AppleIcon from "@iconify-icons/logos/apple";
15
17
  import GithubIcon from "@iconify-icons/logos/github-icon";
16
18
  import GoogleIcon from "@iconify-icons/logos/google-icon";
@@ -18,7 +20,7 @@ import { SessionContext } from "@arcblock/did-connect/lib/Session";
18
20
  import { useContext } from "react";
19
21
  import pick from "lodash/pick";
20
22
  import { getFederatedEnabled, getMaster } from "@arcblock/ux/lib/Util/federated";
21
- import { LOGIN_PROVIDER, OAUTH_PROVIDER } from "@arcblock/ux/lib/Util/constant";
23
+ import { LOGIN_PROVIDER, LOGIN_PROVIDER_NAME } from "@arcblock/ux/lib/Util/constant";
22
24
  import { translations } from "../../libs/locales.js";
23
25
  export default function ThirdPartyItem({
24
26
  item
@@ -33,20 +35,43 @@ export default function ThirdPartyItem({
33
35
  });
34
36
  const { session } = useContext(SessionContext);
35
37
  const { bindOAuth, unbindOAuth, setBaseUrl, baseUrl: oauthBaseUrl } = session.useOAuth();
38
+ const { disconnectPasskey } = session.usePasskey();
36
39
  const iconMap = {
37
40
  // email: MailOutlineRoundedIcon,
38
41
  [LOGIN_PROVIDER.AUTH0]: MailOutlineRoundedIcon,
39
42
  [LOGIN_PROVIDER.APPLE]: AppleIcon,
40
43
  [LOGIN_PROVIDER.GITHUB]: GithubIcon,
41
- [LOGIN_PROVIDER.GOOGLE]: GoogleIcon
44
+ [LOGIN_PROVIDER.GOOGLE]: GoogleIcon,
45
+ [LOGIN_PROVIDER.PASSKEY]: PasskeyIcon,
46
+ [LOGIN_PROVIDER.WALLET]: WalletIcon
42
47
  };
43
48
  const icon = useCreation(() => {
44
49
  return iconMap[item?.provider];
45
50
  }, [item?.provider]);
46
51
  const title = useCreation(() => {
47
- return OAUTH_PROVIDER[item?.provider] || "unknown";
52
+ return LOGIN_PROVIDER_NAME[item?.provider] || "unknown";
48
53
  }, [item?.provider]);
49
54
  const toggleBind = useMemoizedFn(async () => {
55
+ if (item?.provider === LOGIN_PROVIDER.PASSKEY) {
56
+ await new Promise((resolve, reject) => {
57
+ confirmApi.open({
58
+ title: t("thirdPartyLogin.disconnectPasskey", { name: title }),
59
+ content: t("thirdPartyLogin.disconnectPasskeyDescription", { name: title }),
60
+ confirmButtonText: t("common.confirm"),
61
+ confirmButtonProps: {
62
+ color: "error"
63
+ },
64
+ cancelButtonText: t("common.cancel"),
65
+ onConfirm(close) {
66
+ disconnectPasskey({ session, connectedAccount: item }).then(resolve).catch(reject);
67
+ close();
68
+ },
69
+ // @ts-ignore
70
+ onCancel: resolve
71
+ });
72
+ });
73
+ return;
74
+ }
50
75
  try {
51
76
  currentState.loading = true;
52
77
  await new Promise((resolve, reject) => {
@@ -196,6 +196,17 @@ export default function UserCenter({
196
196
  const isProfileTab = useCreation(() => {
197
197
  return currentActiveTab && currentActiveTab?.value === joinURL(PROFILE_URL, "/profile");
198
198
  }, [currentActiveTab]);
199
+ const oauth = session.useOAuth();
200
+ const passkey = session.usePasskey();
201
+ const handleSwitchPassport = useMemoizedFn(() => {
202
+ if (session?.user?.sourceProvider === "passkey") {
203
+ passkey.switchPassport(session.user);
204
+ } else if (["google", "apple", "email", "github"].includes(session?.user?.sourceProvider)) {
205
+ oauth.switchOAuthPassport(session.user);
206
+ } else if (session) {
207
+ session.switchPassport();
208
+ }
209
+ });
199
210
  const renderDefaultTab = useCreation(() => {
200
211
  if (isProfileTab) {
201
212
  return /* @__PURE__ */ jsx(
@@ -231,7 +242,7 @@ export default function UserCenter({
231
242
  children: [
232
243
  /* @__PURE__ */ jsxs(Box, { display: "flex", justifyContent: "space-between", children: [
233
244
  /* @__PURE__ */ jsx(Typography, { sx: { fontWeight: 600, mb: 1.5 }, children: isMyself ? t("myInfo") : t("hisInfo") }),
234
- isMyself ? /* @__PURE__ */ jsx(SwitchRole, { user: userState.data, switchPassport: session.switchPassport }) : null
245
+ isMyself ? /* @__PURE__ */ jsx(SwitchRole, { user: userState.data, switchPassport: handleSwitchPassport }) : null
235
246
  ] }),
236
247
  /* @__PURE__ */ jsx(UserInfo, { user: userState.data, isMySelf: isMyself })
237
248
  ]
@@ -305,7 +316,7 @@ export default function UserCenter({
305
316
  UserBasicInfo,
306
317
  {
307
318
  isMyself,
308
- switchPassport: session.switchPassport,
319
+ switchPassport: handleSwitchPassport,
309
320
  switchProfile: session.switchProfile,
310
321
  user: userState.data,
311
322
  showFullDid: false,
@@ -87,6 +87,8 @@ export declare const translations: {
87
87
  mainProviderCantRemove: string;
88
88
  confirmUnbind: string;
89
89
  confirmUnbindDescription: string;
90
+ disconnectPasskey: string;
91
+ disconnectPasskeyDescription: string;
90
92
  };
91
93
  commonSetting: {
92
94
  title: string;
@@ -181,6 +183,8 @@ export declare const translations: {
181
183
  mainProviderCantRemove: string;
182
184
  confirmUnbind: string;
183
185
  confirmUnbindDescription: string;
186
+ disconnectPasskey: string;
187
+ disconnectPasskeyDescription: string;
184
188
  };
185
189
  commonSetting: {
186
190
  title: string;
@@ -81,12 +81,14 @@ export const translations = {
81
81
  }
82
82
  },
83
83
  thirdPartyLogin: {
84
- title: "\u7B2C\u4E09\u65B9\u767B\u5F55",
84
+ title: "\u5DF2\u8FDE\u63A5\u7684\u8D26\u6237",
85
85
  connect: "\u7ED1\u5B9A",
86
86
  disconnect: "\u89E3\u7ED1",
87
87
  mainProviderCantRemove: "\u4E3B\u8D26\u53F7\u4E0D\u5141\u8BB8\u89E3\u7ED1",
88
88
  confirmUnbind: "\u786E\u5B9A\u8981\u89E3\u7ED1 {name} \u5417?",
89
- confirmUnbindDescription: "\u89E3\u7ED1\u540E\u60A8\u5C06\u65E0\u6CD5\u4F7F\u7528 {name} \u767B\u5F55\u81F3\u8BE5\u8D26\u6237\u3002\u5982\u679C\u89E3\u7ED1\u540E\uFF0C\u60A8\u4ECD\u4F7F\u7528 {name} \u767B\u5F55\uFF0C\u4F1A\u81EA\u52A8\u521B\u5EFA\u4E00\u4E2A\u65B0\u8D26\u6237"
89
+ confirmUnbindDescription: "\u89E3\u7ED1\u540E\u60A8\u5C06\u65E0\u6CD5\u4F7F\u7528 {name} \u767B\u5F55\u81F3\u8BE5\u8D26\u6237\u3002\u5982\u679C\u89E3\u7ED1\u540E\uFF0C\u60A8\u4ECD\u4F7F\u7528 {name} \u767B\u5F55\uFF0C\u4F1A\u81EA\u52A8\u521B\u5EFA\u4E00\u4E2A\u65B0\u8D26\u6237",
90
+ disconnectPasskey: "\u786E\u5B9A\u8981\u79FB\u9664 Passkey \u5417?",
91
+ disconnectPasskeyDescription: "\u79FB\u9664\u540E\u60A8\u5C06\u65E0\u6CD5\u4F7F\u7528\u6B64 Passkey \u767B\u5F55\u3002\u5982\u679C\u79FB\u9664\u540E\uFF0C\u60A8\u4ECD\u4F7F\u7528\u6B64 Passkey \u767B\u5F55\uFF0C\u4F1A\u81EA\u52A8\u521B\u5EFA\u4E00\u4E2A\u65B0\u8D26\u6237"
90
92
  },
91
93
  commonSetting: {
92
94
  title: "\u901A\u7528\u8BBE\u7F6E",
@@ -117,7 +119,7 @@ export const translations = {
117
119
  webhookTested: "Test message sent",
118
120
  done: "Done",
119
121
  emptyField: "None",
120
- notificationManagement: "Notification",
122
+ notificationManagement: "Notifications",
121
123
  privacyManagement: "Privacy",
122
124
  storageManagement: "Storage",
123
125
  emptyContent: "Empty",
@@ -175,12 +177,14 @@ export const translations = {
175
177
  }
176
178
  },
177
179
  thirdPartyLogin: {
178
- title: "Third Party Login",
180
+ title: "Connected Accounts",
179
181
  connect: "Connect",
180
182
  disconnect: "Disconnect",
181
183
  mainProviderCantRemove: "Main account not allowed to remove",
182
184
  confirmUnbind: "Are you sure to unbind {name}?",
183
- confirmUnbindDescription: "You will not be able to log in to this account using {name} after unbundling. If you are still logged in with {name} after unbundling, a new account will be created automatically!"
185
+ confirmUnbindDescription: "You will not be able to log in to this account using {name} after unbundling. If you are still logged in with {name} after unbundling, a new account will be created automatically!",
186
+ disconnectPasskey: "Are you sure to remove {name}?",
187
+ disconnectPasskeyDescription: "You will not be able to log in with this passkey upon removing. If you are still logged in with this passkey after removing, a new account will be created automatically!"
184
188
  },
185
189
  commonSetting: {
186
190
  title: "Common Settings",
@@ -1,6 +1,6 @@
1
1
  import { joinURL } from "ufo";
2
2
  import { createPassportSvg as _createPassportSvg, createKycSvg as _createKycSvg } from "@arcblock/ux/lib/Util/passport";
3
- import { AUTH_SERVICE_PREFIX } from "@arcblock/ux/lib/Util/constant";
3
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from "@arcblock/ux/lib/Util/constant";
4
4
  export const formatAxiosError = (err) => {
5
5
  const { response } = err;
6
6
  if (response) {
@@ -12,12 +12,12 @@ export const createPassportSvg = (props) => {
12
12
  if (props.scope === "kyc") {
13
13
  return _createKycSvg({
14
14
  ...props,
15
- issuerAvatarUrl: joinURL(window.location.origin, AUTH_SERVICE_PREFIX, "/blocklet/logo"),
15
+ issuerAvatarUrl: joinURL(window.location.origin, BLOCKLET_SERVICE_PATH_PREFIX, "/blocklet/logo"),
16
16
  type: props.role
17
17
  });
18
18
  }
19
19
  return _createPassportSvg({
20
20
  ...props,
21
- issuerAvatarUrl: joinURL(window.location.origin, AUTH_SERVICE_PREFIX, "/blocklet/logo")
21
+ issuerAvatarUrl: joinURL(window.location.origin, BLOCKLET_SERVICE_PATH_PREFIX, "/blocklet/logo")
22
22
  });
23
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/ui-react",
3
- "version": "2.11.16",
3
+ "version": "2.11.18",
4
4
  "description": "Some useful front-end web components that can be used in Blocklets.",
5
5
  "keywords": [
6
6
  "react",
@@ -32,9 +32,9 @@
32
32
  "url": "https://github.com/ArcBlock/ux/issues"
33
33
  },
34
34
  "dependencies": {
35
- "@arcblock/bridge": "^2.11.16",
36
- "@arcblock/react-hooks": "^2.11.16",
37
- "@blocklet/did-space-react": "^0.6.0",
35
+ "@arcblock/bridge": "^2.11.18",
36
+ "@arcblock/react-hooks": "^2.11.18",
37
+ "@blocklet/did-space-react": "^1.0.1",
38
38
  "@iconify-icons/logos": "^1.2.36",
39
39
  "@iconify-icons/material-symbols": "^1.2.58",
40
40
  "@iconify/react": "^4.1.1",
@@ -81,5 +81,5 @@
81
81
  "jest": "^29.7.0",
82
82
  "unbuild": "^2.0.0"
83
83
  },
84
- "gitHead": "1216fcd112e02c4135f5d748fe002cafaaebcfd9"
84
+ "gitHead": "68ac427a17804a71e469f6fc61b28c87ecb9e40d"
85
85
  }
@@ -87,6 +87,7 @@ export type Session = {
87
87
 
88
88
  refresh: Function;
89
89
  useOAuth: Function;
90
+ usePasskey: Function;
90
91
  };
91
92
 
92
93
  export type WebhookType = 'slack' | 'api';
@@ -1,4 +1,4 @@
1
- import { WELLKNOWN_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
1
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
2
2
  import { useEffect, useRef, useState } from 'react';
3
3
  import { joinURL } from 'ufo';
4
4
 
@@ -101,7 +101,7 @@ function BlockletStudio({
101
101
  }
102
102
 
103
103
  const src = parseUrl(
104
- joinURL(window.location.origin, WELLKNOWN_SERVICE_PATH_PREFIX, '/embed/resources', componentDid, '/publish'),
104
+ joinURL(window.location.origin, BLOCKLET_SERVICE_PATH_PREFIX, '/embed/resources', componentDid, '/publish'),
105
105
  {
106
106
  title,
107
107
  logo,
@@ -1,4 +1,4 @@
1
- import { AUTH_SERVICE_PREFIX } from '@arcblock/did-connect/lib/constant';
1
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from '@arcblock/did-connect/lib/constant';
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { joinURL } from 'ufo';
4
4
 
@@ -39,7 +39,7 @@ function useComponentInstalled({ did, onInstalled, onError }) {
39
39
  item.storeUrl = joinURL(item.meta.homepage, 'blocklets', item.meta.did);
40
40
  item.installUrl = joinURL(
41
41
  window.blocklet.appUrl,
42
- AUTH_SERVICE_PREFIX,
42
+ BLOCKLET_SERVICE_PATH_PREFIX,
43
43
  `/admin/components?install-component=${item.meta.did}`
44
44
  );
45
45
  });
@@ -1,7 +1,7 @@
1
1
  import { useEffect, ReactElement, useRef, useContext } from 'react';
2
2
  import Button from '@arcblock/ux/lib/Button';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
- import { WELLKNOWN_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
4
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
5
5
  import { useMemoizedFn, useReactive } from 'ahooks';
6
6
  import { translate } from '@arcblock/ux/lib/Locale/util';
7
7
  import { joinURL, withQuery } from 'ufo';
@@ -46,7 +46,7 @@ export default function AddComponent({
46
46
  loading: false,
47
47
  showDialog: false,
48
48
  });
49
- const importUrl = withQuery(joinURL(WELLKNOWN_SERVICE_PATH_PREFIX, 'embed/resources', componentDid, 'add'), {
49
+ const importUrl = withQuery(joinURL(BLOCKLET_SERVICE_PATH_PREFIX, 'embed/resources', componentDid, 'add'), {
50
50
  resourceDid,
51
51
  resourceType,
52
52
  mode: 'dialog',
@@ -1,7 +1,7 @@
1
1
  import { ReactElement, useContext } from 'react';
2
2
  import Button from '@arcblock/ux/lib/Button';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
- import { WELLKNOWN_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
4
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
5
5
  import { useMemoizedFn, useReactive } from 'ahooks';
6
6
  import { translate } from '@arcblock/ux/lib/Locale/util';
7
7
  import { SessionContext } from '@arcblock/did-connect/lib/Session';
@@ -32,7 +32,7 @@ export default function PublishComponent({
32
32
  loading: false,
33
33
  });
34
34
 
35
- const exportUrl = `${WELLKNOWN_SERVICE_PATH_PREFIX}/embed/resources/${componentDid}/publish?mode=dialog`;
35
+ const exportUrl = `${BLOCKLET_SERVICE_PATH_PREFIX}/embed/resources/${componentDid}/publish?mode=dialog`;
36
36
 
37
37
  const handleClose = useMemoizedFn(() => {
38
38
  currentState.showDialog = false;
@@ -1,11 +1,12 @@
1
+ import noop from 'lodash/noop';
1
2
  import { useContext, useState } from 'react';
2
3
  import { Box } from '@mui/material';
3
4
  import { useAsyncEffect, useCreation } from 'ahooks';
4
5
  import { getConnectedAccounts, getSourceProvider } from '@arcblock/ux/lib/SessionUser/libs/utils';
5
6
  import { LOGIN_PROVIDER } from '@arcblock/ux/lib/Util/constant';
6
7
  import { SessionContext } from '@arcblock/did-connect/lib/Session';
8
+ import { PasskeyActions } from '@arcblock/did-connect/lib/Passkey';
7
9
 
8
- import Empty from '@arcblock/ux/lib/Empty';
9
10
  import type { ConnectedAccount, OAuthAccount, User, SessionContext as TSessionContext } from '../../../@types';
10
11
  import ThirdPartyItem from './third-party-item';
11
12
 
@@ -17,6 +18,7 @@ export default function ThirdPartyLogin({ user }: { user: User }) {
17
18
  const { session } = useContext<TSessionContext>(SessionContext);
18
19
  const [oauthConfigs, setOauthConfigs] = useState<Record<string, ConnectedAccountProps>>({});
19
20
  const { getOAuthConfigs } = session.useOAuth();
21
+
20
22
  useAsyncEffect(async () => {
21
23
  const data = await getOAuthConfigs({
22
24
  sourceAppPid: user?.sourceAppPid,
@@ -41,7 +43,7 @@ export default function ThirdPartyLogin({ user }: { user: User }) {
41
43
  * 2. 如果标记移除了 auth0,则还需要对 sourceProvider 进行转换,转换的值为 google, apple, github 中的一种
42
44
  * 3. 如果标记移除了 auth0,还需要将 auth0 的 userInfo 数据添加到转换后的 provider 上
43
45
  */
44
- const normalizedAccounts = useCreation(() => {
46
+ const oauthAccounts = useCreation(() => {
45
47
  const connectedAccounts: ConnectedAccount[] = getConnectedAccounts(user);
46
48
  let removeAuth0 = false;
47
49
  let patchProvider = '';
@@ -118,6 +120,32 @@ export default function ThirdPartyLogin({ user }: { user: User }) {
118
120
  return transformedProviders;
119
121
  }, [user?.connectedAccounts, availableProviders]);
120
122
 
123
+ const passkeyAccounts = useCreation(() => {
124
+ const connectedAccounts: ConnectedAccount[] = getConnectedAccounts(user);
125
+ const passkeyConnectedAccount = connectedAccounts.filter(
126
+ (x) => x.provider === LOGIN_PROVIDER.PASSKEY && x.did !== user?.did
127
+ ) as OAuthAccount[];
128
+ return passkeyConnectedAccount.map((x) => {
129
+ return {
130
+ ...x,
131
+ _bind: true,
132
+ };
133
+ });
134
+ }, [user]);
135
+
136
+ const walletAccounts = useCreation(() => {
137
+ const connectedAccounts: ConnectedAccount[] = getConnectedAccounts(user);
138
+ const walletConnectedAccount = connectedAccounts.filter(
139
+ (x) => x.provider === LOGIN_PROVIDER.WALLET && x.did !== user?.did
140
+ ) as OAuthAccount[];
141
+ return walletConnectedAccount.map((x) => {
142
+ return {
143
+ ...x,
144
+ _mainProvider: true,
145
+ };
146
+ });
147
+ }, [user]);
148
+
121
149
  return (
122
150
  <Box
123
151
  sx={{
@@ -125,13 +153,36 @@ export default function ThirdPartyLogin({ user }: { user: User }) {
125
153
  display: 'grid',
126
154
  gridTemplateColumns: '1fr min-content',
127
155
  }}>
128
- {!normalizedAccounts.length ? (
129
- <Empty />
156
+ {!oauthAccounts.length && !passkeyAccounts.length && !walletAccounts.length ? (
157
+ <PasskeyActions
158
+ behavior="only-new"
159
+ action="connect"
160
+ createMode="connect"
161
+ createButtonText="Add Passkey"
162
+ onSuccess={noop}
163
+ onError={noop}
164
+ dense
165
+ />
130
166
  ) : (
131
167
  <>
132
- {normalizedAccounts.map((account) => {
168
+ {oauthAccounts.map((account) => {
133
169
  return <ThirdPartyItem key={account?.provider} item={account as OAuthAccount} />;
134
170
  })}
171
+ {passkeyAccounts.map((account) => {
172
+ return <ThirdPartyItem key={account.id} item={account as OAuthAccount} />;
173
+ })}
174
+ {walletAccounts.map((account) => {
175
+ return <ThirdPartyItem key={account.id} item={account as OAuthAccount} />;
176
+ })}
177
+ <PasskeyActions
178
+ behavior="only-new"
179
+ action="connect"
180
+ createMode="connect"
181
+ createButtonText="Add Passkey"
182
+ onSuccess={noop}
183
+ onError={noop}
184
+ dense
185
+ />
135
186
  </>
136
187
  )}
137
188
  </Box>
@@ -10,6 +10,8 @@ import { useConfirm } from '@arcblock/ux/lib/Dialog';
10
10
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
11
11
  import MailOutlineRoundedIcon from '@iconify-icons/material-symbols/mail-outline-rounded';
12
12
  import InfoOutlineRoundedIcon from '@iconify-icons/material-symbols/info-outline-rounded';
13
+ import PasskeyIcon from '@iconify-icons/material-symbols/passkey';
14
+ import WalletIcon from '@iconify-icons/material-symbols/wallet-sharp';
13
15
  import AppleIcon from '@iconify-icons/logos/apple';
14
16
  import GithubIcon from '@iconify-icons/logos/github-icon';
15
17
  import GoogleIcon from '@iconify-icons/logos/google-icon';
@@ -18,7 +20,7 @@ import { SessionContext } from '@arcblock/did-connect/lib/Session';
18
20
  import { useContext } from 'react';
19
21
  import pick from 'lodash/pick';
20
22
  import { getFederatedEnabled, getMaster } from '@arcblock/ux/lib/Util/federated';
21
- import { LOGIN_PROVIDER, OAUTH_PROVIDER } from '@arcblock/ux/lib/Util/constant';
23
+ import { LOGIN_PROVIDER, LOGIN_PROVIDER_NAME, OAUTH_PROVIDER } from '@arcblock/ux/lib/Util/constant';
22
24
 
23
25
  import { translations } from '../../libs/locales';
24
26
  import type { OAuthAccount, SessionContext as TSessionContext } from '../../../@types';
@@ -31,6 +33,7 @@ export default function ThirdPartyItem({
31
33
  did: string;
32
34
  pk: string;
33
35
  userInfo?: OAuthAccount['userInfo'];
36
+ id: string;
34
37
  _bind?: boolean;
35
38
  _rawProvider?: string;
36
39
  _mainProvider?: boolean;
@@ -46,6 +49,7 @@ export default function ThirdPartyItem({
46
49
  });
47
50
  const { session } = useContext<TSessionContext>(SessionContext);
48
51
  const { bindOAuth, unbindOAuth, setBaseUrl, baseUrl: oauthBaseUrl } = session.useOAuth();
52
+ const { disconnectPasskey } = session.usePasskey();
49
53
 
50
54
  const iconMap = {
51
55
  // email: MailOutlineRoundedIcon,
@@ -53,16 +57,39 @@ export default function ThirdPartyItem({
53
57
  [LOGIN_PROVIDER.APPLE]: AppleIcon,
54
58
  [LOGIN_PROVIDER.GITHUB]: GithubIcon,
55
59
  [LOGIN_PROVIDER.GOOGLE]: GoogleIcon,
60
+ [LOGIN_PROVIDER.PASSKEY]: PasskeyIcon,
61
+ [LOGIN_PROVIDER.WALLET]: WalletIcon,
56
62
  };
57
63
 
58
64
  const icon = useCreation(() => {
59
65
  return iconMap[item?.provider];
60
66
  }, [item?.provider]);
61
67
  const title = useCreation(() => {
62
- return OAUTH_PROVIDER[item?.provider as keyof typeof OAUTH_PROVIDER] || 'unknown';
68
+ return LOGIN_PROVIDER_NAME[item?.provider as keyof typeof OAUTH_PROVIDER] || 'unknown';
63
69
  }, [item?.provider]);
64
70
 
65
71
  const toggleBind = useMemoizedFn(async () => {
72
+ if (item?.provider === LOGIN_PROVIDER.PASSKEY) {
73
+ await new Promise((resolve, reject) => {
74
+ confirmApi.open({
75
+ title: t('thirdPartyLogin.disconnectPasskey', { name: title }),
76
+ content: t('thirdPartyLogin.disconnectPasskeyDescription', { name: title }),
77
+ confirmButtonText: t('common.confirm'),
78
+ confirmButtonProps: {
79
+ color: 'error',
80
+ },
81
+ cancelButtonText: t('common.cancel'),
82
+ onConfirm(close: () => void) {
83
+ disconnectPasskey({ session, connectedAccount: item }).then(resolve).catch(reject);
84
+ close();
85
+ },
86
+ // @ts-ignore
87
+ onCancel: resolve,
88
+ });
89
+ });
90
+ return;
91
+ }
92
+
66
93
  try {
67
94
  currentState.loading = true;
68
95
  await new Promise((resolve, reject) => {
@@ -235,6 +235,18 @@ export default function UserCenter({
235
235
  return currentActiveTab && currentActiveTab?.value === joinURL(PROFILE_URL, '/profile');
236
236
  }, [currentActiveTab]);
237
237
 
238
+ const oauth = session.useOAuth();
239
+ const passkey = session.usePasskey();
240
+ const handleSwitchPassport = useMemoizedFn(() => {
241
+ if (session?.user?.sourceProvider === 'passkey') {
242
+ passkey.switchPassport(session.user);
243
+ } else if (['google', 'apple', 'email', 'github'].includes(session?.user?.sourceProvider)) {
244
+ oauth.switchOAuthPassport(session.user);
245
+ } else if (session) {
246
+ session.switchPassport();
247
+ }
248
+ });
249
+
238
250
  const renderDefaultTab = useCreation(() => {
239
251
  if (isProfileTab) {
240
252
  return (
@@ -263,7 +275,7 @@ export default function UserCenter({
263
275
  }}>
264
276
  <Box display="flex" justifyContent="space-between">
265
277
  <Typography sx={{ fontWeight: 600, mb: 1.5 }}>{isMyself ? t('myInfo') : t('hisInfo')}</Typography>
266
- {isMyself ? <SwitchRole user={userState.data as User} switchPassport={session.switchPassport} /> : null}
278
+ {isMyself ? <SwitchRole user={userState.data as User} switchPassport={handleSwitchPassport} /> : null}
267
279
  </Box>
268
280
  <UserInfo user={userState.data as User} isMySelf={isMyself} />
269
281
  </Box>
@@ -348,7 +360,7 @@ export default function UserCenter({
348
360
  ]}>
349
361
  <UserBasicInfo
350
362
  isMyself={isMyself}
351
- switchPassport={session.switchPassport}
363
+ switchPassport={handleSwitchPassport}
352
364
  switchProfile={session.switchProfile}
353
365
  user={userState.data as User}
354
366
  showFullDid={false}
@@ -81,13 +81,16 @@ export const translations = {
81
81
  },
82
82
  },
83
83
  thirdPartyLogin: {
84
- title: '第三方登录',
84
+ title: '已连接的账户',
85
85
  connect: '绑定',
86
86
  disconnect: '解绑',
87
87
  mainProviderCantRemove: '主账号不允许解绑',
88
88
  confirmUnbind: '确定要解绑 {name} 吗?',
89
89
  confirmUnbindDescription:
90
90
  '解绑后您将无法使用 {name} 登录至该账户。如果解绑后,您仍使用 {name} 登录,会自动创建一个新账户',
91
+ disconnectPasskey: '确定要移除 Passkey 吗?',
92
+ disconnectPasskeyDescription:
93
+ '移除后您将无法使用此 Passkey 登录。如果移除后,您仍使用此 Passkey 登录,会自动创建一个新账户',
91
94
  },
92
95
  commonSetting: {
93
96
  title: '通用设置',
@@ -118,7 +121,7 @@ export const translations = {
118
121
  webhookTested: 'Test message sent',
119
122
  done: 'Done',
120
123
  emptyField: 'None',
121
- notificationManagement: 'Notification',
124
+ notificationManagement: 'Notifications',
122
125
  privacyManagement: 'Privacy',
123
126
  storageManagement: 'Storage',
124
127
  emptyContent: 'Empty',
@@ -176,13 +179,16 @@ export const translations = {
176
179
  },
177
180
  },
178
181
  thirdPartyLogin: {
179
- title: 'Third Party Login',
182
+ title: 'Connected Accounts',
180
183
  connect: 'Connect',
181
184
  disconnect: 'Disconnect',
182
185
  mainProviderCantRemove: 'Main account not allowed to remove',
183
186
  confirmUnbind: 'Are you sure to unbind {name}?',
184
187
  confirmUnbindDescription:
185
188
  'You will not be able to log in to this account using {name} after unbundling. If you are still logged in with {name} after unbundling, a new account will be created automatically!',
189
+ disconnectPasskey: 'Are you sure to remove {name}?',
190
+ disconnectPasskeyDescription:
191
+ 'You will not be able to log in with this passkey upon removing. If you are still logged in with this passkey after removing, a new account will be created automatically!',
186
192
  },
187
193
  commonSetting: {
188
194
  title: 'Common Settings',
@@ -1,7 +1,7 @@
1
1
  import type { AxiosError } from 'axios';
2
2
  import { joinURL } from 'ufo';
3
3
  import { createPassportSvg as _createPassportSvg, createKycSvg as _createKycSvg } from '@arcblock/ux/lib/Util/passport';
4
- import { AUTH_SERVICE_PREFIX } from '@arcblock/ux/lib/Util/constant';
4
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
5
5
  import { CreatePassportProps } from '../../@types';
6
6
 
7
7
  export const formatAxiosError = (err: AxiosError) => {
@@ -18,13 +18,13 @@ export const createPassportSvg = (props: CreatePassportProps) => {
18
18
  if (props.scope === 'kyc') {
19
19
  return _createKycSvg({
20
20
  ...props,
21
- issuerAvatarUrl: joinURL(window.location.origin, AUTH_SERVICE_PREFIX, '/blocklet/logo'),
21
+ issuerAvatarUrl: joinURL(window.location.origin, BLOCKLET_SERVICE_PATH_PREFIX, '/blocklet/logo'),
22
22
  type: props.role,
23
23
  });
24
24
  }
25
25
 
26
26
  return _createPassportSvg({
27
27
  ...props,
28
- issuerAvatarUrl: joinURL(window.location.origin, AUTH_SERVICE_PREFIX, '/blocklet/logo'),
28
+ issuerAvatarUrl: joinURL(window.location.origin, BLOCKLET_SERVICE_PATH_PREFIX, '/blocklet/logo'),
29
29
  });
30
30
  };