Skip to content

Commit 56bc4ae

Browse files
authoredMar 19, 2025··
[Playground] feat: Add PayEmbed component playground (#6493)
1 parent 71f83a8 commit 56bc4ae

File tree

12 files changed

+1274
-99
lines changed

12 files changed

+1274
-99
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { Suspense, lazy } from "react";
2+
import { CodeLoading } from "../../../../components/code/code.client";
3+
import type { PayEmbedPlaygroundOptions } from "./types";
4+
5+
const CodeClient = lazy(
6+
() => import("../../../../components/code/code.client"),
7+
);
8+
9+
export function CodeGen(props: {
10+
options: PayEmbedPlaygroundOptions;
11+
}) {
12+
return (
13+
<div className="flex w-full grow flex-col">
14+
<Suspense fallback={<CodeLoading />}>
15+
<CodeClient
16+
code={getCode(props.options)}
17+
lang="tsx"
18+
loader={<CodeLoading />}
19+
// Need to add max-h in both places - TODO figure out a better way
20+
className="xl:h-[calc(100vh-100px)]"
21+
scrollableClassName="xl:h-[calc(100vh-100px)]"
22+
/>
23+
</Suspense>
24+
</div>
25+
);
26+
}
27+
28+
function getCode(options: PayEmbedPlaygroundOptions) {
29+
const walletCodes: string[] = [];
30+
const imports = {
31+
react: ["PayEmbed"] as string[],
32+
thirdweb: [] as string[],
33+
wallets: [] as string[],
34+
chains: [] as string[],
35+
};
36+
37+
// Check if we have a custom chain (not base chain which has id 8453)
38+
const isCustomChain =
39+
options.payOptions.buyTokenChain &&
40+
options.payOptions.buyTokenChain.id !== 8453;
41+
42+
if (isCustomChain) {
43+
// Add defineChain to imports if using a custom chain
44+
imports.thirdweb.push("defineChain");
45+
} else {
46+
// Otherwise use the base chain
47+
imports.chains.push("base");
48+
}
49+
50+
// Generate chain reference code
51+
let chainCode: string;
52+
if (isCustomChain && options.payOptions.buyTokenChain?.id) {
53+
chainCode = `defineChain(${options.payOptions.buyTokenChain.id})`;
54+
} else {
55+
chainCode = "base";
56+
}
57+
58+
for (const wallet of options.connectOptions.walletIds) {
59+
walletCodes.push(`createWallet("${wallet}")`);
60+
}
61+
62+
if (options.connectOptions.walletIds.length > 0) {
63+
imports.wallets.push("createWallet");
64+
}
65+
66+
let themeProp: string | undefined;
67+
if (
68+
options.theme.type === "dark" &&
69+
Object.keys(options.theme.darkColorOverrides || {}).length > 0
70+
) {
71+
themeProp = `darkTheme({
72+
colors: ${JSON.stringify(options.theme.darkColorOverrides)},
73+
})`;
74+
imports.react.push("darkTheme");
75+
}
76+
77+
if (options.theme.type === "light") {
78+
if (Object.keys(options.theme.lightColorOverrides || {}).length > 0) {
79+
themeProp = `lightTheme({
80+
colors: ${JSON.stringify(options.theme.lightColorOverrides)},
81+
})`;
82+
imports.react.push("lightTheme");
83+
} else {
84+
themeProp = quotes("light");
85+
}
86+
}
87+
88+
if (options.connectOptions.enableAccountAbstraction) {
89+
imports.chains.push("sepolia");
90+
}
91+
92+
// Generate payOptions based on the mode
93+
let payOptionsCode = "{";
94+
95+
if (options.payOptions.title || options.payOptions.image) {
96+
payOptionsCode += `
97+
metadata: {
98+
${options.payOptions.title ? `name: ${quotes(options.payOptions.title)},` : ""}
99+
${options.payOptions.image ? `image: ${quotes(options.payOptions.image)},` : ""}
100+
},`;
101+
}
102+
103+
// Add mode-specific options
104+
if (options.payOptions.mode) {
105+
payOptionsCode += `
106+
mode: "${options.payOptions.mode}",`;
107+
108+
// Add buyWithCrypto and buyWithFiat if they're set to false
109+
if (options.payOptions.buyWithCrypto === false) {
110+
payOptionsCode += `
111+
buyWithCrypto: false,`;
112+
}
113+
114+
if (options.payOptions.buyWithFiat === false) {
115+
payOptionsCode += `
116+
buyWithFiat: false,`;
117+
}
118+
119+
if (options.payOptions.mode === "fund_wallet" || !options.payOptions.mode) {
120+
payOptionsCode += `
121+
prefillBuy: {
122+
chain: ${chainCode},
123+
amount: ${options.payOptions.buyTokenAmount ? quotes(options.payOptions.buyTokenAmount) : '"0.01"'},
124+
${options.payOptions.buyTokenInfo ? `token: ${JSON.stringify(options.payOptions.buyTokenInfo)},` : ""}
125+
},`;
126+
} else if (options.payOptions.mode === "direct_payment") {
127+
payOptionsCode += `
128+
paymentInfo: {
129+
chain: ${chainCode},
130+
sellerAddress: ${options.payOptions.sellerAddress ? quotes(options.payOptions.sellerAddress) : '"0x0000000000000000000000000000000000000000"'},
131+
amount: ${options.payOptions.buyTokenAmount ? quotes(options.payOptions.buyTokenAmount) : '"0.01"'},
132+
${options.payOptions.buyTokenInfo ? `token: ${JSON.stringify(options.payOptions.buyTokenInfo)},` : ""}
133+
},`;
134+
} else if (options.payOptions.mode === "transaction") {
135+
payOptionsCode += `
136+
transaction: claimTo({
137+
contract: myNftContract,
138+
quantity: 1n,
139+
tokenId: 0n,
140+
to: "0x...",
141+
}),`;
142+
}
143+
}
144+
145+
payOptionsCode += `
146+
}`;
147+
148+
const accountAbstractionCode = options.connectOptions.enableAccountAbstraction
149+
? `\n accountAbstraction: {
150+
chain: ${isCustomChain ? `defineChain(${options.payOptions.buyTokenChain?.id})` : "base"},
151+
sponsorGas: true,
152+
}`
153+
: "";
154+
155+
const connectOptionsCode = `${accountAbstractionCode ? `{${accountAbstractionCode}\n }` : ""}`;
156+
157+
return `\
158+
import { createThirdwebClient } from "thirdweb";
159+
${imports.react.length > 0 ? `import { ${imports.react.join(", ")} } from "thirdweb/react";` : ""}
160+
${imports.thirdweb.length > 0 ? `import { ${imports.thirdweb.join(", ")} } from "thirdweb";` : ""}
161+
${imports.wallets.length > 0 ? `import { ${imports.wallets.join(", ")} } from "thirdweb/wallets";` : ""}
162+
${imports.chains.length > 0 ? `import { ${imports.chains.join(", ")} } from "thirdweb/chains";` : ""}
163+
164+
const client = createThirdwebClient({
165+
clientId: "....",
166+
});
167+
168+
function Example() {
169+
return (
170+
<PayEmbed
171+
client={client}
172+
payOptions={${payOptionsCode}}${connectOptionsCode ? `\n connectOptions={${connectOptionsCode}}` : ""}${themeProp ? `\n theme={${themeProp}}` : ""}
173+
/>
174+
);
175+
}`;
176+
}
177+
178+
function quotes(value: string) {
179+
return `"${value}"`;
180+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { Chain } from "thirdweb";
2+
import type { LocaleId, ThemeOverrides, TokenInfo } from "thirdweb/react";
3+
import type { WalletId } from "thirdweb/wallets";
4+
5+
export type PayEmbedPlaygroundOptions = {
6+
theme: {
7+
type: "dark" | "light";
8+
darkColorOverrides: ThemeOverrides["colors"];
9+
lightColorOverrides: ThemeOverrides["colors"];
10+
};
11+
payOptions: {
12+
mode?: "fund_wallet" | "direct_payment" | "transaction";
13+
title: string | undefined;
14+
image: string | undefined;
15+
16+
// fund_wallet mode options
17+
buyTokenAddress: string | undefined;
18+
buyTokenAmount: string | undefined;
19+
buyTokenChain: Chain | undefined;
20+
buyTokenInfo?: TokenInfo;
21+
buyWithCrypto?: boolean;
22+
buyWithFiat?: boolean;
23+
24+
// direct_payment mode options
25+
sellerAddress?: string;
26+
27+
// transaction mode options
28+
transactionData?: string; // Simplified for demo; could be more complex in real implementation
29+
};
30+
connectOptions: {
31+
walletIds: WalletId[];
32+
modalTitle: string | undefined;
33+
modalTitleIcon: string | undefined;
34+
localeId: LocaleId;
35+
enableAuth: boolean;
36+
enableAccountAbstraction: boolean;
37+
termsOfServiceLink: string | undefined;
38+
privacyPolicyLink: string | undefined;
39+
buttonLabel: string | undefined;
40+
ShowThirdwebBranding: boolean;
41+
requireApproval: boolean;
42+
};
43+
};

‎apps/playground-web/src/app/connect/pay/embed/LeftSection.tsx

+556
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
"use client";
2+
3+
import { abstractWallet } from "@abstract-foundation/agw-react/thirdweb";
4+
import { usePathname } from "next/navigation";
5+
import { useState } from "react";
6+
import { ZERO_ADDRESS, getContract } from "thirdweb";
7+
import { base } from "thirdweb/chains";
8+
import { claimTo } from "thirdweb/extensions/erc1155";
9+
import {
10+
PayEmbed,
11+
darkTheme,
12+
lightTheme,
13+
useActiveAccount,
14+
} from "thirdweb/react";
15+
import { type WalletId, createWallet } from "thirdweb/wallets";
16+
import { Button } from "../../../../components/ui/button";
17+
import { THIRDWEB_CLIENT } from "../../../../lib/client";
18+
import { cn } from "../../../../lib/utils";
19+
import { CodeGen } from "../components/CodeGen";
20+
import type { PayEmbedPlaygroundOptions } from "../components/types";
21+
22+
const nftContract = getContract({
23+
address: "0xf0d0CBf84005Dd4eC81364D1f5D7d896Bd53D1B8",
24+
chain: base,
25+
client: THIRDWEB_CLIENT,
26+
});
27+
28+
type Tab = "ui" | "code";
29+
30+
export function RightSection(props: {
31+
options: PayEmbedPlaygroundOptions;
32+
tab?: string;
33+
}) {
34+
const pathname = usePathname();
35+
const [previewTab, _setPreviewTab] = useState<Tab>(() => {
36+
return "ui";
37+
});
38+
39+
function setPreviewTab(tab: "ui" | "code") {
40+
_setPreviewTab(tab);
41+
window.history.replaceState({}, "", `${pathname}?tab=${tab}`);
42+
}
43+
44+
const account = useActiveAccount();
45+
46+
const themeObj =
47+
props.options.theme.type === "dark"
48+
? darkTheme({
49+
colors: props.options.theme.darkColorOverrides,
50+
})
51+
: lightTheme({
52+
colors: props.options.theme.lightColorOverrides,
53+
});
54+
55+
const embed = (
56+
<PayEmbed
57+
client={THIRDWEB_CLIENT}
58+
theme={themeObj}
59+
// locale={connectOptions.localeId}
60+
connectOptions={{
61+
accountAbstraction: props.options.connectOptions
62+
.enableAccountAbstraction
63+
? {
64+
chain: props.options.payOptions.buyTokenChain || base,
65+
sponsorGas: true,
66+
}
67+
: undefined,
68+
}}
69+
payOptions={{
70+
metadata: {
71+
name:
72+
props.options.payOptions.title ||
73+
(props.options.payOptions.mode === "transaction"
74+
? "Transaction"
75+
: props.options.payOptions.mode === "direct_payment"
76+
? "Purchase"
77+
: "Fund Wallet"),
78+
image:
79+
props.options.payOptions.image ||
80+
`https://placehold.co/600x400/${
81+
props.options.theme.type === "dark"
82+
? "1d1d23/7c7a85"
83+
: "f2eff3/6f6d78"
84+
}?text=Your%20Product%20Here&font=roboto`,
85+
},
86+
87+
// Mode-specific options
88+
// biome-ignore lint/suspicious/noExplicitAny: union type
89+
mode: (props.options.payOptions.mode as any) || "fund_wallet",
90+
91+
// Only include buyWithCrypto and buyWithFiat when they're false
92+
...(props.options.payOptions.buyWithCrypto === false
93+
? { buyWithCrypto: false }
94+
: {}),
95+
...(props.options.payOptions.buyWithFiat === false
96+
? { buyWithFiat: false }
97+
: {}),
98+
99+
...(props.options.payOptions.mode === "fund_wallet" ||
100+
!props.options.payOptions.mode
101+
? {
102+
// Fund wallet mode options
103+
prefillBuy: {
104+
chain: props.options.payOptions.buyTokenChain || base,
105+
amount: props.options.payOptions.buyTokenAmount || "0.01",
106+
...(props.options.payOptions.buyTokenInfo
107+
? {
108+
token: props.options.payOptions.buyTokenInfo,
109+
}
110+
: {}),
111+
},
112+
}
113+
: {}),
114+
115+
...(props.options.payOptions.mode === "direct_payment"
116+
? {
117+
// Direct payment mode options
118+
paymentInfo: {
119+
chain: props.options.payOptions.buyTokenChain || base,
120+
sellerAddress:
121+
props.options.payOptions.sellerAddress ||
122+
"0x0000000000000000000000000000000000000000",
123+
amount: props.options.payOptions.buyTokenAmount || "0.01",
124+
...(props.options.payOptions.buyTokenInfo
125+
? {
126+
token: props.options.payOptions.buyTokenInfo,
127+
}
128+
: {}),
129+
},
130+
}
131+
: {}),
132+
133+
...(props.options.payOptions.mode === "transaction"
134+
? {
135+
// Transaction mode options (simplified for demo)
136+
transaction: claimTo({
137+
contract: nftContract,
138+
quantity: 1n,
139+
tokenId: 2n,
140+
to: account?.address || ZERO_ADDRESS,
141+
}),
142+
}
143+
: {}),
144+
}}
145+
/>
146+
);
147+
148+
return (
149+
<div className="flex shrink-0 flex-col gap-4 xl:sticky xl:top-0 xl:max-h-[100vh] xl:w-[764px]">
150+
<TabButtons
151+
tabs={[
152+
{
153+
name: "UI",
154+
isActive: previewTab === "ui",
155+
onClick: () => setPreviewTab("ui"),
156+
},
157+
{
158+
name: "Code",
159+
isActive: previewTab === "code",
160+
onClick: () => setPreviewTab("code"),
161+
},
162+
]}
163+
/>
164+
165+
<div
166+
className={cn(
167+
"relative flex min-h-[300px] grow justify-center rounded-lg",
168+
previewTab !== "code" && "items-center",
169+
)}
170+
>
171+
<BackgroundPattern />
172+
173+
{previewTab === "ui" && embed}
174+
175+
{previewTab === "code" && <CodeGen options={props.options} />}
176+
</div>
177+
</div>
178+
);
179+
}
180+
181+
/**
182+
* @internal
183+
*/
184+
export function getWallets(walletIds: WalletId[]) {
185+
const wallets = [
186+
...walletIds.map((id) => {
187+
if (id === "xyz.abs") {
188+
return abstractWallet();
189+
}
190+
return createWallet(id);
191+
}),
192+
];
193+
194+
return wallets;
195+
}
196+
197+
function BackgroundPattern() {
198+
const color = "hsl(var(--foreground)/15%)";
199+
return (
200+
<div
201+
className="absolute inset-0 z-[-1]"
202+
style={{
203+
backgroundImage: `radial-gradient(${color} 1px, transparent 1px)`,
204+
backgroundSize: "24px 24px",
205+
maskImage:
206+
"radial-gradient(ellipse 100% 100% at 50% 50%, black 30%, transparent 60%)",
207+
}}
208+
/>
209+
);
210+
}
211+
212+
function TabButtons(props: {
213+
tabs: Array<{
214+
name: string;
215+
isActive: boolean;
216+
onClick: () => void;
217+
}>;
218+
}) {
219+
return (
220+
<div>
221+
<div className="flex justify-start gap-1 rounded-lg border bg-muted p-2 shadow-md md:inline-flex">
222+
{props.tabs.map((tab) => (
223+
<Button
224+
key={tab.name}
225+
onClick={tab.onClick}
226+
variant="ghost"
227+
className={cn(
228+
"gap-2 px-4 text-base",
229+
tab.isActive
230+
? "bg-accent text-foreground"
231+
: "bg-transparent text-muted-foreground",
232+
)}
233+
>
234+
{tab.name}
235+
</Button>
236+
))}
237+
</div>
238+
</div>
239+
);
240+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"use client";
2+
import { use, useState } from "react";
3+
import { NATIVE_TOKEN_ADDRESS } from "thirdweb";
4+
import { base } from "thirdweb/chains";
5+
import type { PayEmbedPlaygroundOptions } from "../components/types";
6+
import { LeftSection } from "./LeftSection";
7+
import { RightSection } from "./RightSection";
8+
9+
// NOTE: Only set the values that are actually the default values used by Connect component
10+
const defaultConnectOptions: PayEmbedPlaygroundOptions = {
11+
theme: {
12+
type: "dark",
13+
darkColorOverrides: {},
14+
lightColorOverrides: {},
15+
},
16+
payOptions: {
17+
mode: "fund_wallet",
18+
title: "",
19+
image: "",
20+
buyTokenAddress: NATIVE_TOKEN_ADDRESS,
21+
buyTokenAmount: "0.01",
22+
buyTokenChain: base,
23+
sellerAddress: "",
24+
transactionData: "",
25+
buyTokenInfo: undefined,
26+
buyWithCrypto: true,
27+
buyWithFiat: true,
28+
},
29+
connectOptions: {
30+
walletIds: [
31+
"io.metamask",
32+
"com.coinbase.wallet",
33+
"me.rainbow",
34+
"io.rabby",
35+
"io.zerion.wallet",
36+
],
37+
modalTitle: undefined,
38+
modalTitleIcon: undefined,
39+
localeId: "en_US",
40+
enableAuth: false,
41+
termsOfServiceLink: undefined,
42+
privacyPolicyLink: undefined,
43+
enableAccountAbstraction: false,
44+
buttonLabel: undefined,
45+
ShowThirdwebBranding: true,
46+
requireApproval: false,
47+
},
48+
};
49+
50+
export default function PayEmbedPlayground(props: {
51+
searchParams: Promise<{ tab: string }>;
52+
}) {
53+
const searchParams = use(props.searchParams);
54+
const [options, setOptions] = useState<PayEmbedPlaygroundOptions>(
55+
defaultConnectOptions,
56+
);
57+
58+
return (
59+
<div className="relative flex flex-col-reverse gap-6 xl:min-h-[900px] xl:flex-row xl:gap-6">
60+
<div className="grow border-b pb-10 xl:mb-0 xl:border-r xl:border-b-0 xl:pr-6">
61+
<LeftSection options={options} setOptions={setOptions} />
62+
</div>
63+
64+
<RightSection tab={searchParams.tab} options={options} />
65+
</div>
66+
);
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { APIHeader } from "@/components/blocks/APIHeader";
2+
import { CodeExample } from "@/components/code/code-example";
3+
import { StyledPayEmbedPreview } from "@/components/pay/embed";
4+
import ThirdwebProvider from "@/components/thirdweb-provider";
5+
import { metadataBase } from "@/lib/constants";
6+
import type { Metadata } from "next";
7+
8+
export const metadata: Metadata = {
9+
metadataBase,
10+
title: "Fund wallets | thirdweb Universal Bridge",
11+
description:
12+
"The easiest way for users to fund their wallets. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.",
13+
};
14+
15+
export default function Page() {
16+
return (
17+
<ThirdwebProvider>
18+
<main className="container px-0 pb-20">
19+
<APIHeader
20+
title="The easiest way for users to fund their wallets"
21+
description={
22+
<>
23+
Onramp users with credit card &amp; cross-chain crypto payments —
24+
and generate revenue for each user transaction.
25+
</>
26+
}
27+
docsLink="https://portal.thirdweb.com/connect/pay/get-started"
28+
heroLink="/pay.png"
29+
/>
30+
31+
<section className="space-y-8">
32+
<StyledPayEmbed />
33+
</section>
34+
</main>
35+
</ThirdwebProvider>
36+
);
37+
}
38+
39+
function StyledPayEmbed() {
40+
return (
41+
<>
42+
<div className="space-y-2">
43+
<h2 className="font-semibold text-2xl tracking-tight sm:text-3xl">
44+
Fund Wallet
45+
</h2>
46+
<p className="max-w-[600px]">
47+
Inline component that allows users to buy any currency.
48+
<br />
49+
Customize theme, currency, amounts, payment methods and more.
50+
</p>
51+
</div>
52+
53+
<CodeExample
54+
preview={<StyledPayEmbedPreview />}
55+
code={`
56+
import { PayEmbed } from "thirdweb/react";
57+
58+
function App() {
59+
return (
60+
<PayEmbed
61+
client={client}
62+
payOptions={{
63+
mode: "fund_wallet",
64+
metadata: {
65+
name: "Get funds",
66+
},
67+
prefillBuy: {
68+
chain: base,
69+
amount: "0.01",
70+
},
71+
// ... theme, currency, amounts, payment methods, etc.
72+
}}
73+
/>
74+
);
75+
};`}
76+
lang="tsx"
77+
/>
78+
</>
79+
);
80+
}
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
import { APIHeader } from "@/components/blocks/APIHeader";
2-
import { CodeExample } from "@/components/code/code-example";
3-
import { StyledPayEmbedPreview } from "@/components/pay/embed";
41
import ThirdwebProvider from "@/components/thirdweb-provider";
52
import { metadataBase } from "@/lib/constants";
63
import type { Metadata } from "next";
4+
import { APIHeader } from "../../../components/blocks/APIHeader";
5+
import PayEmbedPlayground from "./embed/page";
76

87
export const metadata: Metadata = {
98
metadataBase,
10-
title: "Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Pay",
9+
title:
10+
"Integrate Fiat & Cross-Chain Crypto Payments | thirdweb Universal Bridge",
1111
description:
12-
"The easiest way for users to transact in your app. Onramp users in clicks and generate revenue for each user transaction. Integrate for free.",
12+
"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.",
1313
};
1414

15-
export default function Page() {
15+
export default function Page(props: {
16+
searchParams: Promise<{ tab: string }>;
17+
}) {
1618
return (
1719
<ThirdwebProvider>
18-
<main className="container px-0 pb-20">
20+
<div className="">
1921
<APIHeader
20-
title="The easiest way for users to fund their wallets"
22+
title="Universal Bridge UI component"
2123
description={
2224
<>
2325
Onramp users with credit card &amp; cross-chain crypto payments —
@@ -28,53 +30,8 @@ export default function Page() {
2830
heroLink="/pay.png"
2931
/>
3032

31-
<section className="space-y-8">
32-
<StyledPayEmbed />
33-
</section>
34-
</main>
35-
</ThirdwebProvider>
36-
);
37-
}
38-
39-
function StyledPayEmbed() {
40-
return (
41-
<>
42-
<div className="space-y-2">
43-
<h2 className="font-semibold text-2xl tracking-tight sm:text-3xl">
44-
Fund Wallet
45-
</h2>
46-
<p className="max-w-[600px]">
47-
Inline component that allows users to buy any currency.
48-
<br />
49-
Customize theme, currency, amounts, payment methods and more.
50-
</p>
33+
<PayEmbedPlayground searchParams={props.searchParams} />
5134
</div>
52-
53-
<CodeExample
54-
preview={<StyledPayEmbedPreview />}
55-
code={`
56-
import { PayEmbed } from "thirdweb/react";
57-
58-
function App() {
59-
return (
60-
<PayEmbed
61-
client={client}
62-
payOptions={{
63-
mode: "fund_wallet",
64-
metadata: {
65-
name: "Get funds",
66-
},
67-
prefillBuy: {
68-
chain: base,
69-
amount: "0.01",
70-
},
71-
// ... theme, currency, amounts, payment methods, etc.
72-
}}
73-
/>
74-
);
75-
};`}
76-
lang="tsx"
77-
/>
78-
</>
35+
</ThirdwebProvider>
7936
);
8037
}

‎apps/playground-web/src/app/connect/sign-in/button/LeftSection.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,13 @@ export function LeftSection(props: {
201201

202202
{/* Colors */}
203203
<ColorFormGroup
204-
connectOptions={connectOptions}
205-
setConnectOptions={setConnectOptions}
204+
theme={connectOptions.theme}
205+
onChange={(newTheme) => {
206+
setConnectOptions((v) => ({
207+
...v,
208+
theme: newTheme,
209+
}));
210+
}}
206211
/>
207212
</CollapsibleSection>
208213

‎apps/playground-web/src/app/connect/sign-in/components/ColorFormGroup.tsx

+18-24
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,19 @@ import { ColorInput } from "./ColorInput";
77
import type { ConnectPlaygroundOptions } from "./types";
88

99
export function ColorFormGroup(props: {
10-
connectOptions: ConnectPlaygroundOptions;
11-
setConnectOptions: React.Dispatch<
12-
React.SetStateAction<ConnectPlaygroundOptions>
13-
>;
10+
theme: ConnectPlaygroundOptions["theme"];
11+
onChange: (value: ConnectPlaygroundOptions["theme"]) => void;
1412
}) {
1513
const [search, setSearch] = useState("");
16-
const { connectOptions, setConnectOptions } = props;
14+
const { theme, onChange } = props;
1715

1816
const themeObj =
19-
connectOptions.theme.type === "dark"
17+
theme.type === "dark"
2018
? darkTheme({
21-
colors: connectOptions.theme.darkColorOverrides,
19+
colors: theme.darkColorOverrides,
2220
})
2321
: lightTheme({
24-
colors: connectOptions.theme.lightColorOverrides,
22+
colors: theme.lightColorOverrides,
2523
});
2624

2725
const colorSectionsToShow = colorSections
@@ -72,23 +70,19 @@ export function ColorFormGroup(props: {
7270
className="size-10"
7371
value={themeObj.colors[color.colorId]}
7472
onChange={(value) => {
75-
setConnectOptions((v) => {
76-
const overridesKey =
77-
v.theme.type === "dark"
78-
? "darkColorOverrides"
79-
: "lightColorOverrides";
73+
const overridesKey =
74+
theme.type === "dark"
75+
? "darkColorOverrides"
76+
: "lightColorOverrides";
8077

81-
return {
82-
...v,
83-
theme: {
84-
...v.theme,
85-
[overridesKey]: {
86-
...v.theme[overridesKey],
87-
[color.colorId]: value,
88-
},
89-
},
90-
};
91-
});
78+
const newTheme = {
79+
...theme,
80+
[overridesKey]: {
81+
...theme[overridesKey],
82+
[color.colorId]: value,
83+
},
84+
};
85+
onChange(newTheme);
9286
}}
9387
/>
9488
<div>

‎apps/playground-web/src/app/navLinks.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -25,49 +25,53 @@ export const staticSidebarLinks: SidebarLink[] = [
2525
],
2626
},
2727
{
28-
name: "Account Abstraction",
28+
name: "In-App Wallet",
2929
expanded: false,
3030
links: [
3131
{
32-
name: "Connect",
33-
href: "/connect/account-abstraction/connect",
32+
name: "Any Auth",
33+
href: "/connect/in-app-wallet",
3434
},
3535
{
36-
name: "Sponsor Gas",
37-
href: "/connect/account-abstraction/sponsor",
36+
name: "Ecosystems",
37+
href: "/connect/in-app-wallet/ecosystem",
3838
},
3939
{
40-
name: "Native AA (zkSync)",
41-
href: "/connect/account-abstraction/native-aa",
40+
name: "Sponsor Gas",
41+
href: "/connect/in-app-wallet/sponsor",
4242
},
4343
],
4444
},
4545
{
46-
name: "In-App Wallet",
46+
name: "Account Abstraction",
4747
expanded: false,
4848
links: [
4949
{
50-
name: "Any Auth",
51-
href: "/connect/in-app-wallet",
50+
name: "Connect",
51+
href: "/connect/account-abstraction/connect",
5252
},
5353
{
54-
name: "Ecosystems",
55-
href: "/connect/in-app-wallet/ecosystem",
54+
name: "Sponsor Gas",
55+
href: "/connect/account-abstraction/sponsor",
5656
},
5757
{
58-
name: "Sponsor Gas",
59-
href: "/connect/in-app-wallet/sponsor",
58+
name: "Native AA (zkSync)",
59+
href: "/connect/account-abstraction/native-aa",
6060
},
6161
],
6262
},
6363
{
64-
name: "Pay",
64+
name: "Universal Bridge",
6565
expanded: false,
6666
links: [
6767
{
68-
name: "Fund Wallet",
68+
name: "UI Component",
6969
href: "/connect/pay",
7070
},
71+
{
72+
name: "Fund Wallet",
73+
href: "/connect/pay/fund-wallet",
74+
},
7175
{
7276
name: "Commerce",
7377
href: "/connect/pay/commerce",

‎packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx

+38-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import { formatNumber } from "../../../../../../utils/formatNumber.js";
66
import { toTokens } from "../../../../../../utils/units.js";
77
import type { Account } from "../../../../../../wallets/interfaces/wallet.js";
88
import { useCustomTheme } from "../../../../../core/design-system/CustomThemeProvider.js";
9-
import { fontSize, spacing } from "../../../../../core/design-system/index.js";
9+
import {
10+
fontSize,
11+
iconSize,
12+
spacing,
13+
} from "../../../../../core/design-system/index.js";
1014
import type { PayUIOptions } from "../../../../../core/hooks/connection/ConnectButtonProps.js";
1115
import { useChainMetadata } from "../../../../../core/hooks/others/useChainQuery.js";
1216
import { useWalletBalance } from "../../../../../core/hooks/others/useWalletBalance.js";
@@ -26,6 +30,7 @@ import { Button } from "../../../components/buttons.js";
2630
import { Text } from "../../../components/text.js";
2731
import { TokenSymbol } from "../../../components/token/TokenSymbol.js";
2832
import { ConnectButton } from "../../ConnectButton.js";
33+
import { OutlineWalletIcon } from "../../icons/OutlineWalletIcon.js";
2934
import { formatTokenBalance } from "../formatTokenBalance.js";
3035
import {
3136
type ERC20OrNativeToken,
@@ -95,6 +100,38 @@ export function TransactionModeScreen(props: {
95100
return <LoadingScreen />;
96101
}
97102

103+
if (!activeAccount) {
104+
return (
105+
<Container
106+
style={{
107+
minHeight: "350px",
108+
}}
109+
fullHeight
110+
flex="row"
111+
center="both"
112+
>
113+
<Container animate="fadein">
114+
<Spacer y="xxl" />
115+
<Container flex="row" center="x">
116+
<OutlineWalletIcon size={iconSize["3xl"]} />
117+
</Container>
118+
<Spacer y="lg" />
119+
<Text center color="primaryText" size="md">
120+
Please connect a wallet to continue
121+
</Text>
122+
<Spacer y="xl" />
123+
<Container flex="row" center="x" style={{ width: "100%" }}>
124+
<ConnectButton
125+
client={client}
126+
theme={theme}
127+
{...props.connectOptions}
128+
/>
129+
</Container>
130+
</Container>
131+
</Container>
132+
);
133+
}
134+
98135
if (transactionCostAndDataError || chainDataError) {
99136
return (
100137
<Container

‎packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { useEffect, useState } from "react";
22
import { polygon } from "../../../../../../../chains/chain-definitions/polygon.js";
33
import type { Chain } from "../../../../../../../chains/types.js";
44
import type {
@@ -41,6 +41,18 @@ export function useToTokenSelectionStates(options: {
4141
const [tokenAmount, setTokenAmount] = useState<string>(initialTokenAmount);
4242
const deferredTokenAmount = useDebouncedValue(tokenAmount, 300);
4343

44+
useEffect(() => {
45+
if (prefillBuy?.amount) {
46+
setTokenAmount(prefillBuy.amount);
47+
}
48+
if (prefillBuy?.chain) {
49+
setToChain(prefillBuy.chain);
50+
}
51+
if (prefillBuy?.token) {
52+
setToToken(prefillBuy.token);
53+
}
54+
}, [prefillBuy?.amount, prefillBuy?.chain, prefillBuy?.token]);
55+
4456
// Destination chain and token selection -----------------------------------
4557
const [toChain, setToChain] = useState<Chain>(
4658
// use prefill chain if available

0 commit comments

Comments
 (0)
Please sign in to comment.