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,5 +1,6 @@
import { lazy, Suspense } from "react";
import { LoadingDots } from "@/components/ui/LoadingDots";
import { quotes, stringifyImports, stringifyProps } from "@/lib/code-gen";
import type { SwapWidgetPlaygroundOptions } from "./types";

const CodeClient = lazy(() =>
Expand Down Expand Up @@ -28,7 +29,8 @@ export function CodeGen(props: { options: SwapWidgetPlaygroundOptions }) {

function getCode(options: SwapWidgetPlaygroundOptions) {
const imports = {
react: ["SwapWidget"] as string[],
thirdweb: ["createThirdwebClient"] as string[],
"thirdweb/react": ["SwapWidget"] as string[],
};

let themeProp: string | undefined;
Expand All @@ -39,15 +41,15 @@ function getCode(options: SwapWidgetPlaygroundOptions) {
themeProp = `darkTheme({
colors: ${JSON.stringify(options.theme.darkColorOverrides)},
})`;
imports.react.push("darkTheme");
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.react.push("lightTheme");
imports["thirdweb/react"].push("lightTheme");
} else {
themeProp = quotes("light");
}
Expand All @@ -69,37 +71,15 @@ function getCode(options: SwapWidgetPlaygroundOptions) {
};

return `\
import { createThirdwebClient } from "thirdweb";
import { ${imports.react.join(", ")} } from "thirdweb/react";
${stringifyImports(imports)}

const client = createThirdwebClient({
clientId: "....",
});


function Example() {
return (
<SwapWidget
${stringifyProps(props)}
/>
<SwapWidget ${stringifyProps(props)} />
);
}`;
}

function quotes(value: string) {
return `"${value}"`;
}

function stringifyProps(props: Record<string, string | undefined | boolean>) {
const _props: Record<string, string | undefined | boolean> = {};

for (const key in props) {
if (props[key] !== undefined && props[key] !== "") {
_props[key] = props[key];
}
}

return Object.entries(_props)
.map(([key, value]) => `${key}={${value}}`)
.join("\n\t ");
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const defaultOptions: BridgeComponentsPlaygroundOptions = {
sellerAddress: "0x0000000000000000000000000000000000000000",
title: "",
transactionData: "",
widget: "checkout",
currency: "USD",
showThirdwebBranding: true,
},
Expand All @@ -37,12 +36,12 @@ export function CheckoutPlayground() {
<div className="relative flex flex-col-reverse gap-6 xl:min-h-[900px] xl:flex-row xl:gap-6">
<div className="grow border-b pb-10 xl:mb-0 xl:border-r xl:border-b-0 xl:pr-6">
<LeftSection
lockedWidget="checkout"
widget="checkout"
options={options}
setOptions={setOptions}
/>
</div>
<RightSection lockedWidget="checkout" options={options} />
<RightSection widget="checkout" options={options} />
</div>
);
}
146 changes: 97 additions & 49 deletions apps/playground-web/src/app/payments/components/CodeGen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { lazy, Suspense } from "react";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add 'use client' — this file uses React.lazy/Suspense patterns.

Mark as a Client Component to avoid Next.js app router errors.

+ 'use client';
  import { lazy, Suspense } from "react";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { lazy, Suspense } from "react";
'use client';
import { lazy, Suspense } from "react";
🤖 Prompt for AI Agents
In apps/playground-web/src/app/payments/components/CodeGen.tsx around line 1,
the file uses React.lazy and Suspense and must be marked as a Next.js Client
Component; add the directive 'use client' as the very first line in the file
(before any imports) so the app router treats it as a client component and avoid
server-side rendering errors, then save and run/typecheck to verify no other
client-only hooks are used.

import { LoadingDots } from "@/components/ui/LoadingDots";
import {
quotes,
stringifyImports,
stringifyProps,
} from "../../../lib/code-gen";
import type { BridgeComponentsPlaygroundOptions } from "./types";

const CodeClient = lazy(() =>
Expand All @@ -16,79 +21,122 @@ function CodeLoading() {
);
}

export function CodeGen(props: { options: BridgeComponentsPlaygroundOptions }) {
export function CodeGen(props: {
widget: "buy" | "checkout" | "transaction";
options: BridgeComponentsPlaygroundOptions;
}) {
return (
<div className="flex w-full grow flex-col">
<Suspense fallback={<CodeLoading />}>
<CodeClient className="grow" code={getCode(props.options)} lang="tsx" />
<CodeClient
className="grow"
code={getCode(props.widget, props.options)}
lang="tsx"
/>
</Suspense>
</div>
);
}

function getCode(options: BridgeComponentsPlaygroundOptions) {
function getCode(
widget: "buy" | "checkout" | "transaction",
options: BridgeComponentsPlaygroundOptions,
) {
const componentName =
widget === "buy"
? "BuyWidget"
: widget === "checkout"
? "CheckoutWidget"
: widget === "transaction"
? "TransactionWidget"
: "";

const imports = {
chains: [] as string[],
react: ["PayEmbed"] as string[],
thirdweb: [] as string[],
wallets: [] as string[],
"thirdweb/chains": [] as string[],
"thirdweb/react": [componentName] as string[],
thirdweb: ["createThirdwebClient", "defineChain"] as string[],
"thirdweb/wallets": ["createWallet"] as string[],
};

// Check if we have a custom chain (not base chain which has id 8453)
const isCustomChain =
options.payOptions.buyTokenChain &&
options.payOptions.buyTokenChain.id !== 8453;
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 (isCustomChain) {
// Add defineChain to imports if using a custom chain
imports.thirdweb.push("defineChain");
} else {
// Otherwise use the base chain
imports.chains.push("base");
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");
}
}

imports.wallets.push("createWallet");
const transaction =
widget === "transaction"
? `claimTo({
contract: nftContract,
quantity: 1n,
to: account?.address || "",
tokenId: 2n,
})`
: undefined;
Comment on lines +83 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Broken snippet: references undefined vars (nftContract, account).

The generated transaction value will not compile as-is. Gate it on provided data or emit a self-contained example with required imports/variables.

Minimal safe fix using the existing options field:

-  const transaction =
-    widget === "transaction"
-      ? `claimTo({
-contract: nftContract,
-quantity: 1n,
-to: account?.address || "",
-tokenId: 2n,
-})`
-      : undefined;
+  const transaction =
+    widget === "transaction" && options.payOptions.transactionData
+      ? options.payOptions.transactionData
+      : undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const transaction =
widget === "transaction"
? `claimTo({
contract: nftContract,
quantity: 1n,
to: account?.address || "",
tokenId: 2n,
})`
: undefined;
const transaction =
widget === "transaction" && options.payOptions.transactionData
? options.payOptions.transactionData
: undefined;
🤖 Prompt for AI Agents
In apps/playground-web/src/app/payments/components/CodeGen.tsx around lines 83
to 91, the generated transaction snippet references undefined variables
(nftContract, account) and will not compile; update the code to only emit the
claimTo(...) snippet when the required values exist by gating on provided data
(e.g., options.nftContract and options.account?.address) or replace it with a
self-contained example using local placeholders and required imports; the
minimal safe fix is to conditionally set transaction to undefined unless
options.nftContract and options.account?.address are present, otherwise produce
a complete example string that defines necessary variables/imports so the
snippet compiles.


const componentName = (() => {
switch (options.payOptions.widget) {
case "buy":
return "BuyWidget";
case "checkout":
return "CheckoutWidget";
case "transaction":
return "TransactionWidget";
default:
return "PayEmbed";
}
})();
imports.react.push(componentName);
imports.chains.push("defineChain");
const props: Record<string, string | undefined | boolean> = {
theme: themeProp,
client: "client",
description: options.payOptions.description
? quotes(options.payOptions.description)
: undefined,
image: options.payOptions.image
? quotes(options.payOptions.image)
: undefined,
name: options.payOptions.title
? quotes(options.payOptions.title)
: undefined,
paymentMethods:
options.payOptions.paymentMethods.length === 2
? undefined
: JSON.stringify(options.payOptions.paymentMethods),
currency: options.payOptions.currency
? quotes(options.payOptions.currency)
: undefined,
chain: `defineChain(${options.payOptions.buyTokenChain.id})`,
showThirdwebBranding:
options.payOptions.showThirdwebBranding === false ? false : undefined,
amount: options.payOptions.buyTokenAmount
? quotes(options.payOptions.buyTokenAmount)
: undefined,
tokenAddress: options.payOptions.buyTokenAddress
? quotes(options.payOptions.buyTokenAddress)
: undefined,
seller: options.payOptions.sellerAddress
? quotes(options.payOptions.sellerAddress)
: undefined,
buttonLabel: options.payOptions.buttonLabel
? quotes(options.payOptions.buttonLabel)
: undefined,
transaction: transaction,
};

return `\
import { createThirdwebClient } from "thirdweb";
${imports.react.length > 0 ? `import { ${imports.react.join(", ")} } from "thirdweb/react";` : ""}
${imports.thirdweb.length > 0 ? `import { ${imports.thirdweb.join(", ")} } from "thirdweb";` : ""}
${stringifyImports(imports)}

const client = createThirdwebClient({
clientId: "....",
});

function Example() {
return (
<${componentName}
client={client}
chain={defineChain(${options.payOptions.buyTokenChain.id})}
amount="${options.payOptions.buyTokenAmount}"${options.payOptions.buyTokenAddress ? `\n\t tokenAddress="${options.payOptions.buyTokenAddress}"` : ""}${options.payOptions.sellerAddress ? `\n\t seller="${options.payOptions.sellerAddress}"` : ""}${options.payOptions.title ? `\n\t ${options.payOptions.widget === "checkout" ? "name" : "title"}="${options.payOptions.title}"` : ""}${options.payOptions.image ? `\n\t image="${options.payOptions.image}"` : ""}${options.payOptions.description ? `\n\t description="${options.payOptions.description}"` : ""}${options.payOptions.buttonLabel ? `\n\t buttonLabel="${options.payOptions.buttonLabel}"` : ""}${options.payOptions.paymentMethods && options.payOptions.paymentMethods.length > 0 ? `\n\t paymentMethods={${JSON.stringify(options.payOptions.paymentMethods)}}` : ""}${options.payOptions.currency ? `\n\t currency="${options.payOptions.currency}"` : ""}${
options.payOptions.widget === "transaction"
? `\n\t transaction={claimTo({
contract: nftContract,
quantity: 1n,
tokenId: 2n,
to: account?.address || "",
})}`
: ""
}
/>
<${componentName} ${stringifyProps(props)} />
);
}`;
}
40 changes: 6 additions & 34 deletions apps/playground-web/src/app/payments/components/LeftSection.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
"use client";

import {
CreditCardIcon,
ExternalLinkIcon,
FuelIcon,
PaletteIcon,
Settings2Icon,
} from "lucide-react";
import Link from "next/link";
import { CreditCardIcon, PaletteIcon, Settings2Icon } from "lucide-react";
import type React from "react";
import { useId, useState } from "react";
import type { Address } from "thirdweb";
Expand All @@ -30,11 +23,10 @@ export function LeftSection(props: {
setOptions: React.Dispatch<
React.SetStateAction<BridgeComponentsPlaygroundOptions>
>;
lockedWidget: "buy" | "checkout" | "transaction";
widget: "buy" | "checkout" | "transaction";
}) {
const { options, setOptions } = props;
const { theme, payOptions } = options;
const effectiveWidget = props.lockedWidget || payOptions.widget || "buy";
const setThemeType = (themeType: "dark" | "light") => {
setOptions((v) => ({
...v,
Expand Down Expand Up @@ -130,7 +122,7 @@ export function LeftSection(props: {
</section>

{/* Shared Chain and Token Selection - Always visible for Buy and Checkout modes */}
{(effectiveWidget === "buy" || effectiveWidget === "checkout") && (
{(props.widget === "buy" || props.widget === "checkout") && (
<div className="space-y-4">
{/* Chain selection */}
<div className="flex flex-col gap-2">
Expand Down Expand Up @@ -164,7 +156,7 @@ export function LeftSection(props: {
{/* Mode-specific form fields */}
<div className="my-2">
{/* Buy Mode - Amount and Payment Methods */}
{effectiveWidget === "buy" && (
{props.widget === "buy" && (
<div className="space-y-4">
<div className="flex flex-col gap-2">
<Label htmlFor={buyTokenAmountId}>Amount</Label>
Expand Down Expand Up @@ -245,7 +237,7 @@ export function LeftSection(props: {
)}

{/* Checkout Mode - Seller Address, Price and Payment Methods */}
{effectiveWidget === "checkout" && (
{props.widget === "checkout" && (
<div className="space-y-4">
<div className="flex flex-col gap-2">
<Label htmlFor={sellerAddressId}>Seller Address</Label>
Expand Down Expand Up @@ -347,7 +339,7 @@ export function LeftSection(props: {
)}

{/* Transaction Mode Options */}
{effectiveWidget === "transaction" && (
{props.widget === "transaction" && (
<div className="space-y-4">
<div className="flex flex-col gap-2">
<Label>Transaction</Label>
Expand Down Expand Up @@ -495,26 +487,6 @@ export function LeftSection(props: {
<Label htmlFor={"branding"}>Show Branding</Label>
</div>
</CollapsibleSection>

<CollapsibleSection icon={FuelIcon} title="Sponsor gas fees">
<div className="mt-4 flex items-start gap-6">
<div className="flex flex-col gap-2">
<p className="">
Abstract away gas fees for users of your app by enabling ERC-4337
Account Abstraction
</p>

<Link
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground"
href="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground"
target="_blank"
>
Learn more about Account Abstraction
<ExternalLinkIcon className="size-4" />
</Link>
</div>
</div>
</CollapsibleSection>
</div>
);
}
Loading
Loading