Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function SwapWidgetEmbed({
showThirdwebBranding,
theme,
currency,
persistTokenSelections,
}: {
buyChainId?: number;
buyTokenAddress?: Address;
Expand All @@ -26,6 +27,7 @@ export function SwapWidgetEmbed({
showThirdwebBranding?: boolean;
theme: "light" | "dark";
currency?: SupportedFiatCurrency;
persistTokenSelections?: boolean;
}) {
const client = useMemo(
() =>
Expand Down Expand Up @@ -77,6 +79,7 @@ export function SwapWidgetEmbed({
showThirdwebBranding={showThirdwebBranding}
theme={theme}
currency={currency}
persistTokenSelections={persistTokenSelections}
onSuccess={() => {
sendMessageToParent({
source: "swap-widget",
Expand Down
10 changes: 9 additions & 1 deletion apps/dashboard/src/app/bridge/swap-widget/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ export default async function Page(props: {
// Optional params
const showThirdwebBranding = parseQueryParams(
searchParams.showThirdwebBranding,
(v) => v !== "false",
// biome-ignore lint/complexity/noUselessTernary: this is easier to understand
(v) => (v === "false" ? false : true),
);

const persistTokenSelections = parseQueryParams(
searchParams.persistTokenSelections,
// biome-ignore lint/complexity/noUselessTernary: this is easier to understand
(v) => (v === "false" ? false : true),
);

const theme =
Expand All @@ -76,6 +83,7 @@ export default async function Page(props: {
showThirdwebBranding={showThirdwebBranding}
theme={theme}
currency={currency}
persistTokenSelections={persistTokenSelections}
/>
</div>
</BridgeProvidersLite>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { SwapWidgetPlaygroundOptions } from "./types";

const SWAP_WIDGET_IFRAME_BASE_URL = "https://thirdweb.com/bridge/swap-widget";

export function buildSwapIframeUrl(
options: SwapWidgetPlaygroundOptions,
type: "code" | "preview" = "preview",
) {
const url = new URL(SWAP_WIDGET_IFRAME_BASE_URL);

if (type === "preview") {
// always set it to false so playground doesn't show last used tokens
url.searchParams.set("persistTokenSelections", "false");
}

// Buy token params
if (options.prefill?.buyToken?.chainId) {
url.searchParams.set("buyChain", String(options.prefill.buyToken.chainId));

if (options.prefill.buyToken.tokenAddress) {
url.searchParams.set(
"buyTokenAddress",
options.prefill.buyToken.tokenAddress,
);
}

if (options.prefill.buyToken.amount) {
url.searchParams.set("buyAmount", options.prefill.buyToken.amount);
}
}

// Sell token params
if (options.prefill?.sellToken?.chainId) {
url.searchParams.set(
"sellChain",
String(options.prefill.sellToken.chainId),
);

if (options.prefill.sellToken.tokenAddress) {
url.searchParams.set(
"sellTokenAddress",
options.prefill.sellToken.tokenAddress,
);
}

if (options.prefill.sellToken.amount) {
url.searchParams.set("sellAmount", options.prefill.sellToken.amount);
}
}

// Theme (only add if light, dark is default)
if (options.theme.type === "light") {
url.searchParams.set("theme", "light");
}

// Currency (only add if not USD, USD is default)
if (options.currency && options.currency !== "USD") {
url.searchParams.set("currency", options.currency);
}

// Branding
if (options.showThirdwebBranding === false) {
url.searchParams.set("showThirdwebBranding", "false");
}

return url.toString();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { lazy, Suspense } from "react";
import { LoadingDots } from "@/components/ui/LoadingDots";
import { quotes, stringifyImports, stringifyProps } from "@/lib/code-gen";
import { buildSwapIframeUrl } from "./buildSwapIframeUrl";
import type { SwapWidgetPlaygroundOptions } from "./types";

const CodeClient = lazy(() =>
Expand All @@ -18,16 +19,35 @@ function CodeLoading() {
}

export function CodeGen(props: { options: SwapWidgetPlaygroundOptions }) {
const code =
props.options.integrationType === "iframe"
? getIframeCode(props.options)
: getReactCode(props.options);

const lang = props.options.integrationType === "iframe" ? "html" : "ts";

return (
<div className="flex w-full grow flex-col">
<Suspense fallback={<CodeLoading />}>
<CodeClient className="grow" code={getCode(props.options)} lang="ts" />
<CodeClient className="grow" code={code} lang={lang} />
</Suspense>
</div>
);
}

function getCode(options: SwapWidgetPlaygroundOptions) {
function getIframeCode(options: SwapWidgetPlaygroundOptions) {
// Use "code" type to exclude persistTokenSelections from the generated code
const iframeUrl = buildSwapIframeUrl(options, "code");

return `<iframe
src="${iframeUrl}"
height="700px"
width="100%"
style="border: 0;"
/>`;
}

function getReactCode(options: SwapWidgetPlaygroundOptions) {
const imports = {
thirdweb: ["createThirdwebClient"] as string[],
"thirdweb/react": ["SwapWidget"] as string[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,18 @@ export function LeftSection(props: {

<div className="h-6" />

{/* Colors */}
<ColorFormGroup
onChange={(newTheme) => {
setOptions((v) => ({
...v,
theme: newTheme,
}));
}}
theme={options.theme}
/>
{/* Colors - disabled for iframe */}
{options.integrationType !== "iframe" && (
<ColorFormGroup
onChange={(newTheme) => {
setOptions((v) => ({
...v,
theme: newTheme,
}));
}}
theme={options.theme}
/>
)}

<div className="my-4 flex items-center gap-2">
<Checkbox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { darkTheme, lightTheme, SwapWidget } from "thirdweb/react";
import { Button } from "@/components/ui/button";
import { THIRDWEB_CLIENT } from "@/lib/client";
import { cn } from "@/lib/utils";
import { buildSwapIframeUrl } from "./buildSwapIframeUrl";
import { CodeGen } from "./code";
import type { SwapWidgetPlaygroundOptions } from "./types";

Expand Down Expand Up @@ -50,43 +51,38 @@ export function RightSection(props: { options: SwapWidgetPlaygroundOptions }) {
previewTab !== "code" && "items-center",
)}
>
<BackgroundPattern />

{previewTab === "ui" && (
<SwapWidget
client={THIRDWEB_CLIENT}
theme={themeObj}
prefill={props.options.prefill}
currency={props.options.currency}
showThirdwebBranding={props.options.showThirdwebBranding}
key={JSON.stringify({
prefill: props.options.prefill,
})}
persistTokenSelections={false}
/>
)}
{previewTab === "ui" &&
(props.options.integrationType === "iframe" ? (
<iframe
src={buildSwapIframeUrl(props.options, "preview")}
height="700px"
width="100%"
title="Swap Widget"
className="fade-in-0 animate-in rounded-xl duration-500"
style={{
border: "0",
}}
/>
) : (
<SwapWidget
client={THIRDWEB_CLIENT}
theme={themeObj}
prefill={props.options.prefill}
currency={props.options.currency}
showThirdwebBranding={props.options.showThirdwebBranding}
key={JSON.stringify({
prefill: props.options.prefill,
})}
persistTokenSelections={false}
/>
))}

{previewTab === "code" && <CodeGen options={props.options} />}
</div>
</div>
);
}

function BackgroundPattern() {
const color = "hsl(var(--foreground)/15%)";
return (
<div
className="absolute inset-0 z-[-1]"
style={{
backgroundImage: `radial-gradient(${color} 1px, transparent 1px)`,
backgroundSize: "24px 24px",
maskImage:
"radial-gradient(ellipse 100% 100% at 50% 50%, black 30%, transparent 60%)",
}}
/>
);
}

function TabButtons(props: {
tabs: Array<{
name: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { TabButtons } from "@/components/ui/tab-buttons";
import { LeftSection } from "./left-section";
import { RightSection } from "./right-section";
import type { SwapWidgetPlaygroundOptions } from "./types";

const defaultOptions: SwapWidgetPlaygroundOptions = {
integrationType: "react",
prefill: undefined,
currency: "USD",
showThirdwebBranding: true,
Expand All @@ -17,11 +19,26 @@ const defaultOptions: SwapWidgetPlaygroundOptions = {
},
};

export function SwapWidgetPlayground() {
function updatePageUrl(tab: SwapWidgetPlaygroundOptions["integrationType"]) {
const url = new URL(window.location.href);
if (tab === defaultOptions.integrationType) {
url.searchParams.delete("tab");
} else {
url.searchParams.set("tab", tab || "");
}

window.history.replaceState({}, "", url.toString());
}

export function SwapWidgetPlayground(props: {
defaultTab?: "iframe" | "react";
}) {
const { theme } = useTheme();

const [options, setOptions] =
useState<SwapWidgetPlaygroundOptions>(defaultOptions);
const [options, setOptions] = useState<SwapWidgetPlaygroundOptions>(() => ({
...defaultOptions,
integrationType: props.defaultTab || defaultOptions.integrationType,
}));

// change theme on global theme change
useEffect(() => {
Expand All @@ -34,12 +51,36 @@ export function SwapWidgetPlayground() {
}));
}, [theme]);

useEffect(() => {
updatePageUrl(options.integrationType);
}, [options.integrationType]);

return (
<div className="relative flex flex-col-reverse gap-6 xl:min-h-[900px] xl:flex-row xl:gap-6">
<div className="grow border-b pb-10 xl:mb-0 xl:border-r xl:border-b-0 xl:pr-6">
<LeftSection options={options} setOptions={setOptions} />
<div>
<TabButtons
tabs={[
{
name: "React",
onClick: () => setOptions({ ...options, integrationType: "react" }),
isActive: options.integrationType === "react",
},
{
name: "Iframe",
onClick: () =>
setOptions({ ...options, integrationType: "iframe" }),
isActive: options.integrationType === "iframe",
},
]}
/>

<div className="h-6" />

<div className="relative flex flex-col-reverse gap-6 xl:min-h-[900px] xl:flex-row xl:gap-6">
<div className="grow border-b pb-10 xl:mb-0 xl:border-r xl:border-b-0 xl:pr-6">
<LeftSection options={options} setOptions={setOptions} />
</div>
<RightSection options={options} />
</div>
<RightSection options={options} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { SwapWidgetProps, ThemeOverrides } from "thirdweb/react";

export type SwapWidgetPlaygroundOptions = {
integrationType: "iframe" | "react";
theme: {
type: "dark" | "light";
darkColorOverrides: ThemeOverrides["colors"];
Expand Down
18 changes: 16 additions & 2 deletions apps/playground-web/src/app/bridge/swap-widget/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export const metadata = createMetadata({
},
});

export default function Page() {
export default function Page(props: {
searchParams: Promise<{ tab?: string }>;
}) {
return (
<ThirdwebProvider>
<PageLayout
Expand All @@ -28,8 +30,20 @@ export default function Page() {
description={description}
docsLink="https://portal.thirdweb.com/references/typescript/v5/SwapWidget?utm_source=playground"
>
<SwapWidgetPlayground />
<SwapWidgetPlaygroundAsync searchParams={props.searchParams} />
</PageLayout>
</ThirdwebProvider>
);
}

async function SwapWidgetPlaygroundAsync(props: {
searchParams: Promise<{ tab?: string }>;
}) {
const searchParams = await props.searchParams;
const defaultTab =
searchParams.tab === "iframe" || searchParams.tab === "react"
? searchParams.tab
: undefined;

return <SwapWidgetPlayground defaultTab={defaultTab} />;
}
Loading
Loading