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
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"use client";
import {
THIRDWEB_ANALYTICS_DOMAIN,
THIRDWEB_BUNDLER_DOMAIN,
THIRDWEB_INAPP_WALLET_DOMAIN,
THIRDWEB_INSIGHT_API_DOMAIN,
THIRDWEB_PAY_DOMAIN,
THIRDWEB_RPC_DOMAIN,
THIRDWEB_SOCIAL_API_DOMAIN,
THIRDWEB_STORAGE_DOMAIN,
} from "constants/urls";
import { useV5DashboardChain } from "lib/v5-adapter";
import { getVercelEnv } from "lib/vercel-utils";
import { useTheme } from "next-themes";
import { useMemo } from "react";
import { NATIVE_TOKEN_ADDRESS, createThirdwebClient, toTokens } from "thirdweb";
import { AutoConnect, PayEmbed } from "thirdweb/react";
Expand All @@ -23,6 +25,7 @@ export function CheckoutEmbed({
image,
redirectUri,
clientId,
theme,
}: {
chainId: number;
recipientAddress: string;
Expand All @@ -32,6 +35,7 @@ export function CheckoutEmbed({
image?: string;
redirectUri?: string;
clientId: string;
theme: "light" | "dark";
}) {
const client = useMemo(() => {
if (getVercelEnv() !== "production") {
Expand All @@ -41,12 +45,14 @@ export function CheckoutEmbed({
storage: THIRDWEB_STORAGE_DOMAIN,
insight: THIRDWEB_INSIGHT_API_DOMAIN,
analytics: THIRDWEB_ANALYTICS_DOMAIN,
inAppWallet: THIRDWEB_INAPP_WALLET_DOMAIN,
bundler: THIRDWEB_BUNDLER_DOMAIN,
social: THIRDWEB_SOCIAL_API_DOMAIN,
});
}
return createThirdwebClient({ clientId });
}, [clientId]);
const chain = useV5DashboardChain(chainId);
const { theme } = useTheme();

return (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client";

import { useTheme } from "next-themes";
import { useSearchParams } from "next/navigation";
import { useEffect } from "react";

export function ThemeHandler() {
const searchParams = useSearchParams();
const { setTheme } = useTheme();

// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
const theme = searchParams.get("theme");
if (theme === "light") {
setTheme("light");
} else {
setTheme("dark");
}
}, [searchParams, setTheme]);

return null;
}
11 changes: 11 additions & 0 deletions apps/dashboard/src/app/checkout/components/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type CheckoutParams = {
chainId: string;
recipientAddress: string;
tokenAddress: string;
amount: string;
name?: string;
image?: string;
theme?: "light" | "dark";
redirectUri?: string;
clientId?: string;
};
11 changes: 9 additions & 2 deletions apps/dashboard/src/app/checkout/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { cn } from "@/lib/utils";
import { ThemeProvider } from "next-themes";
import { Inter } from "next/font/google";
import { Suspense } from "react";
import { Providers } from "./components/client/Providers.client";
import { ThemeHandler } from "./components/client/ThemeHandler.client";

const fontSans = Inter({
subsets: ["latin"],
variable: "--font-sans",
display: "swap",
});

export default function CheckoutLayout({
export default async function CheckoutLayout({
children,
}: { children: React.ReactNode }) {
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<Providers>
Expand All @@ -27,6 +31,9 @@ export default function CheckoutLayout({
fontSans.variable,
)}
>
<Suspense>
<ThemeHandler />
</Suspense>
{children}
</body>
</ThemeProvider>
Expand Down
57 changes: 29 additions & 28 deletions apps/dashboard/src/app/checkout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createThirdwebClient, defineChain, getContract } from "thirdweb";
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
import { checksumAddress } from "thirdweb/utils";
import { CheckoutEmbed } from "./components/client/CheckoutEmbed.client";
import type { CheckoutParams } from "./components/types";

const title = "thirdweb Checkout";
const description = "Fast, secure, and simple payments.";
Expand All @@ -20,68 +21,68 @@ export const metadata: Metadata = {

export default async function RoutesPage({
searchParams,
}: { searchParams: Record<string, string | string[]> }) {
const {
chainId,
recipientAddress,
tokenAddress,
amount,
clientId,
redirectUri,
} = searchParams;
}: { searchParams: Promise<CheckoutParams> }) {
const params = await searchParams;

if (!chainId || Array.isArray(chainId)) {
if (!params.chainId || Array.isArray(params.chainId)) {
throw new Error("A single chainId parameter is required.");
}
if (!recipientAddress || Array.isArray(recipientAddress)) {
if (!params.recipientAddress || Array.isArray(params.recipientAddress)) {
throw new Error("A single recipientAddress parameter is required.");
}
if (!tokenAddress || Array.isArray(tokenAddress)) {
if (!params.tokenAddress || Array.isArray(params.tokenAddress)) {
throw new Error("A single tokenAddress parameter is required.");
}
if (!amount || Array.isArray(amount)) {
if (!params.amount || Array.isArray(params.amount)) {
throw new Error("A single amount parameter is required.");
}
if (Array.isArray(clientId)) {
if (Array.isArray(params.clientId)) {
throw new Error("A single clientId parameter is required.");
}
if (Array.isArray(redirectUri)) {
if (Array.isArray(params.redirectUri)) {
throw new Error("A single redirectUri parameter is required.");
}

// Use any provided clientId or use the dashboard client
const client =
clientId && !Array.isArray(clientId)
? createThirdwebClient({ clientId })
params.clientId && !Array.isArray(params.clientId)
? createThirdwebClient({ clientId: params.clientId })
: getThirdwebClient(undefined);

const tokenContract = getContract({
client,
client: getThirdwebClient(undefined), // for this RPC call, use the dashboard client
// eslint-disable-next-line no-restricted-syntax
chain: defineChain(Number(chainId)),
address: tokenAddress,
chain: defineChain(Number(params.chainId)),
address: params.tokenAddress,
});
const { symbol, decimals, name } = await getCurrencyMetadata({
const {
symbol,
decimals,
name: tokenName,
} = await getCurrencyMetadata({
contract: tokenContract,
});
const token = {
symbol,
decimals,
name,
address: checksumAddress(tokenAddress),
chainId: Number(chainId),
name: tokenName,
address: checksumAddress(params.tokenAddress),
chainId: Number(params.chainId),
};

return (
<div className="relative mx-auto flex h-screen w-screen flex-col items-center justify-center overflow-hidden border py-10">
<main className="container z-10 flex justify-center">
<CheckoutEmbed
redirectUri={redirectUri}
chainId={Number(chainId)}
recipientAddress={recipientAddress}
amount={BigInt(amount)}
redirectUri={params.redirectUri}
chainId={Number(params.chainId)}
recipientAddress={params.recipientAddress}
amount={BigInt(params.amount)}
token={token}
clientId={client.clientId}
name={params.name}
image={params.image}
theme={params.theme === "light" ? "light" : "dark"}
/>
</main>

Expand Down
Loading