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
11 changes: 11 additions & 0 deletions .changeset/slow-crews-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"thirdweb": patch
---

BuyWidget UI improvements and new features:

- `chain`, and `amount` props are now optional
- User can always change the token and chain selection in the widget
- Both fiat and token amounts are editable
- connected wallet can be disconnected from the widget
- current balance displayed in the widget
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const defaultOptions: BridgeComponentsPlaygroundOptions = {
sellerAddress: "0x0000000000000000000000000000000000000000",
title: "",
transactionData: "",
receiverAddress: undefined,
currency: "USD",
showThirdwebBranding: true,
},
Expand Down
8 changes: 6 additions & 2 deletions apps/playground-web/src/app/payments/components/CodeGen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,12 @@ tokenId: 2n,
tokenAddress: options.payOptions.buyTokenAddress
? quotes(options.payOptions.buyTokenAddress)
: undefined,
seller: options.payOptions.sellerAddress
? quotes(options.payOptions.sellerAddress)
seller:
widget === "checkout"
? quotes(options.payOptions.sellerAddress)
: undefined,
receiverAddress: options.payOptions.receiverAddress
? quotes(options.payOptions.receiverAddress)
: undefined,
buttonLabel: options.payOptions.buttonLabel
? quotes(options.payOptions.buttonLabel)
Expand Down
31 changes: 28 additions & 3 deletions apps/playground-web/src/app/payments/components/LeftSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export function LeftSection(props: {
<div className="flex flex-col gap-2">
<Label>Token</Label>
<TokenSelector
disableAddress
chainId={selectedChain}
client={THIRDWEB_CLIENT}
enabled={true}
Expand All @@ -154,10 +155,10 @@ export function LeftSection(props: {
)}

{/* Mode-specific form fields */}
<div className="my-2">
<div className="">
{/* Buy Mode - Amount and Payment Methods */}
{props.widget === "buy" && (
<div className="space-y-4">
<div className="space-y-6">
<div className="flex flex-col gap-2">
<Label htmlFor={buyTokenAmountId}>Amount</Label>
<Input
Expand All @@ -177,6 +178,30 @@ export function LeftSection(props: {
/>
</div>

{props.widget === "buy" && (
<div className="flex flex-col gap-2">
<Label htmlFor="receiver-address">Receiver Address</Label>
<p className="text-muted-foreground text-sm">
Receive the tokens in a different wallet address
</p>
<Input
className="bg-card"
id="receiver-address"
onChange={(e) => {
setOptions((v) => ({
...v,
payOptions: {
...v.payOptions,
receiverAddress: e.target.value as Address,
},
}));
}}
placeholder="0x..."
value={payOptions.receiverAddress || ""}
/>
</div>
)}

{/* Payment Methods */}
<div className="flex flex-col gap-3">
<Label>Payment Methods</Label>
Expand Down Expand Up @@ -206,6 +231,7 @@ export function LeftSection(props: {
/>
<Label htmlFor={cryptoPaymentId}>Crypto</Label>
</div>

<div className="flex items-center space-x-2">
<Checkbox
checked={payOptions.paymentMethods.includes("card")}
Expand Down Expand Up @@ -257,7 +283,6 @@ export function LeftSection(props: {
value={payOptions.sellerAddress || ""}
/>
</div>

<div className="flex flex-col gap-2">
<Label htmlFor={paymentAmountId}>Price</Label>
<Input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export function RightSection(props: {
tokenAddress={props.options.payOptions.buyTokenAddress}
currency={props.options.payOptions.currency}
showThirdwebBranding={props.options.payOptions.showThirdwebBranding}
receiverAddress={props.options.payOptions.receiverAddress}
key={JSON.stringify({
amount: props.options.payOptions.buyTokenAmount,
chain: props.options.payOptions.buyTokenChain,
tokenAddress: props.options.payOptions.buyTokenAddress,
})}
{...(props.options.payOptions.buttonLabel && {
buttonLabel: props.options.payOptions.buttonLabel,
})}
Expand Down
1 change: 1 addition & 0 deletions apps/playground-web/src/app/payments/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export type BridgeComponentsPlaygroundOptions = {

// direct_payment mode options
sellerAddress: Address;
receiverAddress: Address | undefined;

// transaction mode options
transactionData?: string; // Simplified for demo; could be more complex in real implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const defaultOptions: BridgeComponentsPlaygroundOptions = {
paymentMethods: ["crypto", "card"],
sellerAddress: "0x0000000000000000000000000000000000000000",
title: "",
receiverAddress: undefined,
transactionData: "",
currency: "USD",
showThirdwebBranding: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const defaultOptions: BridgeComponentsPlaygroundOptions = {
transactionData: "",
currency: "USD",
showThirdwebBranding: true,
receiverAddress: undefined,
},
theme: {
darkColorOverrides: {},
Expand Down
124 changes: 36 additions & 88 deletions packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import type { TokenWithPrices } from "../../../../bridge/index.js";
import type { Chain } from "../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../client/client.js";
import { NATIVE_TOKEN_ADDRESS } from "../../../../constants/addresses.js";
import type { SupportedFiatCurrency } from "../../../../pay/convert/type.js";
import type { PurchaseData } from "../../../../pay/types.js";
import type { Address } from "../../../../utils/address.js";
Expand All @@ -25,22 +24,18 @@
import type { CompletedStatusResult } from "../../../core/hooks/useStepExecutor.js";
import type { SupportedTokens } from "../../../core/utils/defaultTokens.js";
import { webWindowAdapter } from "../../adapters/WindowAdapter.js";
import { useConnectLocale } from "../ConnectWallet/locale/getConnectLocale.js";
import type { ConnectLocale } from "../ConnectWallet/locale/types.js";
import connectLocaleEn from "../ConnectWallet/locale/en.js";
import { EmbedContainer } from "../ConnectWallet/Modal/ConnectEmbed.js";
import { DynamicHeight } from "../components/DynamicHeight.js";
import { Spinner } from "../components/Spinner.js";
import type { LocaleId } from "../types.js";
import { useTokenQuery } from "./common/token-query.js";
import { ErrorBanner } from "./ErrorBanner.js";
import { FundWallet } from "./FundWallet.js";
import { PaymentDetails } from "./payment-details/PaymentDetails.js";
import { PaymentSelection } from "./payment-selection/PaymentSelection.js";
import { SuccessScreen } from "./payment-success/SuccessScreen.js";
import { QuoteLoader } from "./QuoteLoader.js";
import { StepRunner } from "./StepRunner.js";
import { useActiveWalletInfo } from "./swap-widget/hooks.js";
import type { PaymentMethod, RequiredParams } from "./types.js";
import { UnsupportedTokenScreen } from "./UnsupportedTokenScreen.js";

export type BuyOrOnrampPrepareResult = Extract<
BridgePrepareResult,
Expand Down Expand Up @@ -68,14 +63,7 @@
* ```
*/
client: ThirdwebClient;
/**
* By default - ConnectButton UI uses the `en-US` locale for english language users.
*
* You can customize the language used in the ConnectButton UI by setting the `locale` prop.
*
* Refer to the [`LocaleId`](https://portal.thirdweb.com/references/typescript/v5/LocaleId) type for supported locales.
*/
locale?: LocaleId;

/**
* Set the theme for the `BuyWidget` component. By default it is set to `"dark"`
*
Expand Down Expand Up @@ -130,7 +118,7 @@
/**
* The chain the accepted token is on.
*/
chain: Chain;
chain?: Chain;

/**
* Address of the token to buy. Leave undefined for the native token, or use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
Expand All @@ -140,7 +128,7 @@
/**
* The amount to buy **(as a decimal string)**, e.g. "1.5" for 1.5 tokens.
*/
amount: string;
amount?: string;

/**
* The title to display in the widget. If `title` is explicity set to an empty string, the title will not be displayed.
Expand Down Expand Up @@ -213,6 +201,11 @@
* The receiver address for the purchased funds.
*/
receiverAddress?: Address;

/**
* Callback to be called when the user disconnects the active wallet.
*/
onDisconnect?: () => void;
};

/**
Expand Down Expand Up @@ -326,31 +319,12 @@
* @bridge
*/
export function BuyWidget(props: BuyWidgetProps) {
return (
<BridgeWidgetContainer
theme={props.theme}
className={props.className}
style={props.style}
>
<BridgeWidgetContentWrapper {...props} />
</BridgeWidgetContainer>
);
}

function BridgeWidgetContentWrapper(props: BuyWidgetProps) {
const localQuery = useConnectLocale(props.locale || "en_US");
const tokenQuery = useTokenQuery({
tokenAddress: props.tokenAddress,
chainId: props.chain.id,
client: props.client,
});

useQuery({
queryFn: () => {
trackPayEvent({
client: props.client,
event: "ub:ui:buy_widget:render",
toChainId: props.chain.id,
toChainId: props.chain?.id,

Check warning on line 327 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L327 was not covered by tests
toToken: props.tokenAddress,
});
return true;
Expand All @@ -372,33 +346,15 @@
return props.connectOptions;
}, [props.connectOptions, props.showThirdwebBranding]);

if (tokenQuery.isPending || !localQuery.data) {
return (
<div
style={{
alignItems: "center",
display: "flex",
justifyContent: "center",
minHeight: "350px",
}}
>
<Spinner color="secondaryText" size="xl" />
</div>
);
} else if (tokenQuery.data?.type === "unsupported_token") {
return (
<UnsupportedTokenScreen
chain={props.chain}
client={props.client}
tokenAddress={props.tokenAddress || NATIVE_TOKEN_ADDRESS}
/>
);
} else if (tokenQuery.data?.type === "success") {
return (
return (
<BridgeWidgetContainer
theme={props.theme}
className={props.className}
style={props.style}

Check warning on line 353 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx#L349-L353

Added lines #L349 - L353 were not covered by tests
>
<BridgeWidgetContent
{...props}
connectLocale={localQuery.data}
destinationToken={tokenQuery.data.token}
theme={props.theme || "dark"}

Check warning on line 357 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L357 was not covered by tests
currency={props.currency || "USD"}
paymentMethods={props.paymentMethods || ["crypto", "card"]}
presetOptions={props.presetOptions || [5, 10, 20]}
Expand All @@ -409,23 +365,8 @@
: props.showThirdwebBranding
}
/>
);
} else if (tokenQuery.error) {
return (
<ErrorBanner
client={props.client}
error={tokenQuery.error}
onRetry={() => {
tokenQuery.refetch();
}}
onCancel={() => {
props.onCancel?.(undefined);
}}
/>
);
}

return null;
</BridgeWidgetContainer>

Check warning on line 368 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L368 was not covered by tests
);
}

type BuyWidgetScreen =
Expand Down Expand Up @@ -475,13 +416,15 @@
function BridgeWidgetContent(
props: RequiredParams<
BuyWidgetProps,
"currency" | "presetOptions" | "showThirdwebBranding" | "paymentMethods"
> & {
connectLocale: ConnectLocale;
destinationToken: TokenWithPrices;
},
| "currency"
| "presetOptions"
| "showThirdwebBranding"
| "paymentMethods"
| "theme"
>,
) {
const [screen, setScreen] = useState<BuyWidgetScreen>({ id: "1:buy-ui" });
const activeWalletInfo = useActiveWalletInfo();

Check warning on line 427 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L427 was not covered by tests

const handleError = useCallback(
(error: Error, quote: BridgePrepareResult | undefined) => {
Expand Down Expand Up @@ -511,9 +454,11 @@
[props.onCancel],
);

if (screen.id === "1:buy-ui") {
if (screen.id === "1:buy-ui" || !activeWalletInfo) {

Check warning on line 457 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L457 was not covered by tests
return (
<FundWallet
theme={props.theme}
onDisconnect={props.onDisconnect}

Check warning on line 461 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx#L460-L461

Added lines #L460 - L461 were not covered by tests
client={props.client}
connectOptions={props.connectOptions}
onContinue={(destinationAmount, destinationToken, receiverAddress) => {
Expand All @@ -534,8 +479,11 @@
}}
buttonLabel={props.buttonLabel}
currency={props.currency}
initialAmount={props.amount}
destinationToken={props.destinationToken}
initialSelection={{
tokenAddress: props.tokenAddress,
chainId: props.chain?.id,
amount: props.amount,
}}

Check warning on line 486 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx#L482-L486

Added lines #L482 - L486 were not covered by tests
/>
);
}
Expand All @@ -545,7 +493,7 @@
<PaymentSelection
// from props
client={props.client}
connectLocale={props.connectLocale}
connectLocale={connectLocaleEn}

Check warning on line 496 in packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L496 was not covered by tests
connectOptions={props.connectOptions}
paymentMethods={props.paymentMethods}
currency={props.currency}
Expand Down
Loading
Loading