Skip to content

Commit 7479f9c

Browse files
feat: add useLinkProfile hook and update profile linking functionality
1 parent e91581a commit 7479f9c

File tree

24 files changed

+558
-44
lines changed

24 files changed

+558
-44
lines changed

.changeset/hot-adults-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
New `useLinkProfile()` hook to link profiles to inapp and ecosystem accounts

apps/playground-web/src/app/connect/in-app-wallet/ecosystem/page.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { CodeExample } from "@/components/code/code-example";
22
import { EcosystemConnectEmbed } from "../../../../components/in-app-wallet/ecosystem";
33
import ThirdwebProvider from "../../../../components/thirdweb-provider";
4+
import { Profiles } from "../page";
45

56
export default function Page() {
67
return (
78
<ThirdwebProvider>
8-
<section className="space-y-8">
9+
<section className="space-y-6">
910
<AnyAuth />
1011
</section>
12+
<section className="space-y-8">
13+
<Profiles />
14+
</section>
1115
</ThirdwebProvider>
1216
);
1317
}
@@ -17,7 +21,7 @@ function AnyAuth() {
1721
<>
1822
<div className="space-y-2">
1923
<h2 className="font-semibold text-2xl tracking-tight sm:text-3xl">
20-
Your own Ecosystem
24+
Build your own Ecosystem
2125
</h2>
2226
<p className="max-w-[600px]">
2327
Build a public or permissioned ecosystem by allowing third party apps

apps/playground-web/src/app/connect/in-app-wallet/page.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { CodeExample } from "@/components/code/code-example";
22
import { InAppConnectEmbed } from "../../../components/in-app-wallet/connect-button";
3+
import {
4+
LinkAccount,
5+
LinkedAccounts,
6+
} from "../../../components/in-app-wallet/profiles";
37
import ThirdwebProvider from "../../../components/thirdweb-provider";
48

59
export default function Page() {
@@ -8,6 +12,9 @@ export default function Page() {
812
<section className="space-y-8">
913
<AnyAuth />
1014
</section>
15+
<section className="mt-8 space-y-8">
16+
<Profiles />
17+
</section>
1118
</ThirdwebProvider>
1219
);
1320
}
@@ -67,3 +74,79 @@ function AnyAuth() {
6774
</>
6875
);
6976
}
77+
78+
export function Profiles() {
79+
return (
80+
<>
81+
<div className="space-y-2">
82+
<h2 className="font-semibold text-xl tracking-tight sm:text-2xl">
83+
View Linked Profiles
84+
</h2>
85+
<p className="max-w-[600px]">
86+
View all web2 and web3 linked profiles for a user along with specific
87+
details for each profile type, including name, email, profile picture
88+
and more.
89+
</p>
90+
</div>
91+
92+
<CodeExample
93+
preview={<LinkedAccounts />}
94+
code={`import { useProfiles } from "thirdweb/react";
95+
96+
function App() {
97+
const { data: profiles } = useProfiles({
98+
client,
99+
});
100+
101+
return (
102+
<div>
103+
{profiles?.map((profile) => (
104+
<div key={profile.type}>
105+
<ProfileCard profile={profile} />
106+
</div>
107+
))}
108+
</div>
109+
);
110+
};`}
111+
lang="tsx"
112+
/>
113+
114+
<div className="space-y-2">
115+
<h2 className="font-semibold text-xl tracking-tight sm:text-2xl">
116+
Link another profile
117+
</h2>
118+
<p className="max-w-[600px]">
119+
Link a web2 or web3 profile to the connected account.
120+
<br />
121+
You can do this with hooks like shown here or from the prebuilt
122+
connect UI.
123+
</p>
124+
</div>
125+
126+
<CodeExample
127+
preview={<LinkAccount />}
128+
code={`import { useLinkProfile } from "thirdweb/react";
129+
130+
function App() {
131+
const { mutate: linkProfile, isPending, error } = useLinkProfile();
132+
133+
return (
134+
<div>
135+
<button
136+
onClick={() => linkProfile({
137+
client: THIRDWEB_CLIENT,
138+
strategy: "wallet",
139+
wallet: createWallet("com.coinbase.wallet"),
140+
chain: baseSepolia,
141+
})}
142+
>
143+
Link Coinbase Wallet
144+
</button>
145+
</div>
146+
);
147+
};`}
148+
lang="tsx"
149+
/>
150+
</>
151+
);
152+
}

apps/playground-web/src/app/connect/sign-in/components/CodeGen.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
import { Suspense, lazy } from "react";
2-
import { LoadingDots } from "../../../../components/ui/LoadingDots";
2+
import { CodeLoading } from "../../../../components/code/code.client";
33
import type { ConnectPlaygroundOptions } from "./types";
44

55
const CodeClient = lazy(
66
() => import("../../../../components/code/code.client"),
77
);
88

9-
function CodeLoading() {
10-
return (
11-
<div className="flex h-[300px] items-center justify-center xl:h-[calc(100vh-100px)]">
12-
<LoadingDots />
13-
</div>
14-
);
15-
}
16-
179
export function CodeGen(props: {
1810
connectOptions: ConnectPlaygroundOptions;
1911
}) {

apps/playground-web/src/app/navLinks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const navLinks: SidebarLink[] = [
4646
href: "/connect/in-app-wallet",
4747
},
4848
{
49-
name: "Your own Ecosystem",
49+
name: "Ecosystems",
5050
href: "/connect/in-app-wallet/ecosystem",
5151
},
5252
{

apps/playground-web/src/components/code/code.client.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { keepPreviousData, useQuery } from "@tanstack/react-query";
22
import type { BundledLanguage } from "shiki";
3+
import { LoadingDots } from "../ui/LoadingDots";
34
import { RenderCode } from "./RenderCode";
45
import { getCodeHtml } from "./getCodeHtml";
56

@@ -14,6 +15,14 @@ export type CodeProps = {
1415
scrollableClassName?: string;
1516
};
1617

18+
export function CodeLoading() {
19+
return (
20+
<div className="flex h-[300px] items-center justify-center xl:h-[calc(100vh-100px)]">
21+
<LoadingDots />
22+
</div>
23+
);
24+
}
25+
1726
export const CodeClient: React.FC<CodeProps> = ({
1827
code,
1928
lang,

apps/playground-web/src/components/in-app-wallet/ecosystem.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
"use client";
2-
import type { ConnectButtonProps } from "thirdweb/react";
3-
import { ecosystemWallet } from "thirdweb/wallets";
2+
import { baseSepolia } from "thirdweb/chains";
3+
import {
4+
type ConnectButtonProps,
5+
useActiveAccount,
6+
useLinkProfile,
7+
useProfiles,
8+
} from "thirdweb/react";
9+
import { type WalletId, createWallet, ecosystemWallet } from "thirdweb/wallets";
10+
import { THIRDWEB_CLIENT } from "../../lib/client";
11+
import CodeClient, { CodeLoading } from "../code/code.client";
412
import { StyledConnectEmbed } from "../styled-connect-embed";
13+
import { Button } from "../ui/button";
514

615
const getEcosystem = () => {
716
if (
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"use client";
2+
import { baseSepolia } from "thirdweb/chains";
3+
import {
4+
type ConnectButtonProps,
5+
useActiveAccount,
6+
useLinkProfile,
7+
useProfiles,
8+
} from "thirdweb/react";
9+
import { type WalletId, createWallet } from "thirdweb/wallets";
10+
import { THIRDWEB_CLIENT } from "../../lib/client";
11+
import CodeClient, { CodeLoading } from "../code/code.client";
12+
import { Button } from "../ui/button";
13+
14+
export function LinkedAccounts(
15+
props?: Omit<ConnectButtonProps, "client" | "theme">,
16+
) {
17+
const { data: profiles } = useProfiles({
18+
client: THIRDWEB_CLIENT,
19+
});
20+
21+
return (
22+
<div className="flex flex-col gap-4 p-6">
23+
{profiles ? (
24+
<CodeClient
25+
code={JSON.stringify(profiles, null, 2)}
26+
lang={"json"}
27+
loader={<CodeLoading />}
28+
/>
29+
) : (
30+
<p>Login to see linked profiles</p>
31+
)}
32+
</div>
33+
);
34+
}
35+
36+
export function LinkAccount(
37+
props?: Omit<ConnectButtonProps, "client" | "theme">,
38+
) {
39+
const { mutate: linkProfile, isPending, error } = useLinkProfile();
40+
const account = useActiveAccount();
41+
const linkWallet = async (walletId: WalletId) => {
42+
linkProfile({
43+
client: THIRDWEB_CLIENT,
44+
strategy: "wallet",
45+
wallet: createWallet(walletId),
46+
chain: baseSepolia,
47+
});
48+
};
49+
50+
const linkPasskey = async () => {
51+
linkProfile({
52+
client: THIRDWEB_CLIENT,
53+
strategy: "passkey",
54+
type: "sign-up",
55+
});
56+
};
57+
58+
return (
59+
<div className="flex flex-col gap-4 p-6">
60+
{account ? (
61+
<>
62+
{isPending ? (
63+
<p>Linking...</p>
64+
) : (
65+
<>
66+
<Button
67+
variant="default"
68+
onClick={() => linkWallet("com.coinbase.wallet")}
69+
className="rounded-full p-6"
70+
disabled={isPending}
71+
>
72+
Link Coinbase Wallet
73+
</Button>
74+
<Button
75+
variant="default"
76+
onClick={() => linkWallet("io.metamask")}
77+
className="rounded-full p-6"
78+
disabled={isPending}
79+
>
80+
Link MetaMask
81+
</Button>
82+
<Button
83+
variant="default"
84+
onClick={linkPasskey}
85+
className="rounded-full p-6"
86+
disabled={isPending}
87+
>
88+
Link Passkey
89+
</Button>
90+
</>
91+
)}
92+
{error && <p className="text-red-500">Error: {error.message}</p>}
93+
</>
94+
) : (
95+
<p>Login to link another account.</p>
96+
)}
97+
</div>
98+
);
99+
}

apps/portal/src/app/react-native/v5/sidebar.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ export const sidebar: SideBar = {
121121
},
122122
],
123123
},
124+
{
125+
name: "Ecosystem Wallets",
126+
links: [
127+
{
128+
name: "React API",
129+
href: "/react/v5/ecosystem-wallet/get-started",
130+
icon: <ReactIcon />,
131+
},
132+
],
133+
},
124134
{
125135
name: "Account Abstraction",
126136
links: [
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {
2+
Tabs,
3+
TabsList,
4+
TabsTrigger,
5+
TabsContent,
6+
DocImage,
7+
createMetadata,
8+
} from "@doc";
9+
10+
export const metadata = createMetadata({
11+
title: "Connect users with Ecosystem Wallets",
12+
description:
13+
"use the prebuilt connect UI components to authenticate users and connect ecosystem wallets",
14+
image: {
15+
title: "Connect users with Ecosystem Wallets",
16+
icon: "wallets",
17+
},
18+
});
19+
20+
# Connect Users to your Ecosystem
21+
22+
## Using the Connect UI components
23+
24+
If you're building a [React website](/typescript/react/v5/ConnectButton), [React Native app](/react-native/v5/), or [Unity game](/unity/ConnectWallet) you can use the prebuilt connect UI components to authenticate users and connect their wallets accross your ecosystem.
25+
26+
```jsx
27+
import { ThirdwebProvider, ConnectButton } from "thirdweb/react";
28+
import { inAppWallet } from "thirdweb/wallets";
29+
30+
const client = createThirdwebClient({ clientId });
31+
const wallets = [ecosystemWallet("ecosystem.your-ecosystem-id")];
32+
33+
export default function App() {
34+
return (
35+
<ThirdwebProvider>
36+
<ConnectButton client={client} wallets={wallets} />
37+
</ThirdwebProvider>
38+
);
39+
}
40+
```
41+
42+
## Using your own UI
43+
44+
You can also build your own UI using the low-level hooks and functions. Remember to wrap your app in a `ThirdwebProvider` to ensure that the wallet is available to all components in your app.
45+
46+
```tsx
47+
import { ecosystemWallet } from "thirdweb/wallets";
48+
49+
const wallet = ecosystemWallet("ecosystem.your-ecosystem-id");
50+
51+
const LoginComponent = () => {
52+
const { connect, isLoading } = useConnect();
53+
54+
return <button onClick={() => connect(async () => {
55+
await wallet.connect({
56+
client,
57+
strategy: "discord", // or any supported auth strategy
58+
})
59+
return wallet;
60+
})}>Connect</button>;
61+
};
62+
```
63+
64+
## Passing a partner ID
65+
66+
For closed ecosystems, you can pass a valid `partnerId` to the `ecosystemWallet` provided by the ecosystem owner.
67+
68+
```tsx
69+
const wallet = ecosystemWallet("ecosystem.your-ecosystem-id", {
70+
partnerId: "your-partner-id",
71+
});
72+
```

0 commit comments

Comments
 (0)