From aad3e6477eb2f71ffffcfe886ba1d147f3a20a07 Mon Sep 17 00:00:00 2001 From: MananTank Date: Tue, 16 Dec 2025 20:42:47 +0000 Subject: [PATCH] [MNY-226] Add Bridge Widget playgrounds (script, iframe, react) (#8571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR focuses on enhancing the `BridgeWidget` component within the playground by introducing new features, improving styling, and refining existing functionalities. It also includes updates to metadata and the handling of token selections and amounts. ### Detailed summary - Removed `PayIcon` and replaced with `BringToFrontIcon`. - Added `showThirdwebBranding` option to `UniversalBridgeEmbed`. - Updated `BridgeWidget` to support token amounts and selections. - Enhanced sorting of bridge chains in `chains.ts`. - Improved styling for various components, including `FeatureCard` and `ChatPageContent`. - Updated documentation for widget parameters. - Refactored code for better readability and maintainability in multiple components. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit * **New Features** * Bridge Widget playground: interactive preview, code generation (script/component/iframe), theme, token/amount prefills, and preview modes. * Unified buy/swap experience under a single Bridge widget and a new branding toggle (hide/show branding). * Expanded connection options for wallet/connect flows. * **Documentation** * Added docs for token amounts, branding, and iframe examples. * **Style** * Minor typography and spacing refinements. * **Chores** * Navigation and feature card updates; small test adjustment (one image test skipped). ✏️ Tip: You can customize this high-level summary in your review settings. --- .../@/components/blocks/BuyAndSwapEmbed.tsx | 353 ++++++++---------- .../public-pages/erc20/erc20.tsx | 2 +- .../client/UniversalBridgeEmbed.tsx | 2 + apps/dashboard/src/app/bridge/widget/page.tsx | 16 + .../src/app/ai/components/ChatPageContent.tsx | 2 +- .../components/bridge-playground.tsx | 74 ++++ .../components/buildIframeUrl.ts | 72 ++++ .../bridge/bridge-widget/components/code.tsx | 214 +++++++++++ .../bridge-widget/components/left-section.tsx | 232 ++++++++++++ .../components/right-section.tsx | 143 +++++++ .../bridge/bridge-widget/components/types.ts | 28 ++ .../src/app/bridge/bridge-widget/page.tsx | 33 ++ apps/playground-web/src/app/bridge/page.tsx | 4 +- .../src/app/data/pages-metadata.ts | 20 +- apps/playground-web/src/app/navLinks.ts | 4 + .../src/app/transactions/users/page.tsx | 22 +- .../app/x402/components/X402LeftSection.tsx | 1 + .../src/components/blocks/FeatureCard.tsx | 2 +- .../src/components/ui/tab-buttons.tsx | 139 +++++++ apps/playground-web/src/hooks/chains.ts | 17 +- apps/playground-web/src/icons/PayIcon.tsx | 21 -- .../app/bridge/bridge-widget/iframe/page.mdx | 34 +- .../ui/Bridge/bridge-widget/bridge-widget.tsx | 131 ++++++- .../ui/MediaRenderer/MediaRenderer.test.tsx | 4 +- .../bridge-widget-script.stories.tsx | 45 ++- 25 files changed, 1334 insertions(+), 281 deletions(-) create mode 100644 apps/playground-web/src/app/bridge/bridge-widget/components/bridge-playground.tsx create mode 100644 apps/playground-web/src/app/bridge/bridge-widget/components/buildIframeUrl.ts create mode 100644 apps/playground-web/src/app/bridge/bridge-widget/components/code.tsx create mode 100644 apps/playground-web/src/app/bridge/bridge-widget/components/left-section.tsx create mode 100644 apps/playground-web/src/app/bridge/bridge-widget/components/right-section.tsx create mode 100644 apps/playground-web/src/app/bridge/bridge-widget/components/types.ts create mode 100644 apps/playground-web/src/app/bridge/bridge-widget/page.tsx create mode 100644 apps/playground-web/src/components/ui/tab-buttons.tsx delete mode 100644 apps/playground-web/src/icons/PayIcon.tsx diff --git a/apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx b/apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx index d41b0d6479e..cc3a563e92a 100644 --- a/apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx +++ b/apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx @@ -2,13 +2,8 @@ "use client"; import { useTheme } from "next-themes"; -import { useEffect, useMemo, useRef, useState } from "react"; -import { defineChain } from "thirdweb"; -import { - BuyWidget, - type SupportedFiatCurrency, - SwapWidget, -} from "thirdweb/react"; +import { useEffect, useMemo, useRef } from "react"; +import { BridgeWidget, type SupportedFiatCurrency } from "thirdweb/react"; import type { Wallet } from "thirdweb/wallets"; import { reportAssetBuyCancelled, @@ -22,14 +17,12 @@ import { reportTokenSwapFailed, reportTokenSwapSuccessful, } from "@/analytics/report"; -import { Button } from "@/components/ui/button"; import { NEXT_PUBLIC_ASSET_PAGE_CLIENT_ID, NEXT_PUBLIC_BRIDGE_IFRAME_CLIENT_ID, NEXT_PUBLIC_BRIDGE_PAGE_CLIENT_ID, NEXT_PUBLIC_CHAIN_PAGE_CLIENT_ID, } from "@/constants/public-envs"; -import { cn } from "@/lib/utils"; import { parseError } from "@/utils/errorParser"; import { getSDKTheme } from "@/utils/sdk-component-theme"; import { appMetadata } from "../../constants/connect"; @@ -71,11 +64,11 @@ export type BuyAndSwapEmbedProps = { pageType: PageType; wallets?: Wallet[]; currency?: SupportedFiatCurrency; + showThirdwebBranding?: boolean; }; export function BuyAndSwapEmbed(props: BuyAndSwapEmbedProps) { const { theme } = useTheme(); - const [tab, setTab] = useState<"buy" | "swap">("swap"); const themeObj = getSDKTheme(theme === "light" ? "light" : "dark"); const isMounted = useRef(false); @@ -108,211 +101,161 @@ export function BuyAndSwapEmbed(props: BuyAndSwapEmbedProps) { }, [props.pageType]); return ( -
-
- setTab("swap")} - isActive={tab === "swap"} - /> - setTab("buy")} - isActive={tab === "buy"} - /> -
+ { + const errorMessage = parseError(e); - {tab === "buy" && ( - { - const errorMessage = parseError(e); + const buyChainId = + quote?.type === "buy" + ? quote.intent.destinationChainId + : quote?.type === "onramp" + ? quote.intent.chainId + : undefined; - const buyChainId = - quote?.type === "buy" - ? quote.intent.destinationChainId - : quote?.type === "onramp" - ? quote.intent.chainId - : undefined; + if (!buyChainId) { + return; + } - if (!buyChainId) { - return; - } + reportTokenBuyFailed({ + buyTokenChainId: buyChainId, + buyTokenAddress: + quote?.type === "buy" + ? quote.intent.destinationTokenAddress + : quote?.type === "onramp" + ? quote.intent.tokenAddress + : undefined, + pageType: props.pageType, + }); - reportTokenBuyFailed({ - buyTokenChainId: buyChainId, - buyTokenAddress: - quote?.type === "buy" - ? quote.intent.destinationTokenAddress - : quote?.type === "onramp" - ? quote.intent.tokenAddress - : undefined, - pageType: props.pageType, - }); + if (props.pageType === "asset") { + reportAssetBuyFailed({ + assetType: "coin", + chainId: buyChainId, + error: errorMessage, + contractType: undefined, + is_testnet: false, + }); + } + }, + onCancel: (quote) => { + const buyChainId = + quote?.type === "buy" + ? quote.intent.destinationChainId + : quote?.type === "onramp" + ? quote.intent.chainId + : undefined; - if (props.pageType === "asset") { - reportAssetBuyFailed({ - assetType: "coin", - chainId: buyChainId, - error: errorMessage, - contractType: undefined, - is_testnet: false, - }); - } - }} - onCancel={(quote) => { - const buyChainId = - quote?.type === "buy" - ? quote.intent.destinationChainId - : quote?.type === "onramp" - ? quote.intent.chainId - : undefined; + if (!buyChainId) { + return; + } - if (!buyChainId) { - return; - } + reportTokenBuyCancelled({ + buyTokenChainId: buyChainId, + buyTokenAddress: + quote?.type === "buy" + ? quote.intent.destinationTokenAddress + : quote?.type === "onramp" + ? quote.intent.tokenAddress + : undefined, + pageType: props.pageType, + }); - reportTokenBuyCancelled({ - buyTokenChainId: buyChainId, - buyTokenAddress: - quote?.type === "buy" - ? quote.intent.destinationTokenAddress - : quote?.type === "onramp" - ? quote.intent.tokenAddress - : undefined, - pageType: props.pageType, - }); + if (props.pageType === "asset") { + reportAssetBuyCancelled({ + assetType: "coin", + chainId: buyChainId, + contractType: undefined, + is_testnet: false, + }); + } + }, + onSuccess: ({ quote }) => { + const buyChainId = + quote?.type === "buy" + ? quote.intent.destinationChainId + : quote?.type === "onramp" + ? quote.intent.chainId + : undefined; - if (props.pageType === "asset") { - reportAssetBuyCancelled({ - assetType: "coin", - chainId: buyChainId, - contractType: undefined, - is_testnet: false, - }); - } - }} - onSuccess={({ quote }) => { - const buyChainId = - quote?.type === "buy" - ? quote.intent.destinationChainId - : quote?.type === "onramp" - ? quote.intent.chainId - : undefined; - - if (!buyChainId) { - return; - } + if (!buyChainId) { + return; + } - reportTokenBuySuccessful({ - buyTokenChainId: buyChainId, - buyTokenAddress: - quote?.type === "buy" - ? quote.intent.destinationTokenAddress - : quote?.type === "onramp" - ? quote.intent.tokenAddress - : undefined, - pageType: props.pageType, - }); + reportTokenBuySuccessful({ + buyTokenChainId: buyChainId, + buyTokenAddress: + quote?.type === "buy" + ? quote.intent.destinationTokenAddress + : quote?.type === "onramp" + ? quote.intent.tokenAddress + : undefined, + pageType: props.pageType, + }); - if (props.pageType === "asset") { - reportAssetBuySuccessful({ - assetType: "coin", - chainId: buyChainId, - contractType: undefined, - is_testnet: false, - }); + if (props.pageType === "asset") { + reportAssetBuySuccessful({ + assetType: "coin", + chainId: buyChainId, + contractType: undefined, + is_testnet: false, + }); + } + }, } - }} - theme={themeObj} - paymentMethods={["card"]} - /> - )} - - {tab === "swap" && ( - { - const errorMessage = parseError(error); - reportTokenSwapFailed({ - errorMessage: errorMessage, - buyTokenChainId: quote.intent.destinationChainId, - buyTokenAddress: quote.intent.destinationTokenAddress, - sellTokenChainId: quote.intent.originChainId, - sellTokenAddress: quote.intent.originTokenAddress, - pageType: props.pageType, - }); - }} - onSuccess={({ quote }) => { - reportTokenSwapSuccessful({ - buyTokenChainId: quote.intent.destinationChainId, - buyTokenAddress: quote.intent.destinationTokenAddress, - sellTokenChainId: quote.intent.originChainId, - sellTokenAddress: quote.intent.originTokenAddress, - pageType: props.pageType, - }); - }} - onCancel={(quote) => { - reportTokenSwapCancelled({ - buyTokenChainId: quote.intent.destinationChainId, - buyTokenAddress: quote.intent.destinationTokenAddress, - sellTokenChainId: quote.intent.originChainId, - sellTokenAddress: quote.intent.originTokenAddress, - pageType: props.pageType, - }); - }} - /> - )} -
- ); -} - -function TabButton(props: { - label: string; - onClick: () => void; - isActive: boolean; -}) { - return ( - + : undefined + } + swap={{ + persistTokenSelections: props.persistTokenSelections, + prefill: { + buyToken: props.swapTab?.buyToken, + sellToken: props.swapTab?.sellToken, + }, + onError: (error, quote) => { + const errorMessage = parseError(error); + reportTokenSwapFailed({ + errorMessage: errorMessage, + buyTokenChainId: quote.intent.destinationChainId, + buyTokenAddress: quote.intent.destinationTokenAddress, + sellTokenChainId: quote.intent.originChainId, + sellTokenAddress: quote.intent.originTokenAddress, + pageType: props.pageType, + }); + }, + onSuccess: ({ quote }) => { + reportTokenSwapSuccessful({ + buyTokenChainId: quote.intent.destinationChainId, + buyTokenAddress: quote.intent.destinationTokenAddress, + sellTokenChainId: quote.intent.originChainId, + sellTokenAddress: quote.intent.originTokenAddress, + pageType: props.pageType, + }); + }, + onCancel: (quote) => { + reportTokenSwapCancelled({ + buyTokenChainId: quote.intent.destinationChainId, + buyTokenAddress: quote.intent.destinationTokenAddress, + sellTokenChainId: quote.intent.originChainId, + sellTokenAddress: quote.intent.originTokenAddress, + pageType: props.pageType, + }); + }, + }} + currency={props.currency} + showThirdwebBranding={props.showThirdwebBranding} + /> ); } diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx index 33ea08ec012..a059e0b4d04 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx @@ -283,7 +283,7 @@ function TokenInfoSection(props: {
{props.label}
-
+
{props.value}
diff --git a/apps/dashboard/src/app/bridge/(general)/components/client/UniversalBridgeEmbed.tsx b/apps/dashboard/src/app/bridge/(general)/components/client/UniversalBridgeEmbed.tsx index 79fb15a551d..021bd2c48d4 100644 --- a/apps/dashboard/src/app/bridge/(general)/components/client/UniversalBridgeEmbed.tsx +++ b/apps/dashboard/src/app/bridge/(general)/components/client/UniversalBridgeEmbed.tsx @@ -25,6 +25,7 @@ export function UniversalBridgeEmbed(props: { swapTab: BuyAndSwapEmbedProps["swapTab"]; pageType: "bridge" | "bridge-iframe"; currency?: SupportedFiatCurrency; + showThirdwebBranding?: boolean; }) { return ( ); } diff --git a/apps/dashboard/src/app/bridge/widget/page.tsx b/apps/dashboard/src/app/bridge/widget/page.tsx index 815686bbe1b..d7567a497d0 100644 --- a/apps/dashboard/src/app/bridge/widget/page.tsx +++ b/apps/dashboard/src/app/bridge/widget/page.tsx @@ -35,9 +35,16 @@ export default async function Page(props: { // output is buy, input is sell const sellChain = parse(searchParams.inputChain, onlyNumber); const sellCurrency = parse(searchParams.inputCurrency, onlyAddress); + const sellAmount = parse(searchParams.inputCurrencyAmount, onlyNumber); const buyChain = parse(searchParams.outputChain, onlyNumber); const buyCurrency = parse(searchParams.outputCurrency, onlyAddress); + const buyAmount = parse(searchParams.outputCurrencyAmount, onlyNumber); + + const showThirdwebBranding = parse( + searchParams.showThirdwebBranding, + (v) => v !== "false", + ); const persistTokenSelections = parse(searchParams.persistTokenSelections, (v) => @@ -61,11 +68,14 @@ export default async function Page(props: { persistTokenSelections={persistTokenSelections === "true"} pageType="bridge-iframe" currency={currency} + showThirdwebBranding={showThirdwebBranding} buyTab={{ buyToken: buyChain ? { chainId: buyChain, tokenAddress: buyCurrency || NATIVE_TOKEN_ADDRESS, + amount: + buyAmount === undefined ? undefined : buyAmount.toString(), } : undefined, }} @@ -74,12 +84,18 @@ export default async function Page(props: { ? { chainId: sellChain, tokenAddress: sellCurrency || NATIVE_TOKEN_ADDRESS, + amount: + sellAmount === undefined + ? undefined + : sellAmount.toString(), } : undefined, buyToken: buyChain ? { chainId: buyChain, tokenAddress: buyCurrency || NATIVE_TOKEN_ADDRESS, + amount: + buyAmount === undefined ? undefined : buyAmount.toString(), } : undefined, }} diff --git a/apps/playground-web/src/app/ai/components/ChatPageContent.tsx b/apps/playground-web/src/app/ai/components/ChatPageContent.tsx index dcb4318181b..18d6d25496e 100644 --- a/apps/playground-web/src/app/ai/components/ChatPageContent.tsx +++ b/apps/playground-web/src/app/ai/components/ChatPageContent.tsx @@ -84,7 +84,7 @@ export function ChatPageContent(props: { const userChainIdArray = userChainIds .split(",") .map((id) => id.trim()) - .filter((id) => id !== "" && !isNaN(Number(id))); + .filter((id) => id !== "" && !Number.isNaN(Number(id))); return { chainIds: diff --git a/apps/playground-web/src/app/bridge/bridge-widget/components/bridge-playground.tsx b/apps/playground-web/src/app/bridge/bridge-widget/components/bridge-playground.tsx new file mode 100644 index 00000000000..f30185f0386 --- /dev/null +++ b/apps/playground-web/src/app/bridge/bridge-widget/components/bridge-playground.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; +import { TabButtons } from "../../../../components/ui/tab-buttons"; +import { LeftSection } from "./left-section"; +import { RightSection } from "./right-section"; +import type { BridgeWidgetPlaygroundOptions } from "./types"; + +const defaultOptions: BridgeWidgetPlaygroundOptions = { + integrationType: "iframe", + prefill: undefined, + currency: "USD", + showThirdwebBranding: true, + theme: { + darkColorOverrides: {}, + lightColorOverrides: {}, + type: "dark", + }, +}; + +export function BridgeWidgetPlayground() { + const { theme } = useTheme(); + + const [options, setOptions] = + useState(defaultOptions); + + // change theme on global theme change + useEffect(() => { + setOptions((prev) => ({ + ...prev, + theme: { + ...prev.theme, + type: theme === "dark" ? "dark" : "light", + }, + })); + }, [theme]); + + return ( +
+ + setOptions({ ...options, integrationType: "iframe" }), + isActive: options.integrationType === "iframe", + }, + { + name: "Script", + onClick: () => + setOptions({ ...options, integrationType: "script" }), + isActive: options.integrationType === "script", + }, + { + name: "React", + onClick: () => + setOptions({ ...options, integrationType: "component" }), + isActive: options.integrationType === "component", + }, + ]} + /> + +
+ +
+
+ +
+ +
+
+ ); +} diff --git a/apps/playground-web/src/app/bridge/bridge-widget/components/buildIframeUrl.ts b/apps/playground-web/src/app/bridge/bridge-widget/components/buildIframeUrl.ts new file mode 100644 index 00000000000..c3e034bd58b --- /dev/null +++ b/apps/playground-web/src/app/bridge/bridge-widget/components/buildIframeUrl.ts @@ -0,0 +1,72 @@ +import type { BridgeWidgetPlaygroundOptions } from "./types"; + +const BRIDGE_WIDGET_IFRAME_BASE_URL = "https://thirdweb.com/bridge/widget"; + +export function buildIframeUrl( + options: BridgeWidgetPlaygroundOptions, + type: "code" | "preview", +) { + const url = new URL(BRIDGE_WIDGET_IFRAME_BASE_URL); + + if (type === "preview") { + // always set it to false so playground doesn't show last used tokens + url.searchParams.set("persistTokenSelections", "false"); + } + + // Theme (only add if light, dark is default) + if (options.theme.type === "light") { + url.searchParams.set("theme", "light"); + } + + // Currency (only add if not USD, USD is default) + if (options.currency && options.currency !== "USD") { + url.searchParams.set("currency", options.currency); + } + + // Branding + if (options.showThirdwebBranding === false) { + url.searchParams.set("showThirdwebBranding", "false"); + } + + // Sell token (input) + if (options.prefill?.sellToken) { + url.searchParams.set( + "inputChain", + String(options.prefill.sellToken.chainId), + ); + if (options.prefill.sellToken.tokenAddress) { + url.searchParams.set( + "inputCurrency", + options.prefill.sellToken.tokenAddress, + ); + } + if (options.prefill.sellToken.amount) { + url.searchParams.set( + "inputCurrencyAmount", + options.prefill.sellToken.amount, + ); + } + } + + // Buy token (output) + if (options.prefill?.buyToken) { + url.searchParams.set( + "outputChain", + String(options.prefill.buyToken.chainId), + ); + if (options.prefill.buyToken.tokenAddress) { + url.searchParams.set( + "outputCurrency", + options.prefill.buyToken.tokenAddress, + ); + } + if (options.prefill.buyToken.amount) { + url.searchParams.set( + "outputCurrencyAmount", + options.prefill.buyToken.amount, + ); + } + } + + return url.toString(); +} diff --git a/apps/playground-web/src/app/bridge/bridge-widget/components/code.tsx b/apps/playground-web/src/app/bridge/bridge-widget/components/code.tsx new file mode 100644 index 00000000000..6c6f87da978 --- /dev/null +++ b/apps/playground-web/src/app/bridge/bridge-widget/components/code.tsx @@ -0,0 +1,214 @@ +import { lazy, Suspense } from "react"; +import { LoadingDots } from "@/components/ui/LoadingDots"; +import { quotes, stringifyImports, stringifyProps } from "@/lib/code-gen"; +import { buildIframeUrl } from "./buildIframeUrl"; +import type { BridgeWidgetPlaygroundOptions } from "./types"; + +const CodeClient = lazy(() => + import("../../../../components/code/code.client").then((m) => ({ + default: m.CodeClient, + })), +); + +function CodeLoading() { + return ( +
+ +
+ ); +} + +export function CodeGen(props: { options: BridgeWidgetPlaygroundOptions }) { + return ( +
+ }> + + +
+ ); +} + +function getCode(options: BridgeWidgetPlaygroundOptions) { + if (options.integrationType === "script") { + return getCode_Script(options); + } + if (options.integrationType === "component") { + return getCode_ReactComponent(options); + } + if (options.integrationType === "iframe") { + return getCode_Iframe(options); + } + return ""; +} + +function getCode_Script(options: BridgeWidgetPlaygroundOptions) { + const widgetOptions: Record = { + clientId: "your-thirdweb-client-id", + }; + + // Theme configuration + if (options.theme.type === "light") { + if (Object.keys(options.theme.lightColorOverrides || {}).length > 0) { + widgetOptions.theme = { + type: "light", + ...options.theme.lightColorOverrides, + }; + } else { + widgetOptions.theme = "light"; + } + } else { + // dark theme + if (Object.keys(options.theme.darkColorOverrides || {}).length > 0) { + widgetOptions.theme = { + type: "dark", + ...options.theme.darkColorOverrides, + }; + } + // default is dark, so no need to set if no overrides + } + + // Currency + if (options.currency && options.currency !== "USD") { + widgetOptions.currency = options.currency; + } + + // Branding + if (options.showThirdwebBranding === false) { + widgetOptions.showThirdwebBranding = false; + } + + // Buy tab options (for buyToken) + if (options.prefill?.buyToken) { + widgetOptions.buy = { + chainId: options.prefill.buyToken.chainId, + tokenAddress: options.prefill.buyToken.tokenAddress, + amount: options.prefill.buyToken.amount, + }; + } + + // Swap prefill options + if (options.prefill?.buyToken || options.prefill?.sellToken) { + widgetOptions.swap = { + prefill: options.prefill, + }; + } + + const optionsString = JSON.stringify(widgetOptions, null, 2) + .split("\n") + .map((line, i) => (i === 0 ? line : ` ${line}`)) + .join("\n"); + + return `\ + + + + +
+ + +`; +} + +function getCode_ReactComponent(options: BridgeWidgetPlaygroundOptions) { + const imports = { + thirdweb: ["createThirdwebClient"] as string[], + "thirdweb/react": ["BridgeWidget"] as string[], + }; + + let themeProp: string | undefined; + if ( + options.theme.type === "dark" && + Object.keys(options.theme.darkColorOverrides || {}).length > 0 + ) { + themeProp = `darkTheme({ + colors: ${JSON.stringify(options.theme.darkColorOverrides)}, + })`; + imports["thirdweb/react"].push("darkTheme"); + } + + if (options.theme.type === "light") { + if (Object.keys(options.theme.lightColorOverrides || {}).length > 0) { + themeProp = `lightTheme({ + colors: ${JSON.stringify(options.theme.lightColorOverrides)}, + })`; + imports["thirdweb/react"].push("lightTheme"); + } else { + themeProp = quotes("light"); + } + } + + // Build buy prop for buyToken + let buyProp: string | undefined; + if (options.prefill?.buyToken) { + buyProp = JSON.stringify( + { + chainId: options.prefill.buyToken.chainId, + tokenAddress: options.prefill.buyToken.tokenAddress, + amount: options.prefill.buyToken.amount, + }, + null, + 2, + ); + } + + // Build swap prop with prefill + let swapProp: string | undefined; + if (options.prefill?.buyToken || options.prefill?.sellToken) { + swapProp = JSON.stringify( + { + prefill: { + buyToken: options.prefill?.buyToken, + sellToken: options.prefill?.sellToken, + }, + }, + null, + 2, + ); + } + + const props: Record = { + client: "client", + theme: themeProp, + buy: buyProp, + swap: swapProp, + currency: + options.currency !== "USD" && options.currency + ? quotes(options.currency) + : undefined, + showThirdwebBranding: + options.showThirdwebBranding === false ? false : undefined, + }; + + return `\ +${stringifyImports(imports)} + +const client = createThirdwebClient({ + clientId: "your-thirdweb-client-id", +}); + +function Example() { + return ( + + ); +}`; +} + +function getCode_Iframe(options: BridgeWidgetPlaygroundOptions) { + const src = buildIframeUrl(options, "code"); + + return `\ +