Skip to content

Commit f2b4fe6

Browse files
committed
Feature: Checkout page (#6954)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the checkout experience in the `dashboard` app by integrating analytics and improving the layout and structure of the checkout components. ### Detailed summary - Removed a redirect from `"/checkout"` to `"/connect"` in `apps/dashboard/redirects.js`. - Added `THIRDWEB_ANALYTICS_DOMAIN` to `urls.ts`. - Created `Providers` component to wrap children with `ThirdwebProvider`. - Enhanced `CheckoutLayout` with `ThemeProvider` and layout structure. - Updated `CheckoutEmbed` to utilize new analytics domain and handle payment success. - Implemented a new `RoutesPage` for handling checkout logic and parameters. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 6dd2b09 commit f2b4fe6

File tree

6 files changed

+229
-5
lines changed

6 files changed

+229
-5
lines changed

apps/dashboard/redirects.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,6 @@ async function redirects() {
100100
destination: "/auth",
101101
permanent: false,
102102
},
103-
{
104-
source: "/checkout",
105-
destination: "/connect",
106-
permanent: false,
107-
},
108103
{
109104
source: "/extensions",
110105
destination: "/build",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"use client";
2+
import {
3+
THIRDWEB_ANALYTICS_DOMAIN,
4+
THIRDWEB_INSIGHT_API_DOMAIN,
5+
THIRDWEB_PAY_DOMAIN,
6+
THIRDWEB_RPC_DOMAIN,
7+
THIRDWEB_STORAGE_DOMAIN,
8+
} from "constants/urls";
9+
import { useV5DashboardChain } from "lib/v5-adapter";
10+
import { getVercelEnv } from "lib/vercel-utils";
11+
import { useTheme } from "next-themes";
12+
import { useMemo } from "react";
13+
import { NATIVE_TOKEN_ADDRESS, createThirdwebClient, toTokens } from "thirdweb";
14+
import { AutoConnect, PayEmbed } from "thirdweb/react";
15+
import { setThirdwebDomains } from "thirdweb/utils";
16+
17+
export function CheckoutEmbed({
18+
chainId,
19+
recipientAddress,
20+
amount,
21+
token,
22+
name,
23+
image,
24+
redirectUri,
25+
clientId,
26+
}: {
27+
chainId: number;
28+
recipientAddress: string;
29+
amount: bigint;
30+
token: { name: string; symbol: string; address: string; decimals: number };
31+
name?: string;
32+
image?: string;
33+
redirectUri?: string;
34+
clientId: string;
35+
}) {
36+
const client = useMemo(() => {
37+
if (getVercelEnv() !== "production") {
38+
setThirdwebDomains({
39+
rpc: THIRDWEB_RPC_DOMAIN,
40+
pay: THIRDWEB_PAY_DOMAIN,
41+
storage: THIRDWEB_STORAGE_DOMAIN,
42+
insight: THIRDWEB_INSIGHT_API_DOMAIN,
43+
analytics: THIRDWEB_ANALYTICS_DOMAIN,
44+
});
45+
}
46+
return createThirdwebClient({ clientId });
47+
}, [clientId]);
48+
const chain = useV5DashboardChain(chainId);
49+
const { theme } = useTheme();
50+
51+
return (
52+
<>
53+
<AutoConnect client={client} />
54+
<PayEmbed
55+
client={client}
56+
theme={theme === "light" ? "light" : "dark"}
57+
payOptions={{
58+
metadata: {
59+
name,
60+
image,
61+
},
62+
mode: "direct_payment",
63+
paymentInfo: {
64+
chain,
65+
sellerAddress: recipientAddress,
66+
amount: toTokens(amount, token.decimals),
67+
token: token.address === NATIVE_TOKEN_ADDRESS ? undefined : token,
68+
},
69+
onPurchaseSuccess: (result) => {
70+
if (!redirectUri) return;
71+
const url = new URL(redirectUri);
72+
if (result.type === "transaction") {
73+
url.searchParams.set("txHash", result.transactionHash);
74+
return window.open(url.toString());
75+
}
76+
if (result.status.status === "NOT_FOUND") {
77+
throw new Error("Transaction not found");
78+
}
79+
const txHash = result.status.source?.transactionHash;
80+
if (typeof txHash === "string") {
81+
url.searchParams.set("txHash", txHash);
82+
}
83+
},
84+
}}
85+
/>
86+
</>
87+
);
88+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"use client";
2+
import { ThirdwebProvider } from "thirdweb/react";
3+
4+
export function Providers({ children }: { children: React.ReactNode }) {
5+
return <ThirdwebProvider>{children}</ThirdwebProvider>;
6+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { cn } from "@/lib/utils";
2+
import { ThemeProvider } from "next-themes";
3+
import { Inter } from "next/font/google";
4+
import { Providers } from "./components/client/Providers.client";
5+
6+
const fontSans = Inter({
7+
subsets: ["latin"],
8+
variable: "--font-sans",
9+
display: "swap",
10+
});
11+
12+
export default function CheckoutLayout({
13+
children,
14+
}: { children: React.ReactNode }) {
15+
return (
16+
<html lang="en" suppressHydrationWarning>
17+
<Providers>
18+
<ThemeProvider
19+
attribute="class"
20+
disableTransitionOnChange
21+
enableSystem={false}
22+
defaultTheme="dark"
23+
>
24+
<body
25+
className={cn(
26+
"bg-background font-sans antialiased",
27+
fontSans.variable,
28+
)}
29+
>
30+
{children}
31+
</body>
32+
</ThemeProvider>
33+
</Providers>
34+
</html>
35+
);
36+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import "../../global.css";
2+
import { getThirdwebClient } from "@/constants/thirdweb.server";
3+
import type { Metadata } from "next";
4+
import { createThirdwebClient, defineChain, getContract } from "thirdweb";
5+
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
6+
import { checksumAddress } from "thirdweb/utils";
7+
import { CheckoutEmbed } from "./components/client/CheckoutEmbed.client";
8+
9+
const title = "thirdweb Checkout";
10+
const description = "Fast, secure, and simple payments.";
11+
12+
export const metadata: Metadata = {
13+
title,
14+
description,
15+
openGraph: {
16+
title,
17+
description,
18+
},
19+
};
20+
21+
export default async function RoutesPage({
22+
searchParams,
23+
}: { searchParams: Record<string, string | string[]> }) {
24+
const {
25+
chainId,
26+
recipientAddress,
27+
tokenAddress,
28+
amount,
29+
clientId,
30+
redirectUri,
31+
} = searchParams;
32+
33+
if (!chainId || Array.isArray(chainId)) {
34+
throw new Error("A single chainId parameter is required.");
35+
}
36+
if (!recipientAddress || Array.isArray(recipientAddress)) {
37+
throw new Error("A single recipientAddress parameter is required.");
38+
}
39+
if (!tokenAddress || Array.isArray(tokenAddress)) {
40+
throw new Error("A single tokenAddress parameter is required.");
41+
}
42+
if (!amount || Array.isArray(amount)) {
43+
throw new Error("A single amount parameter is required.");
44+
}
45+
if (Array.isArray(clientId)) {
46+
throw new Error("A single clientId parameter is required.");
47+
}
48+
if (Array.isArray(redirectUri)) {
49+
throw new Error("A single redirectUri parameter is required.");
50+
}
51+
52+
// Use any provided clientId or use the dashboard client
53+
const client =
54+
clientId && !Array.isArray(clientId)
55+
? createThirdwebClient({ clientId })
56+
: getThirdwebClient(undefined);
57+
58+
const tokenContract = getContract({
59+
client,
60+
// eslint-disable-next-line no-restricted-syntax
61+
chain: defineChain(Number(chainId)),
62+
address: tokenAddress,
63+
});
64+
const { symbol, decimals, name } = await getCurrencyMetadata({
65+
contract: tokenContract,
66+
});
67+
const token = {
68+
symbol,
69+
decimals,
70+
name,
71+
address: checksumAddress(tokenAddress),
72+
chainId: Number(chainId),
73+
};
74+
75+
return (
76+
<div className="relative mx-auto flex h-screen w-screen flex-col items-center justify-center overflow-hidden border py-10">
77+
<main className="container z-10 flex justify-center">
78+
<CheckoutEmbed
79+
redirectUri={redirectUri}
80+
chainId={Number(chainId)}
81+
recipientAddress={recipientAddress}
82+
amount={BigInt(amount)}
83+
token={token}
84+
clientId={client.clientId}
85+
/>
86+
</main>
87+
88+
{/* eslint-disable-next-line @next/next/no-img-element */}
89+
<img
90+
alt=""
91+
src="/assets/login/background.svg"
92+
className="-bottom-12 -right-12 pointer-events-none absolute lg:right-0 lg:bottom-0"
93+
/>
94+
</div>
95+
);
96+
}

apps/dashboard/src/constants/urls.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ export const THIRDWEB_BUNDLER_DOMAIN =
2323

2424
export const THIRDWEB_INSIGHT_API_DOMAIN =
2525
process.env.NEXT_PUBLIC_INSIGHT_API_URL || "insight.thirdweb-dev.com";
26+
27+
export const THIRDWEB_ANALYTICS_DOMAIN =
28+
process.env.NEXT_PUBLIC_ANALYTICS_URL || "c.thirdweb-dev.com";

0 commit comments

Comments
 (0)