Skip to content

Commit 4872581

Browse files
[Playground] feat: Add PayEmbed component and playground
1 parent 2d5ff74 commit 4872581

File tree

8 files changed

+1115
-27
lines changed

8 files changed

+1115
-27
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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+
metadata: {
95+
name: ${options.payOptions.title ? quotes(options.payOptions.title) : "undefined"},
96+
image: ${options.payOptions.image ? quotes(options.payOptions.image) : "undefined"},
97+
},`;
98+
99+
// Add mode-specific options
100+
if (options.payOptions.mode) {
101+
payOptionsCode += `
102+
mode: "${options.payOptions.mode}",`;
103+
104+
// Add buyWithCrypto and buyWithFiat if they're set to false
105+
if (options.payOptions.buyWithCrypto === false) {
106+
payOptionsCode += `
107+
buyWithCrypto: false,`;
108+
}
109+
110+
if (options.payOptions.buyWithFiat === false) {
111+
payOptionsCode += `
112+
buyWithFiat: false,`;
113+
}
114+
115+
if (options.payOptions.mode === "fund_wallet" || !options.payOptions.mode) {
116+
payOptionsCode += `
117+
prefillBuy: {
118+
chain: ${chainCode},
119+
amount: ${options.payOptions.buyTokenAmount ? quotes(options.payOptions.buyTokenAmount) : '"0.01"'},
120+
${options.payOptions.buyTokenInfo ? `token: ${JSON.stringify(options.payOptions.buyTokenInfo)},` : ""}
121+
},`;
122+
} else if (options.payOptions.mode === "direct_payment") {
123+
payOptionsCode += `
124+
paymentInfo: {
125+
chain: ${chainCode},
126+
sellerAddress: ${options.payOptions.sellerAddress ? quotes(options.payOptions.sellerAddress) : '"0x0000000000000000000000000000000000000000"'},
127+
amount: ${options.payOptions.buyTokenAmount ? quotes(options.payOptions.buyTokenAmount) : '"0.01"'},
128+
${options.payOptions.buyTokenInfo ? `token: ${JSON.stringify(options.payOptions.buyTokenInfo)},` : ""}
129+
},`;
130+
} else if (options.payOptions.mode === "transaction") {
131+
payOptionsCode += `
132+
transaction: claimTo({
133+
contract: myNftContract,
134+
quantity: 1n,
135+
tokenId: 0n,
136+
to: "0x...",
137+
}),`;
138+
}
139+
}
140+
141+
payOptionsCode += `
142+
}`;
143+
144+
const accountAbstractionCode = options.connectOptions.enableAccountAbstraction
145+
? `\n accountAbstraction: {
146+
chain: sepolia,
147+
sponsorGas: true,
148+
},`
149+
: "";
150+
151+
const connectOptionsCode = `{
152+
wallets, ${accountAbstractionCode ? `\n${accountAbstractionCode}` : ""}
153+
}`;
154+
155+
return `\
156+
import { createThirdwebClient } from "thirdweb";
157+
${imports.react.length > 0 ? `import { ${imports.react.join(", ")} } from "thirdweb/react";` : ""}
158+
${imports.thirdweb.length > 0 ? `import { ${imports.thirdweb.join(", ")} } from "thirdweb";` : ""}
159+
${imports.wallets.length > 0 ? `import { ${imports.wallets.join(", ")} } from "thirdweb/wallets";` : ""}
160+
${imports.chains.length > 0 ? `import { ${imports.chains.join(", ")} } from "thirdweb/chains";` : ""}
161+
162+
const client = createThirdwebClient({
163+
clientId: "....",
164+
});
165+
166+
const wallets = [${walletCodes.join(", ")}];
167+
168+
function Example() {
169+
return (
170+
<PayEmbed
171+
client={client}
172+
connectOptions={${connectOptionsCode}}
173+
payOptions={${payOptionsCode}}${themeProp ? `\ntheme={${themeProp}}` : ""}
174+
/>
175+
);
176+
}`;
177+
}
178+
179+
function stringifyIgnoreFalsy(
180+
value: Record<string, string | undefined | boolean>,
181+
) {
182+
const _value: Record<string, string | boolean> = {};
183+
184+
for (const key in value) {
185+
if (value[key] !== undefined && value[key] !== "") {
186+
_value[key] = value[key];
187+
}
188+
}
189+
190+
return JSON.stringify(_value);
191+
}
192+
193+
function stringifyProps(props: Record<string, string | undefined | boolean>) {
194+
const _props: Record<string, string | undefined | boolean> = {};
195+
196+
for (const key in props) {
197+
if (props[key] !== undefined && props[key] !== "") {
198+
_props[key] = props[key];
199+
}
200+
}
201+
202+
return Object.entries(_props)
203+
.map(([key, value]) => `${key}={${value}}`)
204+
.join("\n");
205+
}
206+
207+
function quotes(value: string) {
208+
return `"${value}"`;
209+
}
Lines changed: 43 additions & 0 deletions
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+
};

0 commit comments

Comments
 (0)