Skip to content

Commit 2fe8436

Browse files
committed
add swap widget in public token page
1 parent dbba64d commit 2fe8436

File tree

6 files changed

+145
-55
lines changed

6 files changed

+145
-55
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"use client";
22

33
import { useTheme } from "next-themes";
4+
import { useState } from "react";
45
import type { Chain, ThirdwebClient } from "thirdweb";
5-
import { BuyWidget } from "thirdweb/react";
6+
import { BuyWidget, SwapWidget } from "thirdweb/react";
67
import {
78
reportAssetBuyFailed,
89
reportAssetBuySuccessful,
910
} from "@/analytics/report";
11+
import { Button } from "@/components/ui/button";
12+
import { cn } from "@/lib/utils";
1013
import { parseError } from "@/utils/errorParser";
1114
import { getSDKTheme } from "@/utils/sdk-component-theme";
1215

@@ -16,33 +19,89 @@ export function BuyTokenEmbed(props: {
1619
tokenAddress: string;
1720
}) {
1821
const { theme } = useTheme();
22+
const [tab, setTab] = useState<"buy" | "swap">("swap");
23+
const themeObj = getSDKTheme(theme === "light" ? "light" : "dark");
1924
return (
20-
<BuyWidget
21-
amount="1"
22-
chain={props.chain}
23-
className="!rounded-xl !w-full"
24-
client={props.client}
25-
connectOptions={{
26-
autoConnect: false,
27-
}}
28-
onError={(e) => {
29-
const errorMessage = parseError(e);
30-
reportAssetBuyFailed({
31-
assetType: "coin",
32-
chainId: props.chain.id,
33-
contractType: "DropERC20",
34-
error: errorMessage,
35-
});
36-
}}
37-
onSuccess={() => {
38-
reportAssetBuySuccessful({
39-
assetType: "coin",
40-
chainId: props.chain.id,
41-
contractType: "DropERC20",
42-
});
43-
}}
44-
theme={getSDKTheme(theme === "light" ? "light" : "dark")}
45-
tokenAddress={props.tokenAddress as `0x${string}`}
46-
/>
25+
<div className="bg-card rounded-2xl border overflow-hidden flex flex-col">
26+
<div className="flex gap-2 p-4 border-b border-dashed">
27+
<TabButton
28+
label="Swap"
29+
onClick={() => setTab("swap")}
30+
isActive={tab === "swap"}
31+
/>
32+
<TabButton
33+
label="Buy"
34+
onClick={() => setTab("buy")}
35+
isActive={tab === "buy"}
36+
/>
37+
</div>
38+
39+
{tab === "buy" && (
40+
<BuyWidget
41+
amount="1"
42+
chain={props.chain}
43+
className="!rounded-2xl !w-full !border-none"
44+
title=""
45+
client={props.client}
46+
connectOptions={{
47+
autoConnect: false,
48+
}}
49+
onError={(e) => {
50+
const errorMessage = parseError(e);
51+
reportAssetBuyFailed({
52+
assetType: "coin",
53+
chainId: props.chain.id,
54+
contractType: "DropERC20",
55+
error: errorMessage,
56+
});
57+
}}
58+
onSuccess={() => {
59+
reportAssetBuySuccessful({
60+
assetType: "coin",
61+
chainId: props.chain.id,
62+
contractType: "DropERC20",
63+
});
64+
}}
65+
theme={themeObj}
66+
tokenAddress={props.tokenAddress as `0x${string}`}
67+
/>
68+
)}
69+
70+
{tab === "swap" && (
71+
<SwapWidget
72+
client={props.client}
73+
theme={themeObj}
74+
className="!rounded-2xl !border-none !w-full"
75+
prefill={{
76+
sellToken: {
77+
chainId: props.chain.id,
78+
tokenAddress: props.tokenAddress,
79+
},
80+
buyToken: {
81+
chainId: props.chain.id,
82+
},
83+
}}
84+
/>
85+
)}
86+
</div>
87+
);
88+
}
89+
90+
function TabButton(props: {
91+
label: string;
92+
onClick: () => void;
93+
isActive: boolean;
94+
}) {
95+
return (
96+
<Button
97+
onClick={props.onClick}
98+
className={cn(
99+
"rounded-full text-muted-foreground px-5 text-base bg-accent",
100+
props.isActive && "text-foreground border-foreground",
101+
)}
102+
variant="outline"
103+
>
104+
{props.label}
105+
</Button>
47106
);
48107
}

packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export type BuyWidgetProps = {
128128
amount: string;
129129

130130
/**
131-
* The title to display in the widget.
131+
* The title to display in the widget. If `title` is explicity set to an empty string, the title will not be displayed.
132132
*/
133133
title?: string;
134134

packages/thirdweb/src/react/web/ui/Bridge/common/WithHeader.tsx

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export function WithHeader({
1919
client: ThirdwebClient;
2020
}) {
2121
const theme = useCustomTheme();
22+
const showTitle = uiOptions.metadata?.title !== "";
23+
2224
return (
2325
<Container flex="column">
2426
{/* image */}
@@ -36,25 +38,33 @@ export function WithHeader({
3638
}}
3739
/>
3840
)}
41+
3942
<Container flex="column" px="lg">
4043
<Spacer y="lg" />
4144

42-
{/* title */}
43-
<Text color="primaryText" size="lg" weight={700}>
44-
{uiOptions.metadata?.title || defaultTitle}
45-
</Text>
46-
47-
{/* Description */}
48-
{uiOptions.metadata?.description && (
45+
{(showTitle || uiOptions.metadata?.description) && (
4946
<>
50-
<Spacer y="xs" />
51-
<Text color="secondaryText" size="sm">
52-
{uiOptions.metadata?.description}
53-
</Text>
47+
{/* title */}
48+
{showTitle && (
49+
<Text color="primaryText" size="lg" weight={700}>
50+
{uiOptions.metadata?.title || defaultTitle}
51+
</Text>
52+
)}
53+
54+
{/* Description */}
55+
{uiOptions.metadata?.description && (
56+
<>
57+
<Spacer y="xs" />
58+
<Text color="secondaryText" size="sm">
59+
{uiOptions.metadata?.description}
60+
</Text>
61+
</>
62+
)}
63+
64+
<Spacer y="lg" />
5465
</>
5566
)}
5667

57-
<Spacer y="lg" />
5868
{children}
5969
</Container>
6070
</Container>

packages/thirdweb/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import styled from "@emotion/styled";
2-
import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons";
2+
import {
3+
ChevronDownIcon,
4+
ChevronRightIcon,
5+
DiscIcon,
6+
} from "@radix-ui/react-icons";
37
import { useQuery } from "@tanstack/react-query";
48
import { useEffect, useState } from "react";
59
import { Buy, Sell } from "../../../../../bridge/index.js";
@@ -645,28 +649,26 @@ function SelectedTokenButton(props: {
645649
}}
646650
>
647651
{/* icons */}
648-
<div
649-
style={{
650-
position: "relative",
651-
}}
652-
>
652+
<Container relative color="secondaryText">
653653
{/* token icon */}
654654
<Img
655+
key={props.selectedToken?.data?.iconUri}
655656
src={
656-
props.selectedToken === undefined
657+
props.selectedToken?.data === undefined
657658
? undefined
658-
: props.selectedToken.data?.iconUri || ""
659+
: props.selectedToken.data.iconUri || ""
659660
}
660661
client={props.client}
661662
width={iconSize.lg}
662663
height={iconSize.lg}
664+
skeletonColor="modalBg"
665+
fallback={<DiscIcon width={iconSize.lg} height={iconSize.lg} />}
663666
style={{
664667
borderRadius: radius.full,
665668
}}
666669
/>
667670

668671
{/* chain icon */}
669-
670672
<Container
671673
bg="modalBg"
672674
style={{
@@ -683,12 +685,13 @@ function SelectedTokenButton(props: {
683685
client={props.client}
684686
width={iconSize.sm}
685687
height={iconSize.sm}
688+
skeletonColor="modalBg"
686689
style={{
687690
borderRadius: radius.full,
688691
}}
689692
/>
690693
</Container>
691-
</div>
694+
</Container>
692695

693696
{/* token symbol and chain name */}
694697
<Container flex="column" style={{ gap: "2px" }}>

packages/thirdweb/src/react/web/ui/Bridge/swap-widget/use-tokens.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Token } from "../../../../../bridge/index.js";
33
import { tokens } from "../../../../../bridge/Token.js";
44
import type { ThirdwebClient } from "../../../../../client/client.js";
55
import { isAddress } from "../../../../../utils/address.js";
6+
import { getThirdwebBaseUrl } from "../../../../../utils/domains.js";
67

78
export function useTokens(options: {
89
client: ThirdwebClient;
@@ -82,8 +83,10 @@ export function useTokenBalances(options: {
8283
if (!options.chainId) {
8384
throw new Error("Chain ID is required");
8485
}
86+
const baseUrl = getThirdwebBaseUrl("bridge");
87+
const isDev = baseUrl.includes("thirdweb-dev");
8588
const url = new URL(
86-
`https://api.thirdweb.com/v1/wallets/${options.walletAddress}/tokens`,
89+
`https://api.${isDev ? "thirdweb-dev" : "thirdweb"}.com/v1/wallets/${options.walletAddress}/tokens`,
8790
);
8891
url.searchParams.set("chainId", options.chainId.toString());
8992
url.searchParams.set("limit", options.limit.toString());

packages/thirdweb/src/react/web/ui/components/Img.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { useState } from "react";
33
import type { ThirdwebClient } from "../../../../client/client.js";
44
import { resolveScheme } from "../../../../utils/ipfs.js";
5-
import { radius } from "../../../core/design-system/index.js";
5+
import { radius, type Theme } from "../../../core/design-system/index.js";
66
import { Container } from "./basic.js";
77
import { Skeleton } from "./Skeleton.js";
88

@@ -20,6 +20,7 @@ export const Img: React.FC<{
2020
fallbackImage?: string;
2121
fallback?: React.ReactNode;
2222
client: ThirdwebClient;
23+
skeletonColor?: keyof Theme["colors"];
2324
}> = (props) => {
2425
const [_status, setStatus] = useState<"pending" | "fallback" | "loaded">(
2526
"pending",
@@ -38,7 +39,14 @@ export const Img: React.FC<{
3839
const heightPx = `${props.height || props.width}px`;
3940

4041
if (propSrc === undefined) {
41-
return <Skeleton height={heightPx} width={widthPx} />;
42+
return (
43+
<Skeleton
44+
height={heightPx}
45+
width={widthPx}
46+
color={props.skeletonColor}
47+
style={props.style}
48+
/>
49+
);
4250
}
4351

4452
const getSrc = () => {
@@ -65,7 +73,14 @@ export const Img: React.FC<{
6573
position: "relative",
6674
}}
6775
>
68-
{status === "pending" && <Skeleton height={heightPx} width={widthPx} />}
76+
{status === "pending" && (
77+
<Skeleton
78+
height={heightPx}
79+
width={widthPx}
80+
color={props.skeletonColor}
81+
style={props.style}
82+
/>
83+
)}
6984
{status === "fallback" &&
7085
(props.fallback || (
7186
<Container

0 commit comments

Comments
 (0)