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
2 changes: 1 addition & 1 deletion apps/dashboard/src/@/analytics/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@ export function reportProductFeedback(properties: {
*
*/
export function reportBridgePageLinkClick(params: {
linkType: "bridge-docs" | "trending-tokens";
linkType: "bridge-docs" | "trending-tokens" | "integrate-bridge-widget";
}) {
posthog.capture("bridge page link clicked", params);
}
Expand Down
11 changes: 10 additions & 1 deletion apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
import { useTheme } from "next-themes";
import { useEffect, useMemo, useRef, useState } from "react";
import { defineChain } from "thirdweb";
import { BuyWidget, SwapWidget } from "thirdweb/react";
import {
BuyWidget,
type SupportedFiatCurrency,
SwapWidget,
} from "thirdweb/react";
import type { Wallet } from "thirdweb/wallets";
import {
reportAssetBuyCancelled,
Expand Down Expand Up @@ -34,6 +38,7 @@ import { getConfiguredThirdwebClient } from "../../constants/thirdweb.server";
type PageType = "asset" | "bridge" | "chain" | "bridge-iframe";

export type BuyAndSwapEmbedProps = {
persistTokenSelections?: boolean;
buyTab:
| {
buyToken:
Expand Down Expand Up @@ -65,6 +70,7 @@ export type BuyAndSwapEmbedProps = {
| undefined;
pageType: PageType;
wallets?: Wallet[];
currency?: SupportedFiatCurrency;
};

export function BuyAndSwapEmbed(props: BuyAndSwapEmbedProps) {
Expand Down Expand Up @@ -118,6 +124,7 @@ export function BuyAndSwapEmbed(props: BuyAndSwapEmbedProps) {

{tab === "buy" && (
<BuyWidget
currency={props.currency || "USD"}
amount={props.buyTab?.buyToken?.amount || "1"}
chain={
props.buyTab?.buyToken?.chainId
Expand Down Expand Up @@ -241,6 +248,8 @@ export function BuyAndSwapEmbed(props: BuyAndSwapEmbedProps) {

{tab === "swap" && (
<SwapWidget
currency={props.currency || "USD"}
persistTokenSelections={props.persistTokenSelections}
client={client}
theme={themeObj}
className="!rounded-2xl !border-none"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import RoutesImage from "../assets/routes.png";
import TokensImage from "../assets/tokens.png";
import { bridgeStats, bridgeStatsNumbers } from "../data";
import { AnimatedNumbers } from "./client/animated-numbers";
import { AddBridgeWidgetLink } from "./client/badge-link";
import { UniversalBridgeEmbed } from "./client/UniversalBridgeEmbed";
import { BridgePageHeader } from "./header";

Expand All @@ -29,6 +30,12 @@ export function BridgePageUI(props: {
/>
</div>

<div className="container max-w-2xl flex justify-center">
<AddBridgeWidgetLink />
</div>

<div className="h-10" />

<HeadingSection title={props.title} />

<div className="h-20 lg:h-40" />
Expand All @@ -43,7 +50,7 @@ export function BridgePageUI(props: {
function HeadingSection(props: { title: React.ReactNode }) {
return (
<div className="container">
<div className="mb-3 lg:mb-6">{props.title}</div>
<div className="mb-3 lg:mb-5">{props.title}</div>

<p className="text-muted-foreground text-sm text-pretty text-center lg:text-lg mb-6 lg:mb-8">
Seamlessly move your assets across {bridgeStats.supportedChains} chains
Expand Down Expand Up @@ -81,9 +88,9 @@ function DataSquare(props: {
imageClassName?: string;
}) {
return (
<div className="py-2 lg:py-0 size-full lg:size-[220px] rounded-xl border hover:bg-card bg-card/50 relative shrink-0 overflow-hidden">
<div className="py-2 lg:py-0 size-full lg:size-[200px] rounded-2xl border hover:bg-card bg-card/50 relative shrink-0 overflow-hidden">
<div className="p-4">
<div className="flex items-center gap-1 text-3xl lg:text-5xl font-medium tracking-tight font-mono mb-1 h-[45px] lg:h-[56px]">
<div className="flex items-center gap-1 text-3xl lg:text-4xl font-semibold tracking-tight mb-1 h-[45px]">
<AnimatedNumbers
value={props.data}
format={props.format}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import type { SupportedFiatCurrency } from "thirdweb/react";
import { createWallet } from "thirdweb/wallets";
import {
BuyAndSwapEmbed,
Expand All @@ -19,12 +20,16 @@ export const bridgeWallets = [
];

export function UniversalBridgeEmbed(props: {
persistTokenSelections?: boolean;
buyTab: BuyAndSwapEmbedProps["buyTab"];
swapTab: BuyAndSwapEmbedProps["swapTab"];
pageType: "bridge" | "bridge-iframe";
currency?: SupportedFiatCurrency;
}) {
return (
<BuyAndSwapEmbed
persistTokenSelections={props.persistTokenSelections}
currency={props.currency}
buyTab={props.buyTab}
swapTab={props.swapTab}
pageType={props.pageType}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client";
import { ArrowUpRightIcon } from "lucide-react";
import Link from "next/link";
import { reportBridgePageLinkClick } from "@/analytics/report";

export function AddBridgeWidgetLink() {
return (
<BadgeLink
href="https://portal.thirdweb.com/bridge/bridge-widget"
label="Add Bridge widget in your app"
onClick={() => {
reportBridgePageLinkClick({ linkType: "integrate-bridge-widget" });
}}
/>
);
}

function BadgeLink(props: {
href: string;
label: string;
onClick?: () => void;
}) {
return (
<Link
href={props.href}
className="text-sm text-foreground bg-accent/50 rounded-full px-4 py-2 border hover:bg-accent flex items-center gap-2"
target="_blank"
onClick={props.onClick}
>
{props.label}
<ArrowUpRightIcon className="size-4 text-foreground" />
</Link>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { bridgeWallets } from "./client/UniversalBridgeEmbed";

export function BridgePageHeader(props: { containerClassName?: string }) {
return (
<div className="border-b border-border/70">
<div>
<header
className={cn(
"container flex max-w-7xl justify-between py-3 lg:py-4",
"container flex max-w-5xl justify-between py-3 lg:py-5",
props.containerClassName,
)}
>
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/app/bridge/(general)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default async function Page(props: {
: undefined,
}}
title={
<h1 className="text-3xl md:text-6xl font-semibold tracking-tighter text-balance text-center">
<h1 className="text-3xl md:text-5xl font-semibold tracking-tighter text-balance text-center">
Bridge and Swap tokens <br className="max-sm:hidden" /> across any
chain, instantly
</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isAddress, NATIVE_TOKEN_ADDRESS } from "thirdweb";
import { UniversalBridgeEmbed } from "../(general)/components/client/UniversalBridgeEmbed";
import { bridgeStats } from "../(general)/data";
import "@workspace/ui/global.css";
import type { SupportedFiatCurrency } from "thirdweb/react";
import { NEXT_PUBLIC_BRIDGE_IFRAME_CLIENT_ID } from "@/constants/public-envs";
import { BridgeProviders } from "../(general)/components/client/Providers.client";

Expand Down Expand Up @@ -38,15 +39,28 @@ export default async function Page(props: {
const buyChain = parse(searchParams.outputChain, onlyNumber);
const buyCurrency = parse(searchParams.outputCurrency, onlyAddress);

const persistTokenSelections =
parse(searchParams.persistTokenSelections, (v) =>
v === "false" ? "false" : "true",
) || "true";

const theme =
parse(searchParams.theme, (v) => (v === "light" ? "light" : "dark")) ||
"dark";

const currency = parse(searchParams.currency, (v) =>
VALID_CURRENCIES.includes(v as SupportedFiatCurrency)
? (v as SupportedFiatCurrency)
: undefined,
);

return (
<Providers theme={theme}>
<div className="flex items-center justify-center min-h-screen py-6 bg-background">
<UniversalBridgeEmbed
persistTokenSelections={persistTokenSelections === "true"}
pageType="bridge-iframe"
currency={currency}
buyTab={{
buyToken: buyChain
? {
Expand Down Expand Up @@ -75,6 +89,32 @@ export default async function Page(props: {
);
}

const VALID_CURRENCIES: SupportedFiatCurrency[] = [
"USD",
"EUR",
"GBP",
"JPY",
"KRW",
"CNY",
"INR",
"NOK",
"SEK",
"CHF",
"AUD",
"CAD",
"NZD",
"MXN",
"BRL",
"CLP",
"CZK",
"DKK",
"HKD",
"HUF",
"IDR",
"ILS",
"ISK",
];
Comment on lines +92 to +116
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

thirdweb/react SupportedFiatCurrency type latest version

💡 Result:

As of the latest thirdweb React/TypeScript SDK docs, SupportedFiatCurrency currently only supports "USD". [1][2]

Sources:

  • convertFiatToCrypto / convertCryptoToFiat docs (showing parameter type SupportedFiatCurrency and noting only USD supported). [1]
  • BuyWidget / widget docs referencing currency: SupportedFiatCurrency. [2]

References:
[1] thirdweb convertFiatToCrypto / convertCryptoToFiat docs.
[2] thirdweb BuyWidget / widget docs.


Remove or dramatically reduce VALID_CURRENCIES — the hardcoded list does not match actual library support.

The thirdweb/react library currently only supports "USD" as a fiat currency, but this list includes 23 currencies (EUR, GBP, JPY, KRW, CNY, etc.). Using this array for validation will incorrectly allow unsupported currencies, leading to runtime failures. Either maintain only the currencies actually supported by the underlying library, or fetch the supported list from the library at runtime if it provides an export for that.


function parse<T>(
value: string | string[] | undefined,
fn: (value: string) => T | undefined,
Expand Down
5 changes: 5 additions & 0 deletions apps/portal/redirects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,10 @@ const walletRefactorRedirects = {
"/typescript/v5/supported-wallets/:path*": "/wallets/external-wallets",
};

const bridgeRedirects = {
"/bridge/bridge-widget-script": "/bridge/bridge-widget/script",
};

/**
* @type {import('next').NextConfig['redirects']}
*/
Expand All @@ -1115,6 +1119,7 @@ export const redirects = async () => {
...createRedirects(glossaryRedirects),
...createRedirects(payRedirects),
...createRedirects(walletRefactorRedirects),
...createRedirects(bridgeRedirects),
];
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CodeBlock, Tabs, TabsContent, TabsList, TabsTrigger } from "@doc";

export function IframeCodePreview(props: { src: string }) {
return (
<Tabs defaultValue="code">
<TabsList>
<TabsTrigger value="code">Code</TabsTrigger>
<TabsTrigger value="preview">Preview</TabsTrigger>
</TabsList>
<TabsContent value="code">
<CodeBlock
code={`\
<iframe
src="${props.src}"
height="750px"
width="100%"
style="border: 0;"
/>`}
lang="html"
/>
</TabsContent>
<TabsContent value="preview">
<iframe
title="Bridge widget iframe"
src={props.src}
height="750px"
width="100%"
style={{ border: 0 }}
/>
</TabsContent>
</Tabs>
);
}
Loading
Loading