diff --git a/apps/playground-web/src/app/connect/pay/components/CodeGen.tsx b/apps/playground-web/src/app/connect/pay/components/CodeGen.tsx new file mode 100644 index 00000000000..fdf821e3c0f --- /dev/null +++ b/apps/playground-web/src/app/connect/pay/components/CodeGen.tsx @@ -0,0 +1,180 @@ +import { Suspense, lazy } from "react"; +import { CodeLoading } from "../../../../components/code/code.client"; +import type { PayEmbedPlaygroundOptions } from "./types"; + +const CodeClient = lazy( + () => import("../../../../components/code/code.client"), +); + +export function CodeGen(props: { + options: PayEmbedPlaygroundOptions; +}) { + return ( +
+ }> + } + // Need to add max-h in both places - TODO figure out a better way + className="xl:h-[calc(100vh-100px)]" + scrollableClassName="xl:h-[calc(100vh-100px)]" + /> + +
+ ); +} + +function getCode(options: PayEmbedPlaygroundOptions) { + const walletCodes: string[] = []; + const imports = { + react: ["PayEmbed"] as string[], + thirdweb: [] as string[], + wallets: [] as string[], + chains: [] 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; + + 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"); + } + + // Generate chain reference code + let chainCode: string; + if (isCustomChain && options.payOptions.buyTokenChain?.id) { + chainCode = `defineChain(${options.payOptions.buyTokenChain.id})`; + } else { + chainCode = "base"; + } + + for (const wallet of options.connectOptions.walletIds) { + walletCodes.push(`createWallet("${wallet}")`); + } + + if (options.connectOptions.walletIds.length > 0) { + imports.wallets.push("createWallet"); + } + + 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.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"); + } else { + themeProp = quotes("light"); + } + } + + if (options.connectOptions.enableAccountAbstraction) { + imports.chains.push("sepolia"); + } + + // Generate payOptions based on the mode + let payOptionsCode = "{"; + + if (options.payOptions.title || options.payOptions.image) { + payOptionsCode += ` + metadata: { + ${options.payOptions.title ? `name: ${quotes(options.payOptions.title)},` : ""} + ${options.payOptions.image ? `image: ${quotes(options.payOptions.image)},` : ""} + },`; + } + + // Add mode-specific options + if (options.payOptions.mode) { + payOptionsCode += ` + mode: "${options.payOptions.mode}",`; + + // Add buyWithCrypto and buyWithFiat if they're set to false + if (options.payOptions.buyWithCrypto === false) { + payOptionsCode += ` + buyWithCrypto: false,`; + } + + if (options.payOptions.buyWithFiat === false) { + payOptionsCode += ` + buyWithFiat: false,`; + } + + if (options.payOptions.mode === "fund_wallet" || !options.payOptions.mode) { + payOptionsCode += ` + prefillBuy: { + chain: ${chainCode}, + amount: ${options.payOptions.buyTokenAmount ? quotes(options.payOptions.buyTokenAmount) : '"0.01"'}, + ${options.payOptions.buyTokenInfo ? `token: ${JSON.stringify(options.payOptions.buyTokenInfo)},` : ""} + },`; + } else if (options.payOptions.mode === "direct_payment") { + payOptionsCode += ` + paymentInfo: { + chain: ${chainCode}, + sellerAddress: ${options.payOptions.sellerAddress ? quotes(options.payOptions.sellerAddress) : '"0x0000000000000000000000000000000000000000"'}, + amount: ${options.payOptions.buyTokenAmount ? quotes(options.payOptions.buyTokenAmount) : '"0.01"'}, + ${options.payOptions.buyTokenInfo ? `token: ${JSON.stringify(options.payOptions.buyTokenInfo)},` : ""} + },`; + } else if (options.payOptions.mode === "transaction") { + payOptionsCode += ` + transaction: claimTo({ + contract: myNftContract, + quantity: 1n, + tokenId: 0n, + to: "0x...", + }),`; + } + } + + payOptionsCode += ` + }`; + + const accountAbstractionCode = options.connectOptions.enableAccountAbstraction + ? `\n accountAbstraction: { + chain: ${isCustomChain ? `defineChain(${options.payOptions.buyTokenChain?.id})` : "base"}, + sponsorGas: true, + }` + : ""; + + const connectOptionsCode = `${accountAbstractionCode ? `{${accountAbstractionCode}\n }` : ""}`; + + 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";` : ""} +${imports.wallets.length > 0 ? `import { ${imports.wallets.join(", ")} } from "thirdweb/wallets";` : ""} +${imports.chains.length > 0 ? `import { ${imports.chains.join(", ")} } from "thirdweb/chains";` : ""} + +const client = createThirdwebClient({ + clientId: "....", +}); + +function Example() { + return ( + + ); +}`; +} + +function quotes(value: string) { + return `"${value}"`; +} diff --git a/apps/playground-web/src/app/connect/pay/components/types.ts b/apps/playground-web/src/app/connect/pay/components/types.ts new file mode 100644 index 00000000000..4c02e602a22 --- /dev/null +++ b/apps/playground-web/src/app/connect/pay/components/types.ts @@ -0,0 +1,43 @@ +import type { Chain } from "thirdweb"; +import type { LocaleId, ThemeOverrides, TokenInfo } from "thirdweb/react"; +import type { WalletId } from "thirdweb/wallets"; + +export type PayEmbedPlaygroundOptions = { + theme: { + type: "dark" | "light"; + darkColorOverrides: ThemeOverrides["colors"]; + lightColorOverrides: ThemeOverrides["colors"]; + }; + payOptions: { + mode?: "fund_wallet" | "direct_payment" | "transaction"; + title: string | undefined; + image: string | undefined; + + // fund_wallet mode options + buyTokenAddress: string | undefined; + buyTokenAmount: string | undefined; + buyTokenChain: Chain | undefined; + buyTokenInfo?: TokenInfo; + buyWithCrypto?: boolean; + buyWithFiat?: boolean; + + // direct_payment mode options + sellerAddress?: string; + + // transaction mode options + transactionData?: string; // Simplified for demo; could be more complex in real implementation + }; + connectOptions: { + walletIds: WalletId[]; + modalTitle: string | undefined; + modalTitleIcon: string | undefined; + localeId: LocaleId; + enableAuth: boolean; + enableAccountAbstraction: boolean; + termsOfServiceLink: string | undefined; + privacyPolicyLink: string | undefined; + buttonLabel: string | undefined; + ShowThirdwebBranding: boolean; + requireApproval: boolean; + }; +}; diff --git a/apps/playground-web/src/app/connect/pay/embed/LeftSection.tsx b/apps/playground-web/src/app/connect/pay/embed/LeftSection.tsx new file mode 100644 index 00000000000..a7baa1e4a53 --- /dev/null +++ b/apps/playground-web/src/app/connect/pay/embed/LeftSection.tsx @@ -0,0 +1,556 @@ +"use client"; + +import { CustomRadioGroup } from "@/components/ui/CustomRadioGroup"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + CreditCardIcon, + ExternalLinkIcon, + FuelIcon, + PaletteIcon, + Settings2Icon, +} from "lucide-react"; +import Link from "next/link"; +import type React from "react"; +import { useEffect, useState } from "react"; +import { defineChain } from "thirdweb/chains"; +import { Switch } from "../../../../components/ui/switch"; +import { CollapsibleSection } from "../../sign-in/components/CollapsibleSection"; +import { ColorFormGroup } from "../../sign-in/components/ColorFormGroup"; +import type { PayEmbedPlaygroundOptions } from "../components/types"; + +export function LeftSection(props: { + options: PayEmbedPlaygroundOptions; + setOptions: React.Dispatch>; +}) { + const { options, setOptions } = props; + const { theme, payOptions } = options; + const setThemeType = (themeType: "dark" | "light") => { + setOptions((v) => ({ + ...v, + theme: { + ...v.theme, + type: themeType, + }, + })); + }; + + // Local token state that persists between modes + const [tokenName, setTokenName] = useState( + payOptions.buyTokenInfo?.name || "", + ); + const [tokenSymbol, setTokenSymbol] = useState( + payOptions.buyTokenInfo?.symbol || "", + ); + const [tokenAddress, setTokenAddress] = useState( + payOptions.buyTokenInfo?.address || "", + ); + const [tokenIcon, setTokenIcon] = useState( + payOptions.buyTokenInfo?.icon || "", + ); + + // Determine if highlighting is needed - if any field is filled but not all required fields + const needsHighlighting = + (tokenName || tokenSymbol || tokenAddress) && + (!tokenName || !tokenSymbol || !tokenAddress); + + // Update the global state when all required fields are filled + useEffect(() => { + if (tokenName && tokenSymbol && tokenAddress) { + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + buyTokenInfo: { + name: tokenName, + symbol: tokenSymbol, + address: tokenAddress, + ...(tokenIcon ? { icon: tokenIcon } : {}), + }, + }, + })); + } else if (!tokenName && !tokenSymbol && !tokenAddress) { + // If all fields are empty, set buyTokenInfo to undefined + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + buyTokenInfo: undefined, + }, + })); + } + }, [tokenName, tokenSymbol, tokenAddress, tokenIcon, setOptions]); + + return ( +
+ {/* +
+ + */} + + +
+
+ + { + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + mode: value as + | "fund_wallet" + | "direct_payment" + | "transaction", + }, + })); + }} + value={payOptions.mode || "fund_wallet"} + /> +
+ + {/* Conditional form fields based on selected mode */} +
+ {/* Fund Wallet Mode Options */} + {(!payOptions.mode || payOptions.mode === "fund_wallet") && ( +
+
+
+ + + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + buyTokenAmount: e.target.value, + }, + })) + } + /> +
+ + {/* Chain selection */} +
+ + { + const chainId = Number.parseInt(e.target.value); + if (!Number.isNaN(chainId)) { + const chain = defineChain(chainId); + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + buyTokenChain: chain, + }, + })); + } + }} + /> +
+
+ + {/* Token selection for fund_wallet mode */} +
+
+
+
+ + setTokenName(e.target.value)} + className={ + needsHighlighting && !tokenName + ? "border-red-500" + : "" + } + /> +
+
+ + setTokenSymbol(e.target.value)} + className={ + needsHighlighting && !tokenSymbol + ? "border-red-500" + : "" + } + /> +
+
+
+
+ + setTokenAddress(e.target.value)} + className={ + needsHighlighting && !tokenAddress + ? "border-red-500" + : "" + } + /> +
+
+ + setTokenIcon(e.target.value)} + /> +
+
+ {needsHighlighting && ( +
+ All three token fields (Name, Symbol, and Address) are + required +
+ )} +
+
+
+ )} + + {/* Direct Payment Mode Options */} + {payOptions.mode === "direct_payment" && ( +
+
+ + + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + sellerAddress: e.target.value, + }, + })) + } + /> +
+ +
+
+ + + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + buyTokenAmount: e.target.value, + }, + })) + } + /> +
+ + {/* Chain selection */} +
+ + { + const chainId = Number.parseInt(e.target.value); + if (!Number.isNaN(chainId)) { + const chain = defineChain(chainId); + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + buyTokenChain: chain, + }, + })); + } + }} + /> +
+
+ + {/* Token selection for direct_payment mode - shares state with fund_wallet mode */} +
+
+
+
+ + setTokenName(e.target.value)} + className={ + needsHighlighting && !tokenName + ? "border-red-500" + : "" + } + /> +
+
+ + setTokenSymbol(e.target.value)} + className={ + needsHighlighting && !tokenSymbol + ? "border-red-500" + : "" + } + /> +
+
+
+
+ + setTokenAddress(e.target.value)} + className={ + needsHighlighting && !tokenAddress + ? "border-red-500" + : "" + } + /> +
+
+ + setTokenIcon(e.target.value)} + /> +
+
+ {needsHighlighting && ( +
+ All three token fields (Name, Symbol, and Address) are + required +
+ )} +
+
+
+ )} + + {/* Transaction Mode Options */} + {payOptions.mode === "transaction" && ( +
+
+ +

+ This demo uses a ERC1155 NFT claim transaction. Check the + code section for the transaction details. You can provide + any contract call or transfer here, the price will be + automatically inferred from the transaction itself. +

+
+
+ )} +
+ +
+ +
+
+ + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + buyWithCrypto: checked === true, + }, + })) + } + /> + +
+
+ + setOptions((v) => ({ + ...v, + payOptions: { + ...v.payOptions, + buyWithFiat: checked === true, + }, + })) + } + /> + +
+
+
+
+
+ + +
+ {/* Locale TODO (pay) */} + {/* */} + +
+ {/* Modal title */} +
+ + + setOptions((v) => ({ + ...v, + payOptions: { + ...payOptions, + title: e.target.value, + }, + })) + } + /> +
+ + {/* Modal Title Icon */} +
+ + + setOptions((v) => ({ + ...v, + payOptions: { + ...payOptions, + image: e.target.value, + }, + })) + } + /> +
+
+
+
+ + + {/* Theme */} +
+ + +
+ +
+ + {/* Colors */} + { + setOptions((v) => ({ + ...v, + theme: newTheme, + })); + }} + /> + + + +
+
+

+ Abstract away gas fees for users of your app by enabling ERC-4337 + Account Abstraction +

+ + + Learn more about Account Abstraction + + +
+ { + setOptions((v) => ({ + ...v, + connectOptions: { + ...v.connectOptions, + enableAccountAbstraction: checked, + }, + })); + }} + /> +
+
+
+ ); +} diff --git a/apps/playground-web/src/app/connect/pay/embed/RightSection.tsx b/apps/playground-web/src/app/connect/pay/embed/RightSection.tsx new file mode 100644 index 00000000000..ed64e98b7b8 --- /dev/null +++ b/apps/playground-web/src/app/connect/pay/embed/RightSection.tsx @@ -0,0 +1,240 @@ +"use client"; + +import { abstractWallet } from "@abstract-foundation/agw-react/thirdweb"; +import { usePathname } from "next/navigation"; +import { useState } from "react"; +import { ZERO_ADDRESS, getContract } from "thirdweb"; +import { base } from "thirdweb/chains"; +import { claimTo } from "thirdweb/extensions/erc1155"; +import { + PayEmbed, + darkTheme, + lightTheme, + useActiveAccount, +} from "thirdweb/react"; +import { type WalletId, createWallet } from "thirdweb/wallets"; +import { Button } from "../../../../components/ui/button"; +import { THIRDWEB_CLIENT } from "../../../../lib/client"; +import { cn } from "../../../../lib/utils"; +import { CodeGen } from "../components/CodeGen"; +import type { PayEmbedPlaygroundOptions } from "../components/types"; + +const nftContract = getContract({ + address: "0xf0d0CBf84005Dd4eC81364D1f5D7d896Bd53D1B8", + chain: base, + client: THIRDWEB_CLIENT, +}); + +type Tab = "ui" | "code"; + +export function RightSection(props: { + options: PayEmbedPlaygroundOptions; + tab?: string; +}) { + const pathname = usePathname(); + const [previewTab, _setPreviewTab] = useState(() => { + return "ui"; + }); + + function setPreviewTab(tab: "ui" | "code") { + _setPreviewTab(tab); + window.history.replaceState({}, "", `${pathname}?tab=${tab}`); + } + + const account = useActiveAccount(); + + const themeObj = + props.options.theme.type === "dark" + ? darkTheme({ + colors: props.options.theme.darkColorOverrides, + }) + : lightTheme({ + colors: props.options.theme.lightColorOverrides, + }); + + const embed = ( + + ); + + return ( +
+ setPreviewTab("ui"), + }, + { + name: "Code", + isActive: previewTab === "code", + onClick: () => setPreviewTab("code"), + }, + ]} + /> + +
+ + + {previewTab === "ui" && embed} + + {previewTab === "code" && } +
+
+ ); +} + +/** + * @internal + */ +export function getWallets(walletIds: WalletId[]) { + const wallets = [ + ...walletIds.map((id) => { + if (id === "xyz.abs") { + return abstractWallet(); + } + return createWallet(id); + }), + ]; + + return wallets; +} + +function BackgroundPattern() { + const color = "hsl(var(--foreground)/15%)"; + return ( +
+ ); +} + +function TabButtons(props: { + tabs: Array<{ + name: string; + isActive: boolean; + onClick: () => void; + }>; +}) { + return ( +
+
+ {props.tabs.map((tab) => ( + + ))} +
+
+ ); +} diff --git a/apps/playground-web/src/app/connect/pay/embed/page.tsx b/apps/playground-web/src/app/connect/pay/embed/page.tsx new file mode 100644 index 00000000000..983789b7d69 --- /dev/null +++ b/apps/playground-web/src/app/connect/pay/embed/page.tsx @@ -0,0 +1,67 @@ +"use client"; +import { use, useState } from "react"; +import { NATIVE_TOKEN_ADDRESS } from "thirdweb"; +import { base } from "thirdweb/chains"; +import type { PayEmbedPlaygroundOptions } from "../components/types"; +import { LeftSection } from "./LeftSection"; +import { RightSection } from "./RightSection"; + +// NOTE: Only set the values that are actually the default values used by Connect component +const defaultConnectOptions: PayEmbedPlaygroundOptions = { + theme: { + type: "dark", + darkColorOverrides: {}, + lightColorOverrides: {}, + }, + payOptions: { + mode: "fund_wallet", + title: "", + image: "", + buyTokenAddress: NATIVE_TOKEN_ADDRESS, + buyTokenAmount: "0.01", + buyTokenChain: base, + sellerAddress: "", + transactionData: "", + buyTokenInfo: undefined, + buyWithCrypto: true, + buyWithFiat: true, + }, + connectOptions: { + walletIds: [ + "io.metamask", + "com.coinbase.wallet", + "me.rainbow", + "io.rabby", + "io.zerion.wallet", + ], + modalTitle: undefined, + modalTitleIcon: undefined, + localeId: "en_US", + enableAuth: false, + termsOfServiceLink: undefined, + privacyPolicyLink: undefined, + enableAccountAbstraction: false, + buttonLabel: undefined, + ShowThirdwebBranding: true, + requireApproval: false, + }, +}; + +export default function PayEmbedPlayground(props: { + searchParams: Promise<{ tab: string }>; +}) { + const searchParams = use(props.searchParams); + const [options, setOptions] = useState( + defaultConnectOptions, + ); + + return ( +
+
+ +
+ + +
+ ); +} diff --git a/apps/playground-web/src/app/connect/pay/fund-wallet/page.tsx b/apps/playground-web/src/app/connect/pay/fund-wallet/page.tsx new file mode 100644 index 00000000000..b2b5084d3f6 --- /dev/null +++ b/apps/playground-web/src/app/connect/pay/fund-wallet/page.tsx @@ -0,0 +1,80 @@ +import { APIHeader } from "@/components/blocks/APIHeader"; +import { CodeExample } from "@/components/code/code-example"; +import { StyledPayEmbedPreview } from "@/components/pay/embed"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { metadataBase } from "@/lib/constants"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + metadataBase, + title: "Fund wallets | thirdweb Universal Bridge", + description: + "The easiest way for users to fund their wallets. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.", +}; + +export default function Page() { + return ( + +
+ + Onramp users with credit card & cross-chain crypto payments — + and generate revenue for each user transaction. + + } + docsLink="https://portal.thirdweb.com/connect/pay/get-started" + heroLink="/pay.png" + /> + +
+ +
+
+
+ ); +} + +function StyledPayEmbed() { + return ( + <> +
+

+ Fund Wallet +

+

+ Inline component that allows users to buy any currency. +
+ Customize theme, currency, amounts, payment methods and more. +

+
+ + } + code={` + import { PayEmbed } from "thirdweb/react"; + + function App() { + return ( + + ); + };`} + lang="tsx" + /> + + ); +} diff --git a/apps/playground-web/src/app/connect/pay/page.tsx b/apps/playground-web/src/app/connect/pay/page.tsx index cc2db0883dc..27a57696618 100644 --- a/apps/playground-web/src/app/connect/pay/page.tsx +++ b/apps/playground-web/src/app/connect/pay/page.tsx @@ -1,23 +1,25 @@ -import { APIHeader } from "@/components/blocks/APIHeader"; -import { CodeExample } from "@/components/code/code-example"; -import { StyledPayEmbedPreview } from "@/components/pay/embed"; import ThirdwebProvider from "@/components/thirdweb-provider"; import { metadataBase } from "@/lib/constants"; import type { Metadata } from "next"; +import { APIHeader } from "../../../components/blocks/APIHeader"; +import PayEmbedPlayground from "./embed/page"; export const metadata: Metadata = { metadataBase, - title: "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Pay", + title: + "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Universal Bridge", description: - "The easiest way for users to transact in your app. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.", + "The easiest way for users to transact in your app. Onramp users, pay with any token and generate revenue for each user transaction. Integrate for free.", }; -export default function Page() { +export default function Page(props: { + searchParams: Promise<{ tab: string }>; +}) { return ( -
+
Onramp users with credit card & cross-chain crypto payments — @@ -28,53 +30,8 @@ export default function Page() { heroLink="/pay.png" /> -
- -
-
-
- ); -} - -function StyledPayEmbed() { - return ( - <> -
-

- Fund Wallet -

-

- Inline component that allows users to buy any currency. -
- Customize theme, currency, amounts, payment methods and more. -

+
- - } - code={` - import { PayEmbed } from "thirdweb/react"; - - function App() { - return ( - - ); - };`} - lang="tsx" - /> - + ); } diff --git a/apps/playground-web/src/app/connect/sign-in/button/LeftSection.tsx b/apps/playground-web/src/app/connect/sign-in/button/LeftSection.tsx index 5c1df78b2c7..38bcdc9ce82 100644 --- a/apps/playground-web/src/app/connect/sign-in/button/LeftSection.tsx +++ b/apps/playground-web/src/app/connect/sign-in/button/LeftSection.tsx @@ -201,8 +201,13 @@ export function LeftSection(props: { {/* Colors */} { + setConnectOptions((v) => ({ + ...v, + theme: newTheme, + })); + }} /> diff --git a/apps/playground-web/src/app/connect/sign-in/components/ColorFormGroup.tsx b/apps/playground-web/src/app/connect/sign-in/components/ColorFormGroup.tsx index 4747adb8464..cf8cf072fa3 100644 --- a/apps/playground-web/src/app/connect/sign-in/components/ColorFormGroup.tsx +++ b/apps/playground-web/src/app/connect/sign-in/components/ColorFormGroup.tsx @@ -7,21 +7,19 @@ import { ColorInput } from "./ColorInput"; import type { ConnectPlaygroundOptions } from "./types"; export function ColorFormGroup(props: { - connectOptions: ConnectPlaygroundOptions; - setConnectOptions: React.Dispatch< - React.SetStateAction - >; + theme: ConnectPlaygroundOptions["theme"]; + onChange: (value: ConnectPlaygroundOptions["theme"]) => void; }) { const [search, setSearch] = useState(""); - const { connectOptions, setConnectOptions } = props; + const { theme, onChange } = props; const themeObj = - connectOptions.theme.type === "dark" + theme.type === "dark" ? darkTheme({ - colors: connectOptions.theme.darkColorOverrides, + colors: theme.darkColorOverrides, }) : lightTheme({ - colors: connectOptions.theme.lightColorOverrides, + colors: theme.lightColorOverrides, }); const colorSectionsToShow = colorSections @@ -72,23 +70,19 @@ export function ColorFormGroup(props: { className="size-10" value={themeObj.colors[color.colorId]} onChange={(value) => { - setConnectOptions((v) => { - const overridesKey = - v.theme.type === "dark" - ? "darkColorOverrides" - : "lightColorOverrides"; + const overridesKey = + theme.type === "dark" + ? "darkColorOverrides" + : "lightColorOverrides"; - return { - ...v, - theme: { - ...v.theme, - [overridesKey]: { - ...v.theme[overridesKey], - [color.colorId]: value, - }, - }, - }; - }); + const newTheme = { + ...theme, + [overridesKey]: { + ...theme[overridesKey], + [color.colorId]: value, + }, + }; + onChange(newTheme); }} />
diff --git a/apps/playground-web/src/app/navLinks.ts b/apps/playground-web/src/app/navLinks.ts index 268e57304f2..a1ce0f3f712 100644 --- a/apps/playground-web/src/app/navLinks.ts +++ b/apps/playground-web/src/app/navLinks.ts @@ -25,49 +25,53 @@ export const staticSidebarLinks: SidebarLink[] = [ ], }, { - name: "Account Abstraction", + name: "In-App Wallet", expanded: false, links: [ { - name: "Connect", - href: "/connect/account-abstraction/connect", + name: "Any Auth", + href: "/connect/in-app-wallet", }, { - name: "Sponsor Gas", - href: "/connect/account-abstraction/sponsor", + name: "Ecosystems", + href: "/connect/in-app-wallet/ecosystem", }, { - name: "Native AA (zkSync)", - href: "/connect/account-abstraction/native-aa", + name: "Sponsor Gas", + href: "/connect/in-app-wallet/sponsor", }, ], }, { - name: "In-App Wallet", + name: "Account Abstraction", expanded: false, links: [ { - name: "Any Auth", - href: "/connect/in-app-wallet", + name: "Connect", + href: "/connect/account-abstraction/connect", }, { - name: "Ecosystems", - href: "/connect/in-app-wallet/ecosystem", + name: "Sponsor Gas", + href: "/connect/account-abstraction/sponsor", }, { - name: "Sponsor Gas", - href: "/connect/in-app-wallet/sponsor", + name: "Native AA (zkSync)", + href: "/connect/account-abstraction/native-aa", }, ], }, { - name: "Pay", + name: "Universal Bridge", expanded: false, links: [ { - name: "Fund Wallet", + name: "UI Component", href: "/connect/pay", }, + { + name: "Fund Wallet", + href: "/connect/pay/fund-wallet", + }, { name: "Commerce", href: "/connect/pay/commerce", diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx index 5970ad46fbd..f49d0684539 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx @@ -6,7 +6,11 @@ import { formatNumber } from "../../../../../../utils/formatNumber.js"; import { toTokens } from "../../../../../../utils/units.js"; import type { Account } from "../../../../../../wallets/interfaces/wallet.js"; import { useCustomTheme } from "../../../../../core/design-system/CustomThemeProvider.js"; -import { fontSize, spacing } from "../../../../../core/design-system/index.js"; +import { + fontSize, + iconSize, + spacing, +} from "../../../../../core/design-system/index.js"; import type { PayUIOptions } from "../../../../../core/hooks/connection/ConnectButtonProps.js"; import { useChainMetadata } from "../../../../../core/hooks/others/useChainQuery.js"; import { useWalletBalance } from "../../../../../core/hooks/others/useWalletBalance.js"; @@ -26,6 +30,7 @@ import { Button } from "../../../components/buttons.js"; import { Text } from "../../../components/text.js"; import { TokenSymbol } from "../../../components/token/TokenSymbol.js"; import { ConnectButton } from "../../ConnectButton.js"; +import { OutlineWalletIcon } from "../../icons/OutlineWalletIcon.js"; import { formatTokenBalance } from "../formatTokenBalance.js"; import { type ERC20OrNativeToken, @@ -95,6 +100,38 @@ export function TransactionModeScreen(props: { return ; } + if (!activeAccount) { + return ( + + + + + + + + + Please connect a wallet to continue + + + + + + + + ); + } + if (transactionCostAndDataError || chainDataError) { return ( (initialTokenAmount); const deferredTokenAmount = useDebouncedValue(tokenAmount, 300); + useEffect(() => { + if (prefillBuy?.amount) { + setTokenAmount(prefillBuy.amount); + } + if (prefillBuy?.chain) { + setToChain(prefillBuy.chain); + } + if (prefillBuy?.token) { + setToToken(prefillBuy.token); + } + }, [prefillBuy?.amount, prefillBuy?.chain, prefillBuy?.token]); + // Destination chain and token selection ----------------------------------- const [toChain, setToChain] = useState( // use prefill chain if available