Skip to content

Commit aad3e64

Browse files
committed
[MNY-226] Add Bridge Widget playgrounds (script, iframe, react) (#8571)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the `BridgeWidget` component within the playground by introducing new features, improving styling, and refining existing functionalities. It also includes updates to metadata and the handling of token selections and amounts. ### Detailed summary - Removed `PayIcon` and replaced with `BringToFrontIcon`. - Added `showThirdwebBranding` option to `UniversalBridgeEmbed`. - Updated `BridgeWidget` to support token amounts and selections. - Enhanced sorting of bridge chains in `chains.ts`. - Improved styling for various components, including `FeatureCard` and `ChatPageContent`. - Updated documentation for widget parameters. - Refactored code for better readability and maintainability in multiple components. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Bridge Widget playground: interactive preview, code generation (script/component/iframe), theme, token/amount prefills, and preview modes. * Unified buy/swap experience under a single Bridge widget and a new branding toggle (hide/show branding). * Expanded connection options for wallet/connect flows. * **Documentation** * Added docs for token amounts, branding, and iframe examples. * **Style** * Minor typography and spacing refinements. * **Chores** * Navigation and feature card updates; small test adjustment (one image test skipped). <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 4bab6f1 commit aad3e64

File tree

25 files changed

+1334
-281
lines changed

25 files changed

+1334
-281
lines changed

apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx

Lines changed: 148 additions & 205 deletions
Large diffs are not rendered by default.

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ function TokenInfoSection(props: {
283283
<dt className="text-xs font-medium text-muted-foreground tracking-wider uppercase">
284284
{props.label}
285285
</dt>
286-
<dd className="text-3xl font-bold text-foreground tracking-tight">
286+
<dd className="text-3xl font-semibold text-foreground tracking-tight">
287287
{props.value}
288288
</dd>
289289
</dl>

apps/dashboard/src/app/bridge/(general)/components/client/UniversalBridgeEmbed.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function UniversalBridgeEmbed(props: {
2525
swapTab: BuyAndSwapEmbedProps["swapTab"];
2626
pageType: "bridge" | "bridge-iframe";
2727
currency?: SupportedFiatCurrency;
28+
showThirdwebBranding?: boolean;
2829
}) {
2930
return (
3031
<BuyAndSwapEmbed
@@ -34,6 +35,7 @@ export function UniversalBridgeEmbed(props: {
3435
swapTab={props.swapTab}
3536
pageType={props.pageType}
3637
wallets={bridgeWallets}
38+
showThirdwebBranding={props.showThirdwebBranding}
3739
/>
3840
);
3941
}

apps/dashboard/src/app/bridge/widget/page.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@ export default async function Page(props: {
3535
// output is buy, input is sell
3636
const sellChain = parse(searchParams.inputChain, onlyNumber);
3737
const sellCurrency = parse(searchParams.inputCurrency, onlyAddress);
38+
const sellAmount = parse(searchParams.inputCurrencyAmount, onlyNumber);
3839

3940
const buyChain = parse(searchParams.outputChain, onlyNumber);
4041
const buyCurrency = parse(searchParams.outputCurrency, onlyAddress);
42+
const buyAmount = parse(searchParams.outputCurrencyAmount, onlyNumber);
43+
44+
const showThirdwebBranding = parse(
45+
searchParams.showThirdwebBranding,
46+
(v) => v !== "false",
47+
);
4148

4249
const persistTokenSelections =
4350
parse(searchParams.persistTokenSelections, (v) =>
@@ -61,11 +68,14 @@ export default async function Page(props: {
6168
persistTokenSelections={persistTokenSelections === "true"}
6269
pageType="bridge-iframe"
6370
currency={currency}
71+
showThirdwebBranding={showThirdwebBranding}
6472
buyTab={{
6573
buyToken: buyChain
6674
? {
6775
chainId: buyChain,
6876
tokenAddress: buyCurrency || NATIVE_TOKEN_ADDRESS,
77+
amount:
78+
buyAmount === undefined ? undefined : buyAmount.toString(),
6979
}
7080
: undefined,
7181
}}
@@ -74,12 +84,18 @@ export default async function Page(props: {
7484
? {
7585
chainId: sellChain,
7686
tokenAddress: sellCurrency || NATIVE_TOKEN_ADDRESS,
87+
amount:
88+
sellAmount === undefined
89+
? undefined
90+
: sellAmount.toString(),
7791
}
7892
: undefined,
7993
buyToken: buyChain
8094
? {
8195
chainId: buyChain,
8296
tokenAddress: buyCurrency || NATIVE_TOKEN_ADDRESS,
97+
amount:
98+
buyAmount === undefined ? undefined : buyAmount.toString(),
8399
}
84100
: undefined,
85101
}}

apps/playground-web/src/app/ai/components/ChatPageContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function ChatPageContent(props: {
8484
const userChainIdArray = userChainIds
8585
.split(",")
8686
.map((id) => id.trim())
87-
.filter((id) => id !== "" && !isNaN(Number(id)));
87+
.filter((id) => id !== "" && !Number.isNaN(Number(id)));
8888

8989
return {
9090
chainIds:
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"use client";
2+
3+
import { useTheme } from "next-themes";
4+
import { useEffect, useState } from "react";
5+
import { TabButtons } from "../../../../components/ui/tab-buttons";
6+
import { LeftSection } from "./left-section";
7+
import { RightSection } from "./right-section";
8+
import type { BridgeWidgetPlaygroundOptions } from "./types";
9+
10+
const defaultOptions: BridgeWidgetPlaygroundOptions = {
11+
integrationType: "iframe",
12+
prefill: undefined,
13+
currency: "USD",
14+
showThirdwebBranding: true,
15+
theme: {
16+
darkColorOverrides: {},
17+
lightColorOverrides: {},
18+
type: "dark",
19+
},
20+
};
21+
22+
export function BridgeWidgetPlayground() {
23+
const { theme } = useTheme();
24+
25+
const [options, setOptions] =
26+
useState<BridgeWidgetPlaygroundOptions>(defaultOptions);
27+
28+
// change theme on global theme change
29+
useEffect(() => {
30+
setOptions((prev) => ({
31+
...prev,
32+
theme: {
33+
...prev.theme,
34+
type: theme === "dark" ? "dark" : "light",
35+
},
36+
}));
37+
}, [theme]);
38+
39+
return (
40+
<div>
41+
<TabButtons
42+
tabs={[
43+
{
44+
name: "Iframe",
45+
onClick: () =>
46+
setOptions({ ...options, integrationType: "iframe" }),
47+
isActive: options.integrationType === "iframe",
48+
},
49+
{
50+
name: "Script",
51+
onClick: () =>
52+
setOptions({ ...options, integrationType: "script" }),
53+
isActive: options.integrationType === "script",
54+
},
55+
{
56+
name: "React",
57+
onClick: () =>
58+
setOptions({ ...options, integrationType: "component" }),
59+
isActive: options.integrationType === "component",
60+
},
61+
]}
62+
/>
63+
64+
<div className="h-6" />
65+
66+
<div className="relative flex flex-col-reverse gap-6 xl:min-h-[900px] xl:flex-row xl:gap-6">
67+
<div className="grow border-b pb-10 xl:mb-0 xl:border-r xl:border-b-0 xl:pr-6">
68+
<LeftSection options={options} setOptions={setOptions} />
69+
</div>
70+
<RightSection options={options} />
71+
</div>
72+
</div>
73+
);
74+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { BridgeWidgetPlaygroundOptions } from "./types";
2+
3+
const BRIDGE_WIDGET_IFRAME_BASE_URL = "https://thirdweb.com/bridge/widget";
4+
5+
export function buildIframeUrl(
6+
options: BridgeWidgetPlaygroundOptions,
7+
type: "code" | "preview",
8+
) {
9+
const url = new URL(BRIDGE_WIDGET_IFRAME_BASE_URL);
10+
11+
if (type === "preview") {
12+
// always set it to false so playground doesn't show last used tokens
13+
url.searchParams.set("persistTokenSelections", "false");
14+
}
15+
16+
// Theme (only add if light, dark is default)
17+
if (options.theme.type === "light") {
18+
url.searchParams.set("theme", "light");
19+
}
20+
21+
// Currency (only add if not USD, USD is default)
22+
if (options.currency && options.currency !== "USD") {
23+
url.searchParams.set("currency", options.currency);
24+
}
25+
26+
// Branding
27+
if (options.showThirdwebBranding === false) {
28+
url.searchParams.set("showThirdwebBranding", "false");
29+
}
30+
31+
// Sell token (input)
32+
if (options.prefill?.sellToken) {
33+
url.searchParams.set(
34+
"inputChain",
35+
String(options.prefill.sellToken.chainId),
36+
);
37+
if (options.prefill.sellToken.tokenAddress) {
38+
url.searchParams.set(
39+
"inputCurrency",
40+
options.prefill.sellToken.tokenAddress,
41+
);
42+
}
43+
if (options.prefill.sellToken.amount) {
44+
url.searchParams.set(
45+
"inputCurrencyAmount",
46+
options.prefill.sellToken.amount,
47+
);
48+
}
49+
}
50+
51+
// Buy token (output)
52+
if (options.prefill?.buyToken) {
53+
url.searchParams.set(
54+
"outputChain",
55+
String(options.prefill.buyToken.chainId),
56+
);
57+
if (options.prefill.buyToken.tokenAddress) {
58+
url.searchParams.set(
59+
"outputCurrency",
60+
options.prefill.buyToken.tokenAddress,
61+
);
62+
}
63+
if (options.prefill.buyToken.amount) {
64+
url.searchParams.set(
65+
"outputCurrencyAmount",
66+
options.prefill.buyToken.amount,
67+
);
68+
}
69+
}
70+
71+
return url.toString();
72+
}

0 commit comments

Comments
 (0)