Skip to content

Commit 3c02673

Browse files
committed
refactor: move to custom hook and add test
1 parent 94abbb0 commit 3c02673

File tree

7 files changed

+284
-37
lines changed

7 files changed

+284
-37
lines changed

packages/thirdweb/src/exports/react.native.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export { useCallsStatus } from "../react/core/hooks/wallets/useCallsStatus.js";
2727
export { useWalletBalance } from "../react/core/hooks/others/useWalletBalance.js";
2828
export { useProfiles } from "../react/native/hooks/wallets/useProfiles.js";
2929
export { useLinkProfile } from "../react/native/hooks/wallets/useLinkProfile.js";
30+
export { useUnlinkProfile } from "../react/native/hooks/wallets/useUnlinkProfile.js";
3031

3132
// contract
3233
export { useReadContract } from "../react/core/hooks/contract/useReadContract.js";

packages/thirdweb/src/exports/react.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export { useCallsStatus } from "../react/core/hooks/wallets/useCallsStatus.js";
5858
export { useWalletBalance } from "../react/core/hooks/others/useWalletBalance.js";
5959
export { useProfiles } from "../react/web/hooks/wallets/useProfiles.js";
6060
export { useLinkProfile } from "../react/web/hooks/wallets/useLinkProfile.js";
61+
export { useUnlinkProfile } from "../react/web/hooks/wallets/useUnlinkProfile.js";
6162

6263
// chain hooks
6364
export { useChainMetadata } from "../react/core/hooks/others/useChainQuery.js";
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/LinkedProfilesScreen.tsx

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
"use client";
22
import { Cross2Icon } from "@radix-ui/react-icons";
3-
import { useMutation, useQueryClient } from "@tanstack/react-query";
3+
import { useUnlinkProfile } from "src/react/web/hooks/wallets/useUnlinkProfile.js";
44
import type { ThirdwebClient } from "../../../../../client/client.js";
5-
import { useActiveWallet } from "../../../../../react/core/hooks/wallets/useActiveWallet.js";
65
import { shortenAddress } from "../../../../../utils/address.js";
7-
import { isEcosystemWallet } from "../../../../../wallets/ecosystem/is-ecosystem-wallet.js";
86
import type { Profile } from "../../../../../wallets/in-app/core/authentication/types.js";
9-
import type { Ecosystem } from "../../../../../wallets/in-app/core/wallet/types.js";
10-
import { unlinkProfile } from "../../../../../wallets/in-app/web/lib/auth/index.js";
11-
import type { Wallet } from "../../../../../wallets/interfaces/wallet.js";
12-
import type { EcosystemWalletId } from "../../../../../wallets/wallet-types.js";
137
import { fontSize, iconSize } from "../../../../core/design-system/index.js";
148
import { useSocialProfiles } from "../../../../core/social/useSocialProfiles.js";
159
import { getSocialIcon } from "../../../../core/utils/walletIcon.js";
@@ -129,39 +123,11 @@ function LinkedProfile({
129123
enableUnlinking: boolean;
130124
client: ThirdwebClient;
131125
}) {
132-
const activeWallet = useActiveWallet();
133126
const { data: socialProfiles } = useSocialProfiles({
134127
client,
135128
address: profile.details.address,
136129
});
137-
const queryClient = useQueryClient();
138-
const { mutate: unlinkProfileMutation, isPending } = useMutation({
139-
mutationFn: async () => {
140-
let ecosystem: Ecosystem | undefined;
141-
142-
if (!activeWallet) {
143-
throw new Error("No active wallet found");
144-
}
145-
146-
if (isEcosystemWallet(activeWallet)) {
147-
const ecosystemWallet = activeWallet as Wallet<EcosystemWalletId>;
148-
const partnerId = ecosystemWallet.getConfig()?.partnerId;
149-
ecosystem = {
150-
id: ecosystemWallet.id,
151-
partnerId,
152-
};
153-
}
154-
155-
await unlinkProfile({
156-
client,
157-
ecosystem,
158-
profileToUnlink: profile,
159-
});
160-
},
161-
onSuccess: () => {
162-
queryClient.invalidateQueries({ queryKey: ["profiles"] });
163-
},
164-
});
130+
const { mutate: unlinkProfileMutation, isPending } = useUnlinkProfile();
165131

166132
return (
167133
<MenuButton
@@ -241,7 +207,12 @@ function LinkedProfile({
241207
autoFocus
242208
type="button"
243209
aria-label="Unlink"
244-
onClick={() => unlinkProfileMutation()}
210+
onClick={() =>
211+
unlinkProfileMutation({
212+
client,
213+
profileToUnlink: profile,
214+
})
215+
}
245216
style={{
246217
pointerEvents: "auto",
247218
}}

0 commit comments

Comments
 (0)