thirdweb 5.77.0 → 5.79.0-nightly-f91f6310e9396918d0ffc5217eeb4a44cef0b8c8-20241215000412
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/dist/cjs/exports/react.js +11 -2
- package/dist/cjs/exports/react.js.map +1 -1
- package/dist/cjs/exports/react.native.js +4 -1
- package/dist/cjs/exports/react.native.js.map +1 -1
- package/dist/cjs/exports/wallets/in-app.js +2 -1
- package/dist/cjs/exports/wallets/in-app.js.map +1 -1
- package/dist/cjs/exports/wallets/in-app.native.js +2 -1
- package/dist/cjs/exports/wallets/in-app.native.js.map +1 -1
- package/dist/cjs/exports/wallets.js +2 -1
- package/dist/cjs/exports/wallets.js.map +1 -1
- package/dist/cjs/exports/wallets.native.js +2 -1
- package/dist/cjs/exports/wallets.native.js.map +1 -1
- package/dist/cjs/extensions/prebuilts/deploy-published.js +22 -16
- package/dist/cjs/extensions/prebuilts/deploy-published.js.map +1 -1
- package/dist/cjs/react/native/hooks/wallets/useUnlinkProfile.js +58 -0
- package/dist/cjs/react/native/hooks/wallets/useUnlinkProfile.js.map +1 -0
- package/dist/cjs/react/web/hooks/wallets/useUnlinkProfile.js +58 -0
- package/dist/cjs/react/web/hooks/wallets/useUnlinkProfile.js.map +1 -0
- package/dist/cjs/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.js +22 -6
- package/dist/cjs/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.js.map +1 -1
- package/dist/cjs/react/web/ui/MediaRenderer/useResolvedMediaType.js +1 -1
- package/dist/cjs/react/web/ui/MediaRenderer/useResolvedMediaType.js.map +1 -1
- package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js +1 -1
- package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
- package/dist/cjs/react/web/ui/prebuilt/Chain/name.js +15 -11
- package/dist/cjs/react/web/ui/prebuilt/Chain/name.js.map +1 -1
- package/dist/cjs/react/web/ui/prebuilt/Chain/provider.js +1 -0
- package/dist/cjs/react/web/ui/prebuilt/Chain/provider.js.map +1 -1
- package/dist/cjs/react/web/ui/prebuilt/Wallet/icon.js +79 -0
- package/dist/cjs/react/web/ui/prebuilt/Wallet/icon.js.map +1 -0
- package/dist/cjs/react/web/ui/prebuilt/Wallet/name.js +105 -0
- package/dist/cjs/react/web/ui/prebuilt/Wallet/name.js.map +1 -0
- package/dist/cjs/react/web/ui/prebuilt/Wallet/provider.js +47 -0
- package/dist/cjs/react/web/ui/prebuilt/Wallet/provider.js.map +1 -0
- package/dist/cjs/react/web/wallets/in-app/CountrySelector.js +11 -10
- package/dist/cjs/react/web/wallets/in-app/CountrySelector.js.map +1 -1
- package/dist/cjs/react/web/wallets/in-app/InputSelectionUI.js +3 -1
- package/dist/cjs/react/web/wallets/in-app/InputSelectionUI.js.map +1 -1
- package/dist/cjs/react/web/wallets/in-app/supported-sms-countries.js.map +1 -1
- package/dist/cjs/react/web/wallets/shared/ConnectWalletSocialOptions.js +1 -1
- package/dist/cjs/react/web/wallets/shared/ConnectWalletSocialOptions.js.map +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/version.js.map +1 -1
- package/dist/cjs/wallets/in-app/core/authentication/linkAccount.js +31 -0
- package/dist/cjs/wallets/in-app/core/authentication/linkAccount.js.map +1 -1
- package/dist/cjs/wallets/in-app/native/auth/index.js +30 -3
- package/dist/cjs/wallets/in-app/native/auth/index.js.map +1 -1
- package/dist/cjs/wallets/in-app/native/native-connector.js +8 -0
- package/dist/cjs/wallets/in-app/native/native-connector.js.map +1 -1
- package/dist/cjs/wallets/in-app/web/lib/auth/index.js +30 -0
- package/dist/cjs/wallets/in-app/web/lib/auth/index.js.map +1 -1
- package/dist/cjs/wallets/in-app/web/lib/web-connector.js +8 -0
- package/dist/cjs/wallets/in-app/web/lib/web-connector.js.map +1 -1
- package/dist/esm/exports/react.js +5 -0
- package/dist/esm/exports/react.js.map +1 -1
- package/dist/esm/exports/react.native.js +1 -0
- package/dist/esm/exports/react.native.js.map +1 -1
- package/dist/esm/exports/wallets/in-app.js +1 -1
- package/dist/esm/exports/wallets/in-app.js.map +1 -1
- package/dist/esm/exports/wallets/in-app.native.js +1 -1
- package/dist/esm/exports/wallets/in-app.native.js.map +1 -1
- package/dist/esm/exports/wallets.js +1 -1
- package/dist/esm/exports/wallets.js.map +1 -1
- package/dist/esm/exports/wallets.native.js +1 -1
- package/dist/esm/exports/wallets.native.js.map +1 -1
- package/dist/esm/extensions/prebuilts/deploy-published.js +22 -16
- package/dist/esm/extensions/prebuilts/deploy-published.js.map +1 -1
- package/dist/esm/react/native/hooks/wallets/useUnlinkProfile.js +55 -0
- package/dist/esm/react/native/hooks/wallets/useUnlinkProfile.js.map +1 -0
- package/dist/esm/react/web/hooks/wallets/useUnlinkProfile.js +55 -0
- package/dist/esm/react/web/hooks/wallets/useUnlinkProfile.js.map +1 -0
- package/dist/esm/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.js +22 -6
- package/dist/esm/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.js.map +1 -1
- package/dist/esm/react/web/ui/MediaRenderer/useResolvedMediaType.js +1 -1
- package/dist/esm/react/web/ui/MediaRenderer/useResolvedMediaType.js.map +1 -1
- package/dist/esm/react/web/ui/prebuilt/Chain/icon.js +1 -1
- package/dist/esm/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
- package/dist/esm/react/web/ui/prebuilt/Chain/name.js +14 -11
- package/dist/esm/react/web/ui/prebuilt/Chain/name.js.map +1 -1
- package/dist/esm/react/web/ui/prebuilt/Chain/provider.js +1 -0
- package/dist/esm/react/web/ui/prebuilt/Chain/provider.js.map +1 -1
- package/dist/esm/react/web/ui/prebuilt/Wallet/icon.js +75 -0
- package/dist/esm/react/web/ui/prebuilt/Wallet/icon.js.map +1 -0
- package/dist/esm/react/web/ui/prebuilt/Wallet/name.js +100 -0
- package/dist/esm/react/web/ui/prebuilt/Wallet/name.js.map +1 -0
- package/dist/esm/react/web/ui/prebuilt/Wallet/provider.js +42 -0
- package/dist/esm/react/web/ui/prebuilt/Wallet/provider.js.map +1 -0
- package/dist/esm/react/web/wallets/in-app/CountrySelector.js +10 -10
- package/dist/esm/react/web/wallets/in-app/CountrySelector.js.map +1 -1
- package/dist/esm/react/web/wallets/in-app/InputSelectionUI.js +4 -2
- package/dist/esm/react/web/wallets/in-app/InputSelectionUI.js.map +1 -1
- package/dist/esm/react/web/wallets/in-app/supported-sms-countries.js.map +1 -1
- package/dist/esm/react/web/wallets/shared/ConnectWalletSocialOptions.js +1 -1
- package/dist/esm/react/web/wallets/shared/ConnectWalletSocialOptions.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/esm/wallets/in-app/core/authentication/linkAccount.js +30 -0
- package/dist/esm/wallets/in-app/core/authentication/linkAccount.js.map +1 -1
- package/dist/esm/wallets/in-app/native/auth/index.js +29 -3
- package/dist/esm/wallets/in-app/native/auth/index.js.map +1 -1
- package/dist/esm/wallets/in-app/native/native-connector.js +9 -1
- package/dist/esm/wallets/in-app/native/native-connector.js.map +1 -1
- package/dist/esm/wallets/in-app/web/lib/auth/index.js +29 -0
- package/dist/esm/wallets/in-app/web/lib/auth/index.js.map +1 -1
- package/dist/esm/wallets/in-app/web/lib/web-connector.js +9 -1
- package/dist/esm/wallets/in-app/web/lib/web-connector.js.map +1 -1
- package/dist/types/exports/react.d.ts +4 -0
- package/dist/types/exports/react.d.ts.map +1 -1
- package/dist/types/exports/react.native.d.ts +1 -0
- package/dist/types/exports/react.native.d.ts.map +1 -1
- package/dist/types/exports/wallets/in-app.d.ts +3 -2
- package/dist/types/exports/wallets/in-app.d.ts.map +1 -1
- package/dist/types/exports/wallets/in-app.native.d.ts +3 -2
- package/dist/types/exports/wallets/in-app.native.d.ts.map +1 -1
- package/dist/types/exports/wallets.d.ts +3 -2
- package/dist/types/exports/wallets.d.ts.map +1 -1
- package/dist/types/exports/wallets.native.d.ts +3 -2
- package/dist/types/exports/wallets.native.d.ts.map +1 -1
- package/dist/types/extensions/prebuilts/deploy-published.d.ts.map +1 -1
- package/dist/types/react/native/hooks/wallets/useUnlinkProfile.d.ts +34 -0
- package/dist/types/react/native/hooks/wallets/useUnlinkProfile.d.ts.map +1 -0
- package/dist/types/react/web/hooks/wallets/useUnlinkProfile.d.ts +34 -0
- package/dist/types/react/web/hooks/wallets/useUnlinkProfile.d.ts.map +1 -0
- package/dist/types/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.d.ts.map +1 -1
- package/dist/types/react/web/ui/prebuilt/Chain/name.d.ts +10 -1
- package/dist/types/react/web/ui/prebuilt/Chain/name.d.ts.map +1 -1
- package/dist/types/react/web/ui/prebuilt/Chain/provider.d.ts +1 -0
- package/dist/types/react/web/ui/prebuilt/Chain/provider.d.ts.map +1 -1
- package/dist/types/react/web/ui/prebuilt/Wallet/icon.d.ts +81 -0
- package/dist/types/react/web/ui/prebuilt/Wallet/icon.d.ts.map +1 -0
- package/dist/types/react/web/ui/prebuilt/Wallet/name.d.ts +111 -0
- package/dist/types/react/web/ui/prebuilt/Wallet/name.d.ts.map +1 -0
- package/dist/types/react/web/ui/prebuilt/Wallet/provider.d.ts +41 -0
- package/dist/types/react/web/ui/prebuilt/Wallet/provider.d.ts.map +1 -0
- package/dist/types/react/web/wallets/in-app/CountrySelector.d.ts +2 -0
- package/dist/types/react/web/wallets/in-app/CountrySelector.d.ts.map +1 -1
- package/dist/types/react/web/wallets/in-app/InputSelectionUI.d.ts +2 -0
- package/dist/types/react/web/wallets/in-app/InputSelectionUI.d.ts.map +1 -1
- package/dist/types/react/web/wallets/in-app/supported-sms-countries.d.ts +946 -5
- package/dist/types/react/web/wallets/in-app/supported-sms-countries.d.ts.map +1 -1
- package/dist/types/react/web/wallets/shared/ConnectWalletSocialOptions.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/version.d.ts.map +1 -1
- package/dist/types/wallets/ecosystem/types.d.ts +5 -0
- package/dist/types/wallets/ecosystem/types.d.ts.map +1 -1
- package/dist/types/wallets/in-app/core/authentication/linkAccount.d.ts +13 -0
- package/dist/types/wallets/in-app/core/authentication/linkAccount.d.ts.map +1 -1
- package/dist/types/wallets/in-app/core/authentication/types.d.ts +5 -0
- package/dist/types/wallets/in-app/core/authentication/types.d.ts.map +1 -1
- package/dist/types/wallets/in-app/core/interfaces/connector.d.ts +1 -0
- package/dist/types/wallets/in-app/core/interfaces/connector.d.ts.map +1 -1
- package/dist/types/wallets/in-app/core/wallet/types.d.ts +8 -3
- package/dist/types/wallets/in-app/core/wallet/types.d.ts.map +1 -1
- package/dist/types/wallets/in-app/native/auth/index.d.ts +27 -4
- package/dist/types/wallets/in-app/native/auth/index.d.ts.map +1 -1
- package/dist/types/wallets/in-app/native/native-connector.d.ts +4 -3
- package/dist/types/wallets/in-app/native/native-connector.d.ts.map +1 -1
- package/dist/types/wallets/in-app/web/lib/auth/index.d.ts +27 -1
- package/dist/types/wallets/in-app/web/lib/auth/index.d.ts.map +1 -1
- package/dist/types/wallets/in-app/web/lib/web-connector.d.ts +4 -3
- package/dist/types/wallets/in-app/web/lib/web-connector.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/exports/react.native.ts +1 -0
- package/src/exports/react.ts +15 -0
- package/src/exports/wallets/in-app.native.ts +7 -0
- package/src/exports/wallets/in-app.ts +7 -0
- package/src/exports/wallets.native.ts +7 -0
- package/src/exports/wallets.ts +7 -0
- package/src/extensions/prebuilts/deploy-published.ts +27 -20
- package/src/react/core/hooks/wallets/useAddConnectedWallet.test.tsx +52 -0
- package/src/react/core/hooks/wallets/useConnect.test.tsx +105 -0
- package/src/react/core/hooks/wallets/useSetActiveWallet.test.tsx +53 -0
- package/src/react/native/hooks/wallets/useUnlinkProfile.test.tsx +75 -0
- package/src/react/native/hooks/wallets/useUnlinkProfile.ts +62 -0
- package/src/react/web/hooks/wallets/useUnlinkProfile.test.tsx +75 -0
- package/src/react/web/hooks/wallets/useUnlinkProfile.ts +62 -0
- package/src/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.test.tsx +25 -0
- package/src/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.tsx +86 -44
- package/src/react/web/ui/MediaRenderer/useResolvedMediaType.ts +1 -1
- package/src/react/web/ui/prebuilt/Chain/icon.tsx +1 -1
- package/src/react/web/ui/prebuilt/Chain/name.test.tsx +19 -1
- package/src/react/web/ui/prebuilt/Chain/name.tsx +19 -13
- package/src/react/web/ui/prebuilt/Chain/provider.tsx +1 -0
- package/src/react/web/ui/prebuilt/Wallet/icon.test.tsx +30 -0
- package/src/react/web/ui/prebuilt/Wallet/icon.tsx +120 -0
- package/src/react/web/ui/prebuilt/Wallet/name.test.tsx +55 -0
- package/src/react/web/ui/prebuilt/Wallet/name.tsx +164 -0
- package/src/react/web/ui/prebuilt/Wallet/provider.test.tsx +61 -0
- package/src/react/web/ui/prebuilt/Wallet/provider.tsx +65 -0
- package/src/react/web/wallets/in-app/CountrySelector.test.tsx +45 -0
- package/src/react/web/wallets/in-app/CountrySelector.tsx +16 -13
- package/src/react/web/wallets/in-app/InputSelectionUI.test.tsx +45 -0
- package/src/react/web/wallets/in-app/InputSelectionUI.tsx +8 -2
- package/src/react/web/wallets/in-app/supported-sms-countries.ts +3 -1
- package/src/react/web/wallets/shared/ConnectWalletSocialOptions.tsx +3 -0
- package/src/version.ts +1 -1
- package/src/wallets/ecosystem/types.ts +5 -0
- package/src/wallets/in-app/core/authentication/linkAccount.test.ts +160 -0
- package/src/wallets/in-app/core/authentication/linkAccount.ts +49 -0
- package/src/wallets/in-app/core/authentication/types.ts +6 -0
- package/src/wallets/in-app/core/interfaces/connector.ts +1 -0
- package/src/wallets/in-app/core/wallet/types.ts +12 -8
- package/src/wallets/in-app/native/auth/index.ts +31 -3
- package/src/wallets/in-app/native/native-connector.ts +11 -0
- package/src/wallets/in-app/web/lib/auth/index.ts +31 -0
- package/src/wallets/in-app/web/lib/web-connector.ts +11 -0
- package/src/wallets/manager/connection-manager.test.ts +133 -133
@@ -0,0 +1,105 @@
|
|
1
|
+
import { renderHook } from "@testing-library/react";
|
2
|
+
import type { ReactNode } from "react";
|
3
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
4
|
+
import { MockStorage } from "../../../../../test/src/mocks/storage.js";
|
5
|
+
import { TEST_CLIENT } from "../../../../../test/src/test-clients.js";
|
6
|
+
import { TEST_ACCOUNT_A } from "../../../../../test/src/test-wallets.js";
|
7
|
+
import { createWalletAdapter } from "../../../../adapters/wallet-adapter.js";
|
8
|
+
import { ethereum } from "../../../../chains/chain-definitions/ethereum.js";
|
9
|
+
import {
|
10
|
+
type ConnectionManager,
|
11
|
+
createConnectionManager,
|
12
|
+
} from "../../../../wallets/manager/index.js";
|
13
|
+
import { ConnectionManagerCtx } from "../../providers/connection-manager.js";
|
14
|
+
import { useActiveWalletConnectionStatus } from "./useActiveWalletConnectionStatus.js";
|
15
|
+
import { useConnect } from "./useConnect.js";
|
16
|
+
|
17
|
+
describe("useAddConnectedWallet", () => {
|
18
|
+
// Mock the connection manager
|
19
|
+
const mockStorage = new MockStorage();
|
20
|
+
let manager: ConnectionManager;
|
21
|
+
|
22
|
+
// Create a wrapper component with the mocked context
|
23
|
+
const wrapper = ({ children }: { children: ReactNode }) => {
|
24
|
+
return (
|
25
|
+
<ConnectionManagerCtx.Provider value={manager}>
|
26
|
+
{children}
|
27
|
+
</ConnectionManagerCtx.Provider>
|
28
|
+
);
|
29
|
+
};
|
30
|
+
|
31
|
+
const wallet = createWalletAdapter({
|
32
|
+
adaptedAccount: TEST_ACCOUNT_A,
|
33
|
+
client: TEST_CLIENT,
|
34
|
+
chain: ethereum,
|
35
|
+
onDisconnect: () => {},
|
36
|
+
switchChain: () => {},
|
37
|
+
});
|
38
|
+
|
39
|
+
beforeEach(() => {
|
40
|
+
manager = createConnectionManager(mockStorage);
|
41
|
+
});
|
42
|
+
|
43
|
+
it("should connect a wallet to the connection manager", async () => {
|
44
|
+
const { result: statusResult } = renderHook(
|
45
|
+
() => useActiveWalletConnectionStatus(),
|
46
|
+
{
|
47
|
+
wrapper,
|
48
|
+
},
|
49
|
+
);
|
50
|
+
const { result } = renderHook(() => useConnect(), { wrapper });
|
51
|
+
expect(statusResult.current).toEqual("disconnected");
|
52
|
+
await result.current.connect(async () => wallet);
|
53
|
+
expect(statusResult.current).toEqual("connected");
|
54
|
+
|
55
|
+
// should add to connected wallets
|
56
|
+
expect(manager.connectedWallets.getValue()).toHaveLength(1);
|
57
|
+
expect(manager.connectedWallets.getValue()[0]).toEqual(wallet);
|
58
|
+
// should set the active wallet
|
59
|
+
expect(manager.activeWalletStore.getValue()).toEqual(wallet);
|
60
|
+
});
|
61
|
+
|
62
|
+
it("should handle a function that returns a wallet", async () => {
|
63
|
+
const { result: statusResult } = renderHook(
|
64
|
+
() => useActiveWalletConnectionStatus(),
|
65
|
+
{
|
66
|
+
wrapper,
|
67
|
+
},
|
68
|
+
);
|
69
|
+
const { result } = renderHook(() => useConnect(), { wrapper });
|
70
|
+
expect(statusResult.current).toEqual("disconnected");
|
71
|
+
await result.current.connect(async () => wallet);
|
72
|
+
expect(statusResult.current).toEqual("connected");
|
73
|
+
|
74
|
+
// should add to connected wallets
|
75
|
+
expect(manager.connectedWallets.getValue()).toHaveLength(1);
|
76
|
+
expect(manager.connectedWallets.getValue()[0]).toEqual(wallet);
|
77
|
+
// should set the active wallet
|
78
|
+
expect(manager.activeWalletStore.getValue()).toEqual(wallet);
|
79
|
+
});
|
80
|
+
|
81
|
+
it("should handle an error when connecting a wallet", async () => {
|
82
|
+
const { result: statusResult } = renderHook(
|
83
|
+
() => useActiveWalletConnectionStatus(),
|
84
|
+
{
|
85
|
+
wrapper,
|
86
|
+
},
|
87
|
+
);
|
88
|
+
expect(statusResult.current).toEqual("disconnected");
|
89
|
+
const { result } = renderHook(() => useConnect(), { wrapper });
|
90
|
+
await result.current.connect(async () => {
|
91
|
+
throw new Error("test");
|
92
|
+
});
|
93
|
+
|
94
|
+
expect(statusResult.current).toEqual("disconnected");
|
95
|
+
// should set the active wallet
|
96
|
+
expect(manager.activeWalletStore.getValue()).toEqual(undefined);
|
97
|
+
});
|
98
|
+
|
99
|
+
it("should throw an error when used outside of ThirdwebProvider", () => {
|
100
|
+
// Render the hook without a provider
|
101
|
+
expect(() => {
|
102
|
+
renderHook(() => useConnect());
|
103
|
+
}).toThrow("useConnect must be used within <ThirdwebProvider>");
|
104
|
+
});
|
105
|
+
});
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import { renderHook } from "@testing-library/react";
|
2
|
+
import type { ReactNode } from "react";
|
3
|
+
import { describe, expect, it } from "vitest";
|
4
|
+
import { MockStorage } from "../../../../../test/src/mocks/storage.js";
|
5
|
+
import { TEST_CLIENT } from "../../../../../test/src/test-clients.js";
|
6
|
+
import { TEST_ACCOUNT_A } from "../../../../../test/src/test-wallets.js";
|
7
|
+
import { createWalletAdapter } from "../../../../adapters/wallet-adapter.js";
|
8
|
+
import { ethereum } from "../../../../chains/chain-definitions/ethereum.js";
|
9
|
+
import { createConnectionManager } from "../../../../wallets/manager/index.js";
|
10
|
+
import { ConnectionManagerCtx } from "../../providers/connection-manager.js";
|
11
|
+
import { useSetActiveWallet } from "./useSetActiveWallet.js";
|
12
|
+
|
13
|
+
describe("useAddConnectedWallet", () => {
|
14
|
+
// Mock the connection manager
|
15
|
+
const mockStorage = new MockStorage();
|
16
|
+
const manager = createConnectionManager(mockStorage);
|
17
|
+
|
18
|
+
// Create a wrapper component with the mocked context
|
19
|
+
const wrapper = ({ children }: { children: ReactNode }) => {
|
20
|
+
return (
|
21
|
+
<ConnectionManagerCtx.Provider value={manager}>
|
22
|
+
{children}
|
23
|
+
</ConnectionManagerCtx.Provider>
|
24
|
+
);
|
25
|
+
};
|
26
|
+
|
27
|
+
const wallet = createWalletAdapter({
|
28
|
+
adaptedAccount: TEST_ACCOUNT_A,
|
29
|
+
client: TEST_CLIENT,
|
30
|
+
chain: ethereum,
|
31
|
+
onDisconnect: () => {},
|
32
|
+
switchChain: () => {},
|
33
|
+
});
|
34
|
+
|
35
|
+
it("should add a wallet to the connection manager", async () => {
|
36
|
+
// Render the hook
|
37
|
+
const { result } = renderHook(() => useSetActiveWallet(), { wrapper });
|
38
|
+
result.current(wallet);
|
39
|
+
|
40
|
+
// should add to connected wallets
|
41
|
+
expect(manager.connectedWallets.getValue()).toHaveLength(1);
|
42
|
+
expect(manager.connectedWallets.getValue()[0]).toEqual(wallet);
|
43
|
+
// should set the active wallet
|
44
|
+
expect(manager.activeWalletStore.getValue()).toEqual(wallet);
|
45
|
+
});
|
46
|
+
|
47
|
+
it("should throw an error when used outside of ThirdwebProvider", () => {
|
48
|
+
// Render the hook without a provider
|
49
|
+
expect(() => {
|
50
|
+
renderHook(() => useSetActiveWallet());
|
51
|
+
}).toThrow("useSetActiveWallet must be used within <ThirdwebProvider>");
|
52
|
+
});
|
53
|
+
});
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
2
|
+
import { act, renderHook } from "@testing-library/react";
|
3
|
+
import type React from "react";
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
5
|
+
import { TEST_CLIENT } from "~test/test-clients.js";
|
6
|
+
import { useConnectedWallets } from "../../../../react/core/hooks/wallets/useConnectedWallets.js";
|
7
|
+
import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
|
8
|
+
import { unlinkProfile } from "../../../../wallets/in-app/web/lib/auth/index.js";
|
9
|
+
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
|
10
|
+
import { useUnlinkProfile } from "./useUnlinkProfile.js";
|
11
|
+
|
12
|
+
vi.mock("../../../../wallets/in-app/web/lib/auth/index.js");
|
13
|
+
vi.mock("../../../core/hooks/wallets/useConnectedWallets.js");
|
14
|
+
|
15
|
+
describe("useUnlinkProfile", () => {
|
16
|
+
const queryClient = new QueryClient();
|
17
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
18
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
19
|
+
);
|
20
|
+
|
21
|
+
beforeEach(() => {
|
22
|
+
vi.clearAllMocks();
|
23
|
+
vi.spyOn(queryClient, "invalidateQueries");
|
24
|
+
});
|
25
|
+
|
26
|
+
const mockProfile = {} as unknown as Profile;
|
27
|
+
it("should call unlinkProfile with correct parameters", async () => {
|
28
|
+
vi.mocked(useConnectedWallets).mockReturnValue([]);
|
29
|
+
|
30
|
+
const { result } = renderHook(() => useUnlinkProfile(), {
|
31
|
+
wrapper,
|
32
|
+
});
|
33
|
+
const mutationFn = result.current.mutateAsync;
|
34
|
+
|
35
|
+
await act(async () => {
|
36
|
+
await mutationFn({ client: TEST_CLIENT, profileToUnlink: mockProfile });
|
37
|
+
});
|
38
|
+
|
39
|
+
expect(unlinkProfile).toHaveBeenCalledWith({
|
40
|
+
client: TEST_CLIENT,
|
41
|
+
ecosystem: undefined,
|
42
|
+
profileToUnlink: mockProfile,
|
43
|
+
});
|
44
|
+
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
|
45
|
+
queryKey: ["profiles"],
|
46
|
+
});
|
47
|
+
});
|
48
|
+
|
49
|
+
it("should include ecosystem if ecosystem wallet is found", async () => {
|
50
|
+
const mockWallet = {
|
51
|
+
id: "ecosystem.wallet-id",
|
52
|
+
getConfig: () => ({ partnerId: "partner-id" }),
|
53
|
+
} as unknown as Wallet;
|
54
|
+
vi.mocked(useConnectedWallets).mockReturnValue([mockWallet]);
|
55
|
+
|
56
|
+
const { result } = renderHook(() => useUnlinkProfile(), {
|
57
|
+
wrapper,
|
58
|
+
});
|
59
|
+
const mutationFn = result.current.mutateAsync;
|
60
|
+
|
61
|
+
await act(async () => {
|
62
|
+
await mutationFn({ client: TEST_CLIENT, profileToUnlink: mockProfile });
|
63
|
+
});
|
64
|
+
|
65
|
+
expect(unlinkProfile).toHaveBeenCalledWith({
|
66
|
+
client: TEST_CLIENT,
|
67
|
+
ecosystem: {
|
68
|
+
id: mockWallet.id,
|
69
|
+
partnerId: (mockWallet as Wallet<`ecosystem.${string}`>).getConfig()
|
70
|
+
?.partnerId,
|
71
|
+
},
|
72
|
+
profileToUnlink: mockProfile,
|
73
|
+
});
|
74
|
+
});
|
75
|
+
});
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
2
|
+
import type { ThirdwebClient } from "../../../../client/client.js";
|
3
|
+
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
|
4
|
+
import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
|
5
|
+
import type { Ecosystem } from "../../../../wallets/in-app/core/wallet/types.js";
|
6
|
+
import { unlinkProfile } from "../../../../wallets/in-app/web/lib/auth/index.js";
|
7
|
+
import { useConnectedWallets } from "../../../core/hooks/wallets/useConnectedWallets.js";
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Unlinks a web2 or web3 profile currently connected in-app or ecosystem account.
|
11
|
+
* **When a profile is unlinked from the account, it will no longer be able to be used to sign into the account.**
|
12
|
+
*
|
13
|
+
* @example
|
14
|
+
*
|
15
|
+
* ### Unlinking an email account
|
16
|
+
*
|
17
|
+
* ```jsx
|
18
|
+
* import { useUnlinkProfile } from "thirdweb/react";
|
19
|
+
*
|
20
|
+
* const { data: connectedProfiles, isLoading } = useProfiles({
|
21
|
+
* client: props.client,
|
22
|
+
* });
|
23
|
+
* const { mutate: unlinkProfile } = useUnlinkProfile();
|
24
|
+
*
|
25
|
+
* const onClick = () => {
|
26
|
+
* unlinkProfile({
|
27
|
+
* client,
|
28
|
+
* // Select any other profile you want to unlink
|
29
|
+
* profileToUnlink: connectedProfiles[1]
|
30
|
+
* });
|
31
|
+
* };
|
32
|
+
* ```
|
33
|
+
*
|
34
|
+
* @wallet
|
35
|
+
*/
|
36
|
+
export function useUnlinkProfile() {
|
37
|
+
const wallets = useConnectedWallets();
|
38
|
+
const queryClient = useQueryClient();
|
39
|
+
return useMutation({
|
40
|
+
mutationFn: async ({
|
41
|
+
client,
|
42
|
+
profileToUnlink,
|
43
|
+
}: { client: ThirdwebClient; profileToUnlink: Profile }) => {
|
44
|
+
const ecosystemWallet = wallets.find((w) => isEcosystemWallet(w));
|
45
|
+
const ecosystem: Ecosystem | undefined = ecosystemWallet
|
46
|
+
? {
|
47
|
+
id: ecosystemWallet.id,
|
48
|
+
partnerId: ecosystemWallet.getConfig()?.partnerId,
|
49
|
+
}
|
50
|
+
: undefined;
|
51
|
+
|
52
|
+
await unlinkProfile({
|
53
|
+
client,
|
54
|
+
ecosystem,
|
55
|
+
profileToUnlink,
|
56
|
+
});
|
57
|
+
},
|
58
|
+
onSuccess: () => {
|
59
|
+
queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
60
|
+
},
|
61
|
+
});
|
62
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
2
|
+
import { act, renderHook } from "@testing-library/react";
|
3
|
+
import type React from "react";
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
5
|
+
import { TEST_CLIENT } from "~test/test-clients.js";
|
6
|
+
import { useConnectedWallets } from "../../../../react/core/hooks/wallets/useConnectedWallets.js";
|
7
|
+
import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
|
8
|
+
import { unlinkProfile } from "../../../../wallets/in-app/web/lib/auth/index.js";
|
9
|
+
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
|
10
|
+
import { useUnlinkProfile } from "./useUnlinkProfile.js";
|
11
|
+
|
12
|
+
vi.mock("../../../../wallets/in-app/web/lib/auth/index.js");
|
13
|
+
vi.mock("../../../core/hooks/wallets/useConnectedWallets.js");
|
14
|
+
|
15
|
+
describe("useUnlinkProfile", () => {
|
16
|
+
const queryClient = new QueryClient();
|
17
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
18
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
19
|
+
);
|
20
|
+
|
21
|
+
beforeEach(() => {
|
22
|
+
vi.clearAllMocks();
|
23
|
+
vi.spyOn(queryClient, "invalidateQueries");
|
24
|
+
});
|
25
|
+
|
26
|
+
const mockProfile = {} as unknown as Profile;
|
27
|
+
it("should call unlinkProfile with correct parameters", async () => {
|
28
|
+
vi.mocked(useConnectedWallets).mockReturnValue([]);
|
29
|
+
|
30
|
+
const { result } = renderHook(() => useUnlinkProfile(), {
|
31
|
+
wrapper,
|
32
|
+
});
|
33
|
+
const mutationFn = result.current.mutateAsync;
|
34
|
+
|
35
|
+
await act(async () => {
|
36
|
+
await mutationFn({ client: TEST_CLIENT, profileToUnlink: mockProfile });
|
37
|
+
});
|
38
|
+
|
39
|
+
expect(unlinkProfile).toHaveBeenCalledWith({
|
40
|
+
client: TEST_CLIENT,
|
41
|
+
ecosystem: undefined,
|
42
|
+
profileToUnlink: mockProfile,
|
43
|
+
});
|
44
|
+
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
|
45
|
+
queryKey: ["profiles"],
|
46
|
+
});
|
47
|
+
});
|
48
|
+
|
49
|
+
it("should include ecosystem if ecosystem wallet is found", async () => {
|
50
|
+
const mockWallet = {
|
51
|
+
id: "ecosystem.wallet-id",
|
52
|
+
getConfig: () => ({ partnerId: "partner-id" }),
|
53
|
+
} as unknown as Wallet;
|
54
|
+
vi.mocked(useConnectedWallets).mockReturnValue([mockWallet]);
|
55
|
+
|
56
|
+
const { result } = renderHook(() => useUnlinkProfile(), {
|
57
|
+
wrapper,
|
58
|
+
});
|
59
|
+
const mutationFn = result.current.mutateAsync;
|
60
|
+
|
61
|
+
await act(async () => {
|
62
|
+
await mutationFn({ client: TEST_CLIENT, profileToUnlink: mockProfile });
|
63
|
+
});
|
64
|
+
|
65
|
+
expect(unlinkProfile).toHaveBeenCalledWith({
|
66
|
+
client: TEST_CLIENT,
|
67
|
+
ecosystem: {
|
68
|
+
id: mockWallet.id,
|
69
|
+
partnerId: (mockWallet as Wallet<`ecosystem.${string}`>).getConfig()
|
70
|
+
?.partnerId,
|
71
|
+
},
|
72
|
+
profileToUnlink: mockProfile,
|
73
|
+
});
|
74
|
+
});
|
75
|
+
});
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
2
|
+
import type { ThirdwebClient } from "../../../../client/client.js";
|
3
|
+
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
|
4
|
+
import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
|
5
|
+
import type { Ecosystem } from "../../../../wallets/in-app/core/wallet/types.js";
|
6
|
+
import { unlinkProfile } from "../../../../wallets/in-app/web/lib/auth/index.js";
|
7
|
+
import { useConnectedWallets } from "../../../core/hooks/wallets/useConnectedWallets.js";
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Unlinks a web2 or web3 profile currently connected in-app or ecosystem account.
|
11
|
+
* **When a profile is unlinked from the account, it will no longer be able to be used to sign into the account.**
|
12
|
+
*
|
13
|
+
* @example
|
14
|
+
*
|
15
|
+
* ### Unlinking an email account
|
16
|
+
*
|
17
|
+
* ```jsx
|
18
|
+
* import { useUnlinkProfile } from "thirdweb/react";
|
19
|
+
*
|
20
|
+
* const { data: connectedProfiles, isLoading } = useProfiles({
|
21
|
+
* client: props.client,
|
22
|
+
* });
|
23
|
+
* const { mutate: unlinkProfile } = useUnlinkProfile();
|
24
|
+
*
|
25
|
+
* const onClick = () => {
|
26
|
+
* unlinkProfile({
|
27
|
+
* client,
|
28
|
+
* // Select any other profile you want to unlink
|
29
|
+
* profileToUnlink: connectedProfiles[1]
|
30
|
+
* });
|
31
|
+
* };
|
32
|
+
* ```
|
33
|
+
*
|
34
|
+
* @wallet
|
35
|
+
*/
|
36
|
+
export function useUnlinkProfile() {
|
37
|
+
const wallets = useConnectedWallets();
|
38
|
+
const queryClient = useQueryClient();
|
39
|
+
return useMutation({
|
40
|
+
mutationFn: async ({
|
41
|
+
client,
|
42
|
+
profileToUnlink,
|
43
|
+
}: { client: ThirdwebClient; profileToUnlink: Profile }) => {
|
44
|
+
const ecosystemWallet = wallets.find((w) => isEcosystemWallet(w));
|
45
|
+
const ecosystem: Ecosystem | undefined = ecosystemWallet
|
46
|
+
? {
|
47
|
+
id: ecosystemWallet.id,
|
48
|
+
partnerId: ecosystemWallet.getConfig()?.partnerId,
|
49
|
+
}
|
50
|
+
: undefined;
|
51
|
+
|
52
|
+
await unlinkProfile({
|
53
|
+
client,
|
54
|
+
ecosystem,
|
55
|
+
profileToUnlink,
|
56
|
+
});
|
57
|
+
},
|
58
|
+
onSuccess: () => {
|
59
|
+
queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
60
|
+
},
|
61
|
+
});
|
62
|
+
}
|
@@ -131,5 +131,30 @@ describe("LinkedProfilesScreen", () => {
|
|
131
131
|
render(<LinkedProfilesScreen {...mockProps} />);
|
132
132
|
expect(screen.queryByText("Guest")).not.toBeInTheDocument();
|
133
133
|
});
|
134
|
+
|
135
|
+
it("should render unlink button when there are multiple profiles", () => {
|
136
|
+
vi.mocked(useProfiles).mockReturnValue({
|
137
|
+
data: [
|
138
|
+
{ type: "email", details: { email: "test@example.com" } },
|
139
|
+
{ type: "google", details: { email: "google@example.com" } },
|
140
|
+
],
|
141
|
+
isLoading: false,
|
142
|
+
// biome-ignore lint/suspicious/noExplicitAny: Mocking data
|
143
|
+
} as any);
|
144
|
+
|
145
|
+
render(<LinkedProfilesScreen {...mockProps} />);
|
146
|
+
expect(screen.getAllByLabelText("Unlink")).toHaveLength(2);
|
147
|
+
});
|
148
|
+
|
149
|
+
it("should not render unlink button when there is only one profile", () => {
|
150
|
+
vi.mocked(useProfiles).mockReturnValue({
|
151
|
+
data: [{ type: "email", details: { email: "test@example.com" } }],
|
152
|
+
isLoading: false,
|
153
|
+
// biome-ignore lint/suspicious/noExplicitAny: Mocking data
|
154
|
+
} as any);
|
155
|
+
|
156
|
+
render(<LinkedProfilesScreen {...mockProps} />);
|
157
|
+
expect(screen.queryByLabelText("Unlink")).not.toBeInTheDocument();
|
158
|
+
});
|
134
159
|
});
|
135
160
|
});
|
@@ -1,5 +1,7 @@
|
|
1
1
|
"use client";
|
2
|
+
import { Cross2Icon } from "@radix-ui/react-icons";
|
2
3
|
import type { ThirdwebClient } from "../../../../../client/client.js";
|
4
|
+
import { useUnlinkProfile } from "../../../../../react/web/hooks/wallets/useUnlinkProfile.js";
|
3
5
|
import { shortenAddress } from "../../../../../utils/address.js";
|
4
6
|
import type { Profile } from "../../../../../wallets/in-app/core/authentication/types.js";
|
5
7
|
import { fontSize, iconSize } from "../../../../core/design-system/index.js";
|
@@ -10,6 +12,7 @@ import { LoadingScreen } from "../../../wallets/shared/LoadingScreen.js";
|
|
10
12
|
import { Img } from "../../components/Img.js";
|
11
13
|
import { Spacer } from "../../components/Spacer.js";
|
12
14
|
import { Container, Line, ModalHeader } from "../../components/basic.js";
|
15
|
+
import { IconButton } from "../../components/buttons.js";
|
13
16
|
import { Text } from "../../components/text.js";
|
14
17
|
import { Blobbie } from "../Blobbie.js";
|
15
18
|
import { MenuButton } from "../MenuButton.js";
|
@@ -70,57 +73,61 @@ export function LinkedProfilesScreen(props: {
|
|
70
73
|
/>
|
71
74
|
</Container>
|
72
75
|
<Line />
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
>
|
82
|
-
<
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
>
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
))}
|
108
|
-
</Container>
|
109
|
-
<Spacer y="md" />
|
76
|
+
|
77
|
+
<Container
|
78
|
+
scrollY
|
79
|
+
style={{
|
80
|
+
height: "300px",
|
81
|
+
}}
|
82
|
+
>
|
83
|
+
<Spacer y="md" />
|
84
|
+
<Container px="sm">
|
85
|
+
<MenuButton
|
86
|
+
onClick={() => {
|
87
|
+
props.setScreen("link-profile");
|
88
|
+
}}
|
89
|
+
style={{
|
90
|
+
fontSize: fontSize.sm,
|
91
|
+
}}
|
92
|
+
>
|
93
|
+
<AddUserIcon size={iconSize.lg} />
|
94
|
+
<Text color="primaryText">
|
95
|
+
{props.locale.manageWallet.linkProfile}
|
96
|
+
</Text>
|
97
|
+
</MenuButton>
|
98
|
+
<Spacer y="xs" />
|
99
|
+
{/* Exclude guest as a profile */}
|
100
|
+
{connectedProfiles
|
101
|
+
?.filter((profile) => profile.type !== "guest")
|
102
|
+
.map((profile) => (
|
103
|
+
<LinkedProfile
|
104
|
+
key={`${JSON.stringify(profile)}`}
|
105
|
+
enableUnlinking={connectedProfiles.length > 1}
|
106
|
+
profile={profile}
|
107
|
+
client={props.client}
|
108
|
+
/>
|
109
|
+
))}
|
110
110
|
</Container>
|
111
|
-
|
111
|
+
<Spacer y="md" />
|
112
|
+
</Container>
|
112
113
|
</Container>
|
113
114
|
);
|
114
115
|
}
|
115
116
|
|
116
117
|
function LinkedProfile({
|
117
118
|
profile,
|
119
|
+
enableUnlinking,
|
118
120
|
client,
|
119
|
-
}: {
|
121
|
+
}: {
|
122
|
+
profile: Profile;
|
123
|
+
enableUnlinking: boolean;
|
124
|
+
client: ThirdwebClient;
|
125
|
+
}) {
|
120
126
|
const { data: socialProfiles } = useSocialProfiles({
|
121
127
|
client,
|
122
128
|
address: profile.details.address,
|
123
129
|
});
|
130
|
+
const { mutate: unlinkProfileMutation, isPending } = useUnlinkProfile();
|
124
131
|
|
125
132
|
return (
|
126
133
|
<MenuButton
|
@@ -128,6 +135,7 @@ function LinkedProfile({
|
|
128
135
|
fontSize: fontSize.sm,
|
129
136
|
cursor: "default",
|
130
137
|
}}
|
138
|
+
as={"div"}
|
131
139
|
disabled // disabled until we have more data to show on a dedicated profile screen
|
132
140
|
>
|
133
141
|
{socialProfiles?.some((p) => p.avatar) ? (
|
@@ -180,12 +188,46 @@ function LinkedProfile({
|
|
180
188
|
{socialProfiles?.find((p) => p.avatar)?.name ||
|
181
189
|
getProfileDisplayName(profile)}
|
182
190
|
</Text>
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
191
|
+
<div
|
192
|
+
style={{
|
193
|
+
display: "flex",
|
194
|
+
flexDirection: "row",
|
195
|
+
alignItems: "center",
|
196
|
+
gap: "8px",
|
197
|
+
}}
|
198
|
+
>
|
199
|
+
{socialProfiles?.find((p) => p.avatar)?.name &&
|
200
|
+
profile.details.address && (
|
201
|
+
<Text color="secondaryText" size="sm">
|
202
|
+
{shortenAddress(profile.details.address, 4)}
|
203
|
+
</Text>
|
204
|
+
)}
|
205
|
+
{enableUnlinking && (
|
206
|
+
<IconButton
|
207
|
+
autoFocus
|
208
|
+
type="button"
|
209
|
+
aria-label="Unlink"
|
210
|
+
onClick={() =>
|
211
|
+
unlinkProfileMutation({
|
212
|
+
client,
|
213
|
+
profileToUnlink: profile,
|
214
|
+
})
|
215
|
+
}
|
216
|
+
style={{
|
217
|
+
pointerEvents: "auto",
|
218
|
+
}}
|
219
|
+
disabled={isPending}
|
220
|
+
>
|
221
|
+
<Cross2Icon
|
222
|
+
width={iconSize.md}
|
223
|
+
height={iconSize.md}
|
224
|
+
style={{
|
225
|
+
color: "inherit",
|
226
|
+
}}
|
227
|
+
/>
|
228
|
+
</IconButton>
|
188
229
|
)}
|
230
|
+
</div>
|
189
231
|
</div>
|
190
232
|
</MenuButton>
|
191
233
|
);
|
@@ -143,7 +143,7 @@ export function ChainIcon({
|
|
143
143
|
}
|
144
144
|
// Check if the chain object already has "icon"
|
145
145
|
if (chain.icon?.url) {
|
146
|
-
return chain.icon.url;
|
146
|
+
return resolveScheme({ uri: chain.icon.url, client });
|
147
147
|
}
|
148
148
|
const possibleUrl = await getChainMetadata(chain).then(
|
149
149
|
(data) => data.icon?.url,
|