Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/real-cars-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"thirdweb": minor
---

Add SiteLink component for creating wallet-aware links between thirdweb-enabled sites. This component automatically adds wallet connection parameters to the target URL when a wallet is connected, enabling seamless wallet state sharing between sites.

Example:
```tsx
import { SiteLink } from "thirdweb/react";

function App() {
return (
<SiteLink
href="https://thirdweb.com"
client={thirdwebClient}
ecosystem={{ id: "ecosystem.thirdweb" }}
>
Visit thirdweb.com with connected wallet
</SiteLink>
);
}
```
3 changes: 2 additions & 1 deletion packages/thirdweb/src/exports/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,5 +196,6 @@ export type {
LensProfile,
} from "../social/types.js";

// Site Embed
// Site Embed and Linking
export { SiteEmbed } from "../react/web/ui/SiteEmbed.js";
export { SiteLink } from "../react/web/ui/SiteLink.js";
6 changes: 3 additions & 3 deletions packages/thirdweb/src/react/web/ui/SiteEmbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import { useActiveWallet } from "../../core/hooks/wallets/useActiveWallet.js";
*
* @param {Object} props - The props to pass to the iframe
* @param {String} props.src - The URL of the site to embed
* @param {ThirdwebClient} props.client - The client to use for the embedded site
* @param {Ecosystem} [props.ecosystem] - The ecosystem to use for the embedded site
* @param {ThirdwebClient} props.client - The current site's thirdweb client
* @param {Ecosystem} [props.ecosystem] - The ecosystem to use for the wallet connection in the embedded site
*
* @example
* ```tsx
* import { SiteEmbed } from "thirdweb/react";
*
* <SiteEmbed src="https://thirdweb.com" clientId="thirdweb-client-id" />
* <SiteEmbed src="https://thirdweb.com" client={thirdwebClient} ecosystem={{ id: "ecosystem.thirdweb" }} />
* ```
*/
export function SiteEmbed({
Expand Down
48 changes: 48 additions & 0 deletions packages/thirdweb/src/react/web/ui/SiteLink.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, expect, it } from "vitest";
import { render, waitFor } from "../../../../test/src/react-render.js";
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
import { SiteLink } from "./SiteLink.js";

describe("SiteLink", () => {
it("renders anchor with correct href", () => {
const testUrl = "https://example.com/";
const { container } = render(
<SiteLink href={testUrl} client={TEST_CLIENT}>
Test Link
</SiteLink>,
);

const anchor = container.querySelector("a");
expect(anchor).toBeTruthy();
expect(anchor?.href).toBe(testUrl);
expect(anchor?.textContent).toBe("Test Link");
});

it("throws error if clientId is not provided", () => {
const testUrl = "https://example.com/";
expect(() =>
render(
// biome-ignore lint/suspicious/noExplicitAny: testing invalid input
<SiteLink href={testUrl} client={{} as any}>
Test Link
</SiteLink>,
),
).toThrow("The SiteLink client must have a clientId");
});

it("adds wallet params to url when wallet is connected", async () => {
const testUrl = "https://example.com/";
const { container } = render(
<SiteLink href={testUrl} client={TEST_CLIENT}>
Test Link
</SiteLink>,
{
setConnectedWallet: true,
},
);

const anchor = container.querySelector("a");
expect(anchor).toBeTruthy();
await waitFor(() => expect(anchor?.href).toContain("walletId="));
});
});
86 changes: 86 additions & 0 deletions packages/thirdweb/src/react/web/ui/SiteLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import type { ThirdwebClient } from "../../../client/client.js";
import { getLastAuthProvider } from "../../../react/core/utils/storage.js";
import { webLocalStorage } from "../../../utils/storage/webStorage.js";
import { isEcosystemWallet } from "../../../wallets/ecosystem/is-ecosystem-wallet.js";
import { ClientScopedStorage } from "../../../wallets/in-app/core/authentication/client-scoped-storage.js";
import type { Ecosystem } from "../../../wallets/in-app/core/wallet/types.js";
import { useActiveWallet } from "../../core/hooks/wallets/useActiveWallet.js";

/**
* Creates a link to another thirdweb-supported site with wallet connection parameters.
*
* @note The target site must support the connected wallet (ecosystem or in-app).
*
* @param {Object} props - The props to pass to the anchor tag
* @param {String} props.href - The URL of the site to link to
* @param {ThirdwebClient} props.client - The current site's thirdweb client
* @param {Ecosystem} [props.ecosystem] - The ecosystem to use for the wallet connection in the target site
* @param {React.ReactNode} props.children - The content to render inside the link
*
* @example
* ```tsx
* import { SiteLink } from "thirdweb/react";
*
* <SiteLink href="https://thirdweb.com" client={thirdwebClient} ecosystem={{ id: "ecosystem.thirdweb" }}>
* Visit Site
* </SiteLink>
* ```
*/
export function SiteLink({
href,
client,
ecosystem,
children,
...props
}: {
href: string;
client: ThirdwebClient;
ecosystem?: Ecosystem;
children: React.ReactNode;
} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href">) {
if (!client.clientId) {
throw new Error("The SiteLink client must have a clientId");
}

const activeWallet = useActiveWallet();
const walletId = activeWallet?.id;

const {
data: { authProvider, authCookie } = {},
} = useQuery({
queryKey: ["site-link", walletId, href, client.clientId, ecosystem],
enabled:
activeWallet && (isEcosystemWallet(activeWallet) || walletId === "inApp"),
queryFn: async () => {
const storage = new ClientScopedStorage({
storage: webLocalStorage,
clientId: client.clientId,
ecosystem,
});

const authProvider = await getLastAuthProvider(webLocalStorage);
const authCookie = await storage.getAuthCookie();

return { authProvider, authCookie };
},
});

const url = new URL(href);
if (walletId) {
url.searchParams.set("walletId", walletId);
}
if (authProvider) {
url.searchParams.set("authProvider", authProvider);

Check warning on line 75 in packages/thirdweb/src/react/web/ui/SiteLink.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/ui/SiteLink.tsx#L75

Added line #L75 was not covered by tests
}
if (authCookie) {
url.searchParams.set("authCookie", authCookie);

Check warning on line 78 in packages/thirdweb/src/react/web/ui/SiteLink.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/ui/SiteLink.tsx#L78

Added line #L78 was not covered by tests
}

return (
<a href={encodeURI(url.toString())} {...props}>
{children}
</a>
);
}
Loading