Skip to content

Commit 63d0777

Browse files
feat: add useLinkProfile hook and update profile linking docs + playground (#4906)
1 parent a94f981 commit 63d0777

File tree

28 files changed

+575
-60
lines changed

28 files changed

+575
-60
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: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
export const dynamic = "force-dynamic";
12
import { CodeExample } from "@/components/code/code-example";
23
import { EcosystemConnectEmbed } from "../../../../components/in-app-wallet/ecosystem";
4+
import { Profiles } from "../../../../components/in-app-wallet/profile-sections";
35
import ThirdwebProvider from "../../../../components/thirdweb-provider";
46

57
export default function Page() {
68
return (
79
<ThirdwebProvider>
8-
<section className="space-y-8">
10+
<section className="space-y-6">
911
<AnyAuth />
1012
</section>
13+
<section className="space-y-8">
14+
<Profiles />
15+
</section>
1116
</ThirdwebProvider>
1217
);
1318
}
@@ -17,7 +22,7 @@ function AnyAuth() {
1722
<>
1823
<div className="space-y-2">
1924
<h2 className="font-semibold text-2xl tracking-tight sm:text-3xl">
20-
Your own Ecosystem
25+
Build your own Ecosystem
2126
</h2>
2227
<p className="max-w-[600px]">
2328
Build a public or permissioned ecosystem by allowing third party apps

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
export const dynamic = "force-dynamic";
12
import { CodeExample } from "@/components/code/code-example";
23
import { InAppConnectEmbed } from "../../../components/in-app-wallet/connect-button";
4+
import { Profiles } from "../../../components/in-app-wallet/profile-sections";
35
import ThirdwebProvider from "../../../components/thirdweb-provider";
46

57
export default function Page() {
@@ -8,6 +10,9 @@ export default function Page() {
810
<section className="space-y-8">
911
<AnyAuth />
1012
</section>
13+
<section className="mt-8 space-y-8">
14+
<Profiles />
15+
</section>
1116
</ThirdwebProvider>
1217
);
1318
}

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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ export const navLinks: SidebarLink[] = [
4242
expanded: true,
4343
links: [
4444
{
45-
name: "Any auth method",
45+
name: "Any Auth",
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,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { CodeExample } from "@/components/code/code-example";
2+
import { LinkAccount, LinkedAccounts } from "./profiles";
3+
4+
export function Profiles() {
5+
return (
6+
<>
7+
<div className="space-y-2">
8+
<h2 className="font-semibold text-xl tracking-tight sm:text-2xl">
9+
View Linked Profiles
10+
</h2>
11+
<p className="max-w-[600px]">
12+
View all web2 and web3 linked profiles for a user along with specific
13+
details for each profile type, including name, email, profile picture
14+
and more.
15+
</p>
16+
</div>
17+
18+
<CodeExample
19+
preview={<LinkedAccounts />}
20+
code={`import { useProfiles } from "thirdweb/react";
21+
22+
function App() {
23+
const { data: profiles } = useProfiles({
24+
client,
25+
});
26+
27+
return (
28+
<div>
29+
{profiles?.map((profile) => (
30+
<div key={profile.type}>
31+
<ProfileCard profile={profile} />
32+
</div>
33+
))}
34+
</div>
35+
);
36+
};`}
37+
lang="tsx"
38+
/>
39+
40+
<div className="space-y-2">
41+
<h2 className="font-semibold text-xl tracking-tight sm:text-2xl">
42+
Link another profile
43+
</h2>
44+
<p className="max-w-[600px]">
45+
Link a web2 or web3 profile to the connected account.
46+
<br />
47+
You can do this with hooks like shown here or from the prebuilt
48+
connect UI.
49+
</p>
50+
</div>
51+
52+
<CodeExample
53+
preview={<LinkAccount />}
54+
code={`import { useLinkProfile } from "thirdweb/react";
55+
56+
function App() {
57+
const { mutate: linkProfile, isPending, error } = useLinkProfile();
58+
59+
return (
60+
<div>
61+
<button
62+
onClick={() => linkProfile({
63+
client: THIRDWEB_CLIENT,
64+
strategy: "wallet",
65+
wallet: createWallet("com.coinbase.wallet"),
66+
chain: baseSepolia,
67+
})}
68+
>
69+
Link Coinbase Wallet
70+
</button>
71+
</div>
72+
);
73+
};`}
74+
lang="tsx"
75+
/>
76+
</>
77+
);
78+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"use client";
2+
import { baseSepolia } from "thirdweb/chains";
3+
import { useActiveAccount, useLinkProfile, useProfiles } from "thirdweb/react";
4+
import { type WalletId, createWallet } from "thirdweb/wallets";
5+
import { THIRDWEB_CLIENT } from "../../lib/client";
6+
import CodeClient, { CodeLoading } from "../code/code.client";
7+
import { Button } from "../ui/button";
8+
9+
export function LinkedAccounts() {
10+
const { data: profiles } = useProfiles({
11+
client: THIRDWEB_CLIENT,
12+
});
13+
14+
return (
15+
<div className="flex flex-col gap-4 p-6">
16+
{profiles ? (
17+
<CodeClient
18+
code={JSON.stringify(profiles, null, 2)}
19+
lang={"json"}
20+
loader={<CodeLoading />}
21+
/>
22+
) : (
23+
<p>Login to see linked profiles</p>
24+
)}
25+
</div>
26+
);
27+
}
28+
29+
export function LinkAccount() {
30+
const { mutate: linkProfile, isPending, error } = useLinkProfile();
31+
const account = useActiveAccount();
32+
const linkWallet = async (walletId: WalletId) => {
33+
linkProfile({
34+
client: THIRDWEB_CLIENT,
35+
strategy: "wallet",
36+
wallet: createWallet(walletId),
37+
chain: baseSepolia,
38+
});
39+
};
40+
41+
const linkPasskey = async () => {
42+
linkProfile({
43+
client: THIRDWEB_CLIENT,
44+
strategy: "passkey",
45+
type: "sign-up",
46+
});
47+
};
48+
49+
return (
50+
<div className="flex flex-col gap-4 p-6">
51+
{account ? (
52+
<>
53+
{isPending ? (
54+
<p>Linking...</p>
55+
) : (
56+
<>
57+
{/*
58+
TODO make cb smart wallet linking work
59+
<Button
60+
variant="default"
61+
onClick={() => linkWallet("com.coinbase.wallet")}
62+
className="rounded-full p-6"
63+
disabled={isPending}
64+
>
65+
Link Coinbase Wallet
66+
</Button> */}
67+
<Button
68+
variant="default"
69+
onClick={() => linkWallet("io.metamask")}
70+
className="rounded-full p-6"
71+
disabled={isPending}
72+
>
73+
Link MetaMask
74+
</Button>
75+
<Button
76+
variant="default"
77+
onClick={linkPasskey}
78+
className="rounded-full p-6"
79+
disabled={isPending}
80+
>
81+
Link Passkey
82+
</Button>
83+
</>
84+
)}
85+
{error && <p className="text-red-500">Error: {error.message}</p>}
86+
</>
87+
) : (
88+
<p>Login to link another account.</p>
89+
)}
90+
</div>
91+
);
92+
}

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)