Skip to content

Commit 849ff2c

Browse files
committed
feat: checkout page
1 parent 736c3f8 commit 849ff2c

File tree

5 files changed

+251
-5
lines changed

5 files changed

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

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)