Skip to content

Commit 0e3f346

Browse files
authored
Add SwapWidget in SDK, add in dashboard, BuyWidget UI improvements (#8044)
1 parent 552f702 commit 0e3f346

File tree

68 files changed

+3725
-343
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+3725
-343
lines changed

.changeset/lucky-turtles-smell.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Add `SwapWidget` component for swapping tokens using thirdweb Bridge
6+
7+
```tsx
8+
<SwapWidget client={thirdwebClient} />
9+
```

apps/dashboard/src/@/analytics/report.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,54 @@ export function reportAssetBuySuccessful(properties: {
250250
});
251251
}
252252

253+
type TokenSwapParams = {
254+
buyTokenChainId: number;
255+
buyTokenAddress: string;
256+
sellTokenChainId: number;
257+
sellTokenAddress: string;
258+
pageType: "asset" | "bridge" | "chain";
259+
};
260+
261+
/**
262+
* ### Why do we need to report this event?
263+
* - To track number of successful token swaps from the token page
264+
* - To track which tokens are being swapped the most
265+
*
266+
* ### Who is responsible for this event?
267+
* @MananTank
268+
*/
269+
export function reportTokenSwapSuccessful(properties: TokenSwapParams) {
270+
posthog.capture("token swap successful", properties);
271+
}
272+
273+
/**
274+
* ### Why do we need to report this event?
275+
* - To track number of failed token swaps from the token page
276+
* - To track which tokens are being swapped the most
277+
*
278+
* ### Who is responsible for this event?
279+
* @MananTank
280+
*/
281+
export function reportTokenSwapFailed(
282+
properties: TokenSwapParams & {
283+
errorMessage: string;
284+
},
285+
) {
286+
posthog.capture("token swap failed", properties);
287+
}
288+
289+
/**
290+
* ### Why do we need to report this event?
291+
* - To track number of cancelled token swaps from the token page
292+
* - To track which tokens are being swapped the most
293+
*
294+
* ### Who is responsible for this event?
295+
* @MananTank
296+
*/
297+
export function reportTokenSwapCancelled(properties: TokenSwapParams) {
298+
posthog.capture("token swap cancelled", properties);
299+
}
300+
253301
/**
254302
* ### Why do we need to report this event?
255303
* - To track number of failed asset purchases from the token page
@@ -272,6 +320,26 @@ export function reportAssetBuyFailed(properties: {
272320
});
273321
}
274322

323+
/**
324+
* ### Why do we need to report this event?
325+
* - To track number of cancelled asset purchases from the token page
326+
* - To track the errors that users encounter when trying to purchase an asset
327+
*
328+
* ### Who is responsible for this event?
329+
* @MananTank
330+
*/
331+
export function reportAssetBuyCancelled(properties: {
332+
chainId: number;
333+
contractType: AssetContractType;
334+
assetType: "nft" | "coin";
335+
}) {
336+
posthog.capture("asset buy cancelled", {
337+
assetType: properties.assetType,
338+
chainId: properties.chainId,
339+
contractType: properties.contractType,
340+
});
341+
}
342+
275343
// Assets Landing Page ----------------------------
276344

277345
/**
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
"use client";
2+
3+
import { useTheme } from "next-themes";
4+
import { useState } from "react";
5+
import type { Chain, ThirdwebClient } from "thirdweb";
6+
import { BuyWidget, SwapWidget } from "thirdweb/react";
7+
import {
8+
reportAssetBuyCancelled,
9+
reportAssetBuyFailed,
10+
reportAssetBuySuccessful,
11+
reportTokenSwapCancelled,
12+
reportTokenSwapFailed,
13+
reportTokenSwapSuccessful,
14+
} from "@/analytics/report";
15+
import { Button } from "@/components/ui/button";
16+
import { cn } from "@/lib/utils";
17+
import { parseError } from "@/utils/errorParser";
18+
import { getSDKTheme } from "@/utils/sdk-component-theme";
19+
20+
export function BuyAndSwapEmbed(props: {
21+
client: ThirdwebClient;
22+
chain: Chain;
23+
tokenAddress: string | undefined;
24+
buyAmount: string | undefined;
25+
pageType: "asset" | "bridge" | "chain";
26+
}) {
27+
const { theme } = useTheme();
28+
const [tab, setTab] = useState<"buy" | "swap">("swap");
29+
const themeObj = getSDKTheme(theme === "light" ? "light" : "dark");
30+
return (
31+
<div className="bg-card rounded-2xl border overflow-hidden flex flex-col">
32+
<div className="flex gap-2.5 p-4 border-b border-dashed">
33+
<TabButton
34+
label="Swap"
35+
onClick={() => setTab("swap")}
36+
isActive={tab === "swap"}
37+
/>
38+
<TabButton
39+
label="Buy"
40+
onClick={() => setTab("buy")}
41+
isActive={tab === "buy"}
42+
/>
43+
</div>
44+
45+
{tab === "buy" && (
46+
<BuyWidget
47+
amount={props.buyAmount || "1"}
48+
chain={props.chain}
49+
className="!rounded-2xl !w-full !border-none"
50+
title=""
51+
client={props.client}
52+
connectOptions={{
53+
autoConnect: false,
54+
}}
55+
onError={(e) => {
56+
const errorMessage = parseError(e);
57+
if (props.pageType === "asset") {
58+
reportAssetBuyFailed({
59+
assetType: "coin",
60+
chainId: props.chain.id,
61+
contractType: "DropERC20",
62+
error: errorMessage,
63+
});
64+
}
65+
}}
66+
onCancel={() => {
67+
if (props.pageType === "asset") {
68+
reportAssetBuyCancelled({
69+
assetType: "coin",
70+
chainId: props.chain.id,
71+
contractType: "DropERC20",
72+
});
73+
}
74+
}}
75+
onSuccess={() => {
76+
if (props.pageType === "asset") {
77+
reportAssetBuySuccessful({
78+
assetType: "coin",
79+
chainId: props.chain.id,
80+
contractType: "DropERC20",
81+
});
82+
}
83+
}}
84+
theme={themeObj}
85+
tokenAddress={props.tokenAddress as `0x${string}`}
86+
paymentMethods={["card"]}
87+
/>
88+
)}
89+
90+
{tab === "swap" && (
91+
<SwapWidget
92+
client={props.client}
93+
theme={themeObj}
94+
className="!rounded-2xl !border-none !w-full"
95+
prefill={{
96+
sellToken: {
97+
chainId: props.chain.id,
98+
tokenAddress: props.tokenAddress,
99+
},
100+
buyToken: {
101+
chainId: props.chain.id,
102+
},
103+
}}
104+
onError={(error, quote) => {
105+
const errorMessage = parseError(error);
106+
reportTokenSwapFailed({
107+
errorMessage: errorMessage,
108+
buyTokenChainId: quote.intent.destinationChainId,
109+
buyTokenAddress: quote.intent.destinationTokenAddress,
110+
sellTokenChainId: quote.intent.originChainId,
111+
sellTokenAddress: quote.intent.originTokenAddress,
112+
pageType: props.pageType,
113+
});
114+
}}
115+
onSuccess={(quote) => {
116+
reportTokenSwapSuccessful({
117+
buyTokenChainId: quote.intent.destinationChainId,
118+
buyTokenAddress: quote.intent.destinationTokenAddress,
119+
sellTokenChainId: quote.intent.originChainId,
120+
sellTokenAddress: quote.intent.originTokenAddress,
121+
pageType: props.pageType,
122+
});
123+
}}
124+
onCancel={(quote) => {
125+
reportTokenSwapCancelled({
126+
buyTokenChainId: quote.intent.destinationChainId,
127+
buyTokenAddress: quote.intent.destinationTokenAddress,
128+
sellTokenChainId: quote.intent.originChainId,
129+
sellTokenAddress: quote.intent.originTokenAddress,
130+
pageType: props.pageType,
131+
});
132+
}}
133+
/>
134+
)}
135+
</div>
136+
);
137+
}
138+
139+
function TabButton(props: {
140+
label: string;
141+
onClick: () => void;
142+
isActive: boolean;
143+
}) {
144+
return (
145+
<Button
146+
onClick={props.onClick}
147+
className={cn(
148+
"rounded-full text-muted-foreground px-5 text-base bg-accent",
149+
props.isActive && "text-foreground border-foreground",
150+
)}
151+
variant="outline"
152+
>
153+
{props.label}
154+
</Button>
155+
);
156+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { GridPattern } from "@/components/ui/background-patterns";
2+
3+
export function GridPatternEmbedContainer(props: {
4+
children: React.ReactNode;
5+
}) {
6+
return (
7+
<div className=" sm:flex sm:justify-center w-full sm:border sm:border-dashed sm:bg-accent/20 sm:py-12 rounded-lg overflow-hidden relative">
8+
<GridPattern
9+
width={30}
10+
height={30}
11+
x={-1}
12+
y={-1}
13+
strokeDasharray={"4 2"}
14+
className="text-border dark:text-border/70 hidden lg:block"
15+
style={{
16+
maskImage:
17+
"linear-gradient(to bottom right,white,transparent,transparent)",
18+
}}
19+
/>
20+
<div className="sm:w-[420px] z-10">{props.children}</div>
21+
</div>
22+
);
23+
}
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
"use client";
2-
import { useTheme } from "next-themes";
3-
import { defineChain, type ThirdwebClient } from "thirdweb";
2+
import type { ThirdwebClient } from "thirdweb";
43
import type { ChainMetadata } from "thirdweb/chains";
5-
import { BuyWidget } from "thirdweb/react";
6-
import { getSDKTheme } from "@/utils/sdk-component-theme";
4+
import { BuyAndSwapEmbed } from "@/components/blocks/BuyAndSwapEmbed";
5+
import { GridPatternEmbedContainer } from "@/components/blocks/grid-pattern-embed-container";
6+
import { defineDashboardChain } from "@/lib/defineDashboardChain";
77

88
export function BuyFundsSection(props: {
99
chain: ChainMetadata;
1010
client: ThirdwebClient;
1111
}) {
12-
const { theme } = useTheme();
1312
return (
14-
<section className="flex flex-col gap-4 items-center justify-center">
15-
<BuyWidget
16-
amount="0"
17-
// eslint-disable-next-line no-restricted-syntax
18-
chain={defineChain(props.chain.chainId)}
13+
<GridPatternEmbedContainer>
14+
<BuyAndSwapEmbed
1915
client={props.client}
20-
theme={getSDKTheme(theme === "dark" ? "dark" : "light")}
16+
// eslint-disable-next-line no-restricted-syntax
17+
chain={defineDashboardChain(props.chain.chainId, props.chain)}
18+
buyAmount={undefined}
19+
tokenAddress={undefined}
20+
pageType="chain"
2121
/>
22-
</section>
22+
</GridPatternEmbedContainer>
2323
);
2424
}

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

Lines changed: 0 additions & 48 deletions
This file was deleted.

0 commit comments

Comments
 (0)