Skip to content

Commit 5281ce2

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

File tree

12 files changed

+1302
-99
lines changed

12 files changed

+1302
-99
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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 stringifyIgnoreFalsy(
179+
value: Record<string, string | undefined | boolean>,
180+
) {
181+
const _value: Record<string, string | boolean> = {};
182+
183+
for (const key in value) {
184+
if (value[key] !== undefined && value[key] !== "") {
185+
_value[key] = value[key];
186+
}
187+
}
188+
189+
return JSON.stringify(_value);
190+
}
191+
192+
function stringifyProps(props: Record<string, string | undefined | boolean>) {
193+
const _props: Record<string, string | undefined | boolean> = {};
194+
195+
for (const key in props) {
196+
if (props[key] !== undefined && props[key] !== "") {
197+
_props[key] = props[key];
198+
}
199+
}
200+
201+
return Object.entries(_props)
202+
.map(([key, value]) => `${key}={${value}}`)
203+
.join("\n");
204+
}
205+
206+
function quotes(value: string) {
207+
return `"${value}"`;
208+
}
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)