diff --git a/apps/dashboard/redirects.js b/apps/dashboard/redirects.js index a36f05659dd..64a6174fd8f 100644 --- a/apps/dashboard/redirects.js +++ b/apps/dashboard/redirects.js @@ -100,11 +100,6 @@ async function redirects() { destination: "/auth", permanent: false, }, - { - source: "/checkout", - destination: "/connect", - permanent: false, - }, { source: "/extensions", destination: "/build", diff --git a/apps/dashboard/src/app/checkout/components/client/CheckoutEmbed.client.tsx b/apps/dashboard/src/app/checkout/components/client/CheckoutEmbed.client.tsx new file mode 100644 index 00000000000..a8077895eeb --- /dev/null +++ b/apps/dashboard/src/app/checkout/components/client/CheckoutEmbed.client.tsx @@ -0,0 +1,88 @@ +"use client"; +import { + THIRDWEB_ANALYTICS_DOMAIN, + THIRDWEB_INSIGHT_API_DOMAIN, + THIRDWEB_PAY_DOMAIN, + THIRDWEB_RPC_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"; +import { setThirdwebDomains } from "thirdweb/utils"; + +export function CheckoutEmbed({ + chainId, + recipientAddress, + amount, + token, + name, + image, + redirectUri, + clientId, +}: { + chainId: number; + recipientAddress: string; + amount: bigint; + token: { name: string; symbol: string; address: string; decimals: number }; + name?: string; + image?: string; + redirectUri?: string; + clientId: string; +}) { + const client = useMemo(() => { + if (getVercelEnv() !== "production") { + setThirdwebDomains({ + rpc: THIRDWEB_RPC_DOMAIN, + pay: THIRDWEB_PAY_DOMAIN, + storage: THIRDWEB_STORAGE_DOMAIN, + insight: THIRDWEB_INSIGHT_API_DOMAIN, + analytics: THIRDWEB_ANALYTICS_DOMAIN, + }); + } + return createThirdwebClient({ clientId }); + }, [clientId]); + const chain = useV5DashboardChain(chainId); + const { theme } = useTheme(); + + return ( + <> + + { + if (!redirectUri) return; + const url = new URL(redirectUri); + if (result.type === "transaction") { + url.searchParams.set("txHash", result.transactionHash); + return window.open(url.toString()); + } + if (result.status.status === "NOT_FOUND") { + throw new Error("Transaction not found"); + } + const txHash = result.status.source?.transactionHash; + if (typeof txHash === "string") { + url.searchParams.set("txHash", txHash); + } + }, + }} + /> + + ); +} diff --git a/apps/dashboard/src/app/checkout/components/client/Providers.client.tsx b/apps/dashboard/src/app/checkout/components/client/Providers.client.tsx new file mode 100644 index 00000000000..a3412078c16 --- /dev/null +++ b/apps/dashboard/src/app/checkout/components/client/Providers.client.tsx @@ -0,0 +1,6 @@ +"use client"; +import { ThirdwebProvider } from "thirdweb/react"; + +export function Providers({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/apps/dashboard/src/app/checkout/layout.tsx b/apps/dashboard/src/app/checkout/layout.tsx new file mode 100644 index 00000000000..c21155f94c0 --- /dev/null +++ b/apps/dashboard/src/app/checkout/layout.tsx @@ -0,0 +1,36 @@ +import { cn } from "@/lib/utils"; +import { ThemeProvider } from "next-themes"; +import { Inter } from "next/font/google"; +import { Providers } from "./components/client/Providers.client"; + +const fontSans = Inter({ + subsets: ["latin"], + variable: "--font-sans", + display: "swap", +}); + +export default function CheckoutLayout({ + children, +}: { children: React.ReactNode }) { + return ( + + + + + {children} + + + + + ); +} diff --git a/apps/dashboard/src/app/checkout/page.tsx b/apps/dashboard/src/app/checkout/page.tsx new file mode 100644 index 00000000000..ebdde336e67 --- /dev/null +++ b/apps/dashboard/src/app/checkout/page.tsx @@ -0,0 +1,96 @@ +import "../../global.css"; +import { getThirdwebClient } from "@/constants/thirdweb.server"; +import type { Metadata } from "next"; +import { createThirdwebClient, defineChain, getContract } from "thirdweb"; +import { getCurrencyMetadata } from "thirdweb/extensions/erc20"; +import { checksumAddress } from "thirdweb/utils"; +import { CheckoutEmbed } from "./components/client/CheckoutEmbed.client"; + +const title = "thirdweb Checkout"; +const description = "Fast, secure, and simple payments."; + +export const metadata: Metadata = { + title, + description, + openGraph: { + title, + description, + }, +}; + +export default async function RoutesPage({ + searchParams, +}: { searchParams: Record }) { + const { + chainId, + recipientAddress, + tokenAddress, + amount, + clientId, + redirectUri, + } = searchParams; + + if (!chainId || Array.isArray(chainId)) { + throw new Error("A single chainId parameter is required."); + } + if (!recipientAddress || Array.isArray(recipientAddress)) { + throw new Error("A single recipientAddress parameter is required."); + } + if (!tokenAddress || Array.isArray(tokenAddress)) { + throw new Error("A single tokenAddress parameter is required."); + } + if (!amount || Array.isArray(amount)) { + throw new Error("A single amount parameter is required."); + } + if (Array.isArray(clientId)) { + throw new Error("A single clientId parameter is required."); + } + if (Array.isArray(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 }) + : getThirdwebClient(undefined); + + const tokenContract = getContract({ + client, + // eslint-disable-next-line no-restricted-syntax + chain: defineChain(Number(chainId)), + address: tokenAddress, + }); + const { symbol, decimals, name } = await getCurrencyMetadata({ + contract: tokenContract, + }); + const token = { + symbol, + decimals, + name, + address: checksumAddress(tokenAddress), + chainId: Number(chainId), + }; + + return ( +
+
+ +
+ + {/* eslint-disable-next-line @next/next/no-img-element */} + +
+ ); +} diff --git a/apps/dashboard/src/constants/urls.ts b/apps/dashboard/src/constants/urls.ts index 7a83c1e4f40..3c45478b889 100644 --- a/apps/dashboard/src/constants/urls.ts +++ b/apps/dashboard/src/constants/urls.ts @@ -23,3 +23,6 @@ export const THIRDWEB_BUNDLER_DOMAIN = export const THIRDWEB_INSIGHT_API_DOMAIN = process.env.NEXT_PUBLIC_INSIGHT_API_URL || "insight.thirdweb-dev.com"; + +export const THIRDWEB_ANALYTICS_DOMAIN = + process.env.NEXT_PUBLIC_ANALYTICS_URL || "c.thirdweb-dev.com";