Skip to content

Commit 4c54110

Browse files
committed
feat: checkout page
1 parent 736c3f8 commit 4c54110

File tree

5 files changed

+250
-5
lines changed

5 files changed

+250
-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 { createThirdwebClient, NATIVE_TOKEN_ADDRESS, 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: 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 { ThirdwebProvider } from "thirdweb/react";
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+
<ThirdwebProvider>
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+
</ThirdwebProvider>
34+
</html>
35+
);
36+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import "../../global.css";
2+
import { getThirdwebClient } from "@/constants/thirdweb.server";
3+
import type { Metadata } from "next";
4+
import {
5+
createThirdwebClient,
6+
defineChain,
7+
getContract,
8+
NATIVE_TOKEN_ADDRESS,
9+
} from "thirdweb";
10+
import { name, symbol } from "thirdweb/extensions/common";
11+
import { decimals } from "thirdweb/extensions/erc20";
12+
import { checksumAddress } from "thirdweb/utils";
13+
import { CheckoutEmbed } from "./components/client/CheckoutEmbed.client";
14+
15+
const title = "thirdweb Checkout";
16+
const description = "Fast, secure, and simple payments.";
17+
18+
export const metadata: Metadata = {
19+
title,
20+
description,
21+
openGraph: {
22+
title,
23+
description,
24+
},
25+
};
26+
27+
export default async function RoutesPage({
28+
searchParams,
29+
}: { searchParams: Record<string, string | string[]> }) {
30+
const {
31+
chainId,
32+
recipientAddress,
33+
tokenAddress,
34+
amount,
35+
clientId,
36+
redirectUri,
37+
} = searchParams;
38+
39+
if (!chainId || Array.isArray(chainId)) {
40+
throw new Error("A single chainId parameter is required.");
41+
}
42+
if (!recipientAddress || Array.isArray(recipientAddress)) {
43+
throw new Error("A single recipientAddress parameter is required.");
44+
}
45+
if (!tokenAddress || Array.isArray(tokenAddress)) {
46+
throw new Error("A single tokenAddress parameter is required.");
47+
}
48+
if (!amount || Array.isArray(amount)) {
49+
throw new Error("An single amount parameter is required.");
50+
}
51+
if (Array.isArray(clientId)) {
52+
throw new Error("A single clientId parameter is required.");
53+
}
54+
if (Array.isArray(redirectUri)) {
55+
throw new Error("A single redirectUri parameter is required.");
56+
}
57+
58+
// Use any provided clientId or use the dashboard client
59+
const client =
60+
clientId && !Array.isArray(clientId)
61+
? createThirdwebClient({ clientId })
62+
: getThirdwebClient(undefined);
63+
64+
const token = await (async () => {
65+
if (
66+
checksumAddress(tokenAddress) ===
67+
"0x0000000000000000000000000000000000000000" ||
68+
checksumAddress(tokenAddress) ===
69+
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
70+
) {
71+
return {
72+
name: "Ether",
73+
symbol: "ETH",
74+
address: NATIVE_TOKEN_ADDRESS,
75+
decimals: 18,
76+
};
77+
} else {
78+
const tokenContract = getContract({
79+
client,
80+
// eslint-disable-next-line no-restricted-syntax
81+
chain: defineChain(Number(chainId)),
82+
address: tokenAddress,
83+
});
84+
const symbolPromise = symbol({ contract: tokenContract });
85+
const namePromise = name({ contract: tokenContract });
86+
const decimalsPromise = decimals({ contract: tokenContract });
87+
88+
const [symbolResult, nameResult, decimalsResult] = await Promise.all([
89+
symbolPromise,
90+
namePromise,
91+
decimalsPromise,
92+
]);
93+
return {
94+
name: nameResult,
95+
symbol: symbolResult,
96+
address: tokenAddress,
97+
decimals: Number(decimalsResult),
98+
};
99+
}
100+
})();
101+
102+
return (
103+
<div className="relative mx-auto flex h-screen w-screen flex-col items-center justify-center overflow-hidden border py-10">
104+
<main className="container z-10 flex justify-center">
105+
<CheckoutEmbed
106+
redirectUri={redirectUri}
107+
chainId={Number(chainId)}
108+
recipientAddress={recipientAddress}
109+
amount={BigInt(amount)}
110+
token={token}
111+
clientId={client.clientId}
112+
/>
113+
</main>
114+
115+
{/* eslint-disable-next-line @next/next/no-img-element */}
116+
<img
117+
alt=""
118+
src="/assets/login/background.svg"
119+
className="-bottom-12 -right-12 pointer-events-none absolute lg:right-0 lg:bottom-0"
120+
/>
121+
</div>
122+
);
123+
}

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)