diff --git a/.changeset/pink-guests-agree.md b/.changeset/pink-guests-agree.md new file mode 100644 index 00000000000..10701366e69 --- /dev/null +++ b/.changeset/pink-guests-agree.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +New PayEmbed UI for token selection diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx index 9c014f87e03..c497c29ce0f 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx @@ -5,6 +5,7 @@ import type { ThirdwebClient } from "../../../../../../client/client.js"; import { NATIVE_TOKEN_ADDRESS } from "../../../../../../constants/addresses.js"; import type { BuyWithCryptoStatus } from "../../../../../../pay/buyWithCrypto/getStatus.js"; import type { BuyWithFiatStatus } from "../../../../../../pay/buyWithFiat/getStatus.js"; +import { formatNumber } from "../../../../../../utils/formatNumber.js"; import type { Account } from "../../../../../../wallets/interfaces/wallet.js"; import type { WalletId } from "../../../../../../wallets/wallet-types.js"; import { @@ -56,6 +57,7 @@ import { FiatValue } from "./swap/FiatValue.js"; import { PaymentSelectionScreen } from "./swap/PaymentSelectionScreen.js"; import { SwapFlow } from "./swap/SwapFlow.js"; import { SwapScreenContent } from "./swap/SwapScreenContent.js"; +import { TokenSelectorScreen } from "./swap/TokenSelectorScreen.js"; import { TransferFlow } from "./swap/TransferFlow.js"; import { type SupportedChainAndTokens, @@ -453,30 +455,39 @@ function BuyScreenContent(props: BuyScreenContentProps) { } return ( - x.address !== NATIVE_TOKEN_ADDRESS)} - onTokenSelect={(tokenInfo) => { - setFromToken(tokenInfo); + onConnect={() => { + setScreen({ + id: "connect-payer-wallet", + backScreen: screen, + }); + }} + modalTitle="Available tokens" + connectLocale={props.connectLocale} + client={props.client} + sourceTokens={sourceSupportedTokens} + sourceSupportedTokens={sourceSupportedTokens} + toChain={toChain} + toToken={toToken} + tokenAmount={tokenAmount} + fromChain={fromChain} + fromToken={fromToken} + mode={payOptions.mode} + hiddenWallets={props.hiddenWallets} + onSelect={(w, token, chain) => { + const account = w.getAccount(); + if (account) { + setPayer({ + account, + chain, + wallet: w, + }); + setFromToken(token); + setFromChain(chain); + } goBack(); }} - chain={fromChain} - chainSelection={ - // hide chain selection if it's disabled - payOptions.buyWithCrypto !== false && - payOptions.buyWithCrypto?.prefillSource?.allowEdits?.chain !== false - ? { - chains: supportedSourcesQuery.data.map((x) => x.chain), - select: (c) => setFromChain(c), - } - : undefined - } - connectLocale={connectLocale} - client={client} - modalTitle="Pay with" /> ); } @@ -550,23 +561,23 @@ function BuyScreenContent(props: BuyScreenContentProps) { payWithFiatEnabled={props.payOptions.buyWithFiat !== false} toChain={toChain} toToken={toToken} + fromToken={fromToken} + fromChain={fromChain} tokenAmount={tokenAmount} - onSelect={(w, token, chain) => { - const account = w.getAccount(); - if (account) { - setPayer({ - account, - chain, - wallet: w, - }); - setFromToken(token); - setFromChain(chain); - setScreen({ id: "buy-with-crypto" }); - } + onContinue={() => { + setScreen({ id: "buy-with-crypto" }); }} onSelectFiat={() => { setScreen({ id: "buy-with-fiat" }); }} + onPickToken={() => { + setScreen({ + id: "select-from-token", + backScreen: { + id: "select-payment-method", + }, + }); + }} showAllWallets={!!props.connectOptions?.showAllWallets} wallets={props.connectOptions?.wallets} onBack={() => { @@ -659,8 +670,9 @@ function SelectedTokenInfo(props: { disabled?: boolean; }) { const getWidth = () => { - let chars = props.tokenAmount.replace(".", "").length; - const hasDot = props.tokenAmount.includes("."); + const amount = formatNumber(Number(props.tokenAmount), 5).toString(); + let chars = amount.replace(".", "").length; + const hasDot = amount.includes("."); if (hasDot) { chars += 0.3; } @@ -685,7 +697,11 @@ function SelectedTokenInfo(props: { placeholder="0" type="text" data-placeholder={props.tokenAmount === ""} - value={props.tokenAmount || "0"} + value={ + props.tokenAmount + ? formatNumber(Number(props.tokenAmount), 5) + : "0" + } disabled={props.disabled} onClick={(e) => { // put cursor at the end of the input @@ -885,7 +901,7 @@ function MainScreen(props: { client={props.client} /> - + {/* Continue */} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx index 8cf800b48b6..2e7a56b0caf 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/TransactionModeScreen.tsx @@ -32,8 +32,8 @@ import { NATIVE_TOKEN, isNativeToken, } from "../nativeToken.js"; -import { WalletRow } from "./WalletSelectorButton.js"; import { useTransactionCostAndData } from "./main/useBuyTxStates.js"; +import { WalletRow } from "./swap/TokenSelectorScreen.js"; import type { SupportedChainAndTokens } from "./swap/useSwapSupportedChains.js"; export function TransactionModeScreen(props: { diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/WalletSelectorButton.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/WalletSelectorButton.tsx deleted file mode 100644 index 26d89094e3b..00000000000 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/WalletSelectorButton.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import styled from "@emotion/styled"; -import { ChevronRightIcon } from "@radix-ui/react-icons"; -import { useState } from "react"; -import type { Chain } from "../../../../../../chains/types.js"; -import type { ThirdwebClient } from "../../../../../../client/client.js"; -import { shortenAddress } from "../../../../../../utils/address.js"; -import type { Wallet } from "../../../../../../wallets/interfaces/wallet.js"; -import type { WalletId } from "../../../../../../wallets/wallet-types.js"; -import { useCustomTheme } from "../../../../../core/design-system/CustomThemeProvider.js"; -import { - type fontSize, - iconSize, - radius, - spacing, -} from "../../../../../core/design-system/index.js"; -import { useChainName } from "../../../../../core/hooks/others/useChainQuery.js"; -import type { TokenInfo } from "../../../../../core/utils/defaultTokens.js"; -import { useEnsAvatar, useEnsName } from "../../../../../core/utils/wallet.js"; -import { Img } from "../../../components/Img.js"; -import { TokenIcon } from "../../../components/TokenIcon.js"; -import { WalletImage } from "../../../components/WalletImage.js"; -import { Container } from "../../../components/basic.js"; -import { Button } from "../../../components/buttons.js"; -import { Text } from "../../../components/text.js"; -import { Blobbie } from "../../Blobbie.js"; -import { formatTokenBalance } from "../formatTokenBalance.js"; -import { FiatValue } from "./swap/FiatValue.js"; -import type { TokenBalance } from "./swap/PaymentSelectionScreen.js"; - -export function WalletRowWithBalances(props: { - client: ThirdwebClient; - address: string; - wallet: Wallet; - balances: TokenBalance[]; - onClick: (wallet: Wallet, token: TokenInfo, chain: Chain) => void; -}) { - const theme = useCustomTheme(); - const [showAll, setShowAll] = useState(false); - const maxDisplayedBalances = 3; - const displayedBalances = showAll - ? props.balances - : props.balances.slice(0, maxDisplayedBalances); - - return ( - - - - -
- - {props.balances.length > 0 ? ( - <> - {displayedBalances.map((b) => ( - props.onClick(props.wallet, b.token, b.chain)} - key={`${b.token.address}-${b.chain.id}`} - tokenBalance={b} - wallet={props.wallet} - /> - ))} - {props.balances.length > maxDisplayedBalances && ( - setShowAll(!showAll)} - style={{ - justifyContent: "start", - }} - > - {showAll ? "Show less" : "Show more"} - - )} - - ) : ( - - - Not enough funds - - - )} - - - ); -} - -function TokenBalanceRow(props: { - client: ThirdwebClient; - tokenBalance: TokenBalance; - wallet: Wallet; - onClick: (token: TokenInfo, wallet: Wallet) => void; -}) { - const { tokenBalance, wallet, onClick, client } = props; - const chainInfo = useChainName(tokenBalance.chain); - return ( - onClick(tokenBalance.token, wallet)} - variant="secondary" - > - - - - - {tokenBalance.token.symbol} - - {chainInfo && {chainInfo.name}} - - - - - - {formatTokenBalance(tokenBalance.balance, true, 2)} - - - - - - - ); -} - -export function WalletRow(props: { - client: ThirdwebClient; - address: string; - iconSize?: keyof typeof iconSize; - textSize?: keyof typeof fontSize; - walletId?: WalletId; -}) { - const { client, address } = props; - const walletId = props.walletId; - const theme = useCustomTheme(); - const ensNameQuery = useEnsName({ - client, - address, - }); - const addressOrENS = ensNameQuery.data || shortenAddress(address); - const ensAvatarQuery = useEnsAvatar({ - client, - ensName: ensNameQuery.data, - }); - return ( - - {ensAvatarQuery.data ? ( - - ) : walletId ? ( - - ) : ( - - - - )} - - - {addressOrENS || shortenAddress(props.address)} - - - ); -} - -const StyledButton = /* @__PURE__ */ styled(Button)((_) => { - const theme = useCustomTheme(); - return { - background: theme.colors.tertiaryBg, - justifyContent: "space-between", - flexDirection: "row", - padding: spacing.sm, - gap: spacing.sm, - "&:hover": { - background: theme.colors.secondaryButtonBg, - transform: "scale(1.01)", - }, - transition: "background 200ms ease, transform 150ms ease", - }; -}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts index 5b523f4a897..18db8cd4cbb 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts @@ -80,7 +80,9 @@ export function useFromTokenSelectionStates(options: { // -------------------------------------------------------------------------- const firstSupportedSource = supportedSources?.length - ? supportedSources[0] + ? supportedSources.length === 1 + ? supportedSources[0] + : supportedSources.find((x) => x.chain.id !== 1) // dont use mainnet as a default source, unless its the only source : undefined; // Source token and chain selection --------------------------------------------------- diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/BuyTokenInput.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/BuyTokenInput.tsx index 8e3a65fff7a..e7d7221d510 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/BuyTokenInput.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/BuyTokenInput.tsx @@ -1,21 +1,10 @@ -import styled from "@emotion/styled"; -import { ChevronDownIcon } from "@radix-ui/react-icons"; import type { Chain } from "../../../../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js"; -import { - fontSize, - iconSize, - spacing, -} from "../../../../../../core/design-system/index.js"; -import { useChainName } from "../../../../../../core/hooks/others/useChainQuery.js"; -import { Skeleton } from "../../../../components/Skeleton.js"; +import { fontSize } from "../../../../../../core/design-system/index.js"; import { Spacer } from "../../../../components/Spacer.js"; -import { TokenIcon } from "../../../../components/TokenIcon.js"; import { Container } from "../../../../components/basic.js"; -import { Button } from "../../../../components/buttons.js"; import { Input } from "../../../../components/formElements.js"; -import { Text } from "../../../../components/text.js"; +import { TokenRow } from "../../../../components/token/TokenRow.js"; import { TokenSymbol } from "../../../../components/token/TokenSymbol.js"; import type { ERC20OrNativeToken } from "../../nativeToken.js"; import { getBuyTokenAmountFontSize } from "../utils.js"; @@ -35,7 +24,6 @@ export function BuyTokenInput(props: { freezeAmount?: boolean; freezeChainAndToken?: boolean; }) { - const { name } = useChainName(props.chain); const getWidth = () => { let chars = props.value.replace(".", "").length; const hasDot = props.value.includes("."); @@ -144,70 +132,16 @@ export function BuyTokenInput(props: { {/* Token / Chain selector */} - - - - - - {/* Token Symbol */} - - - {/* Network Name */} - {name ? ( - - {name} - - ) : ( - - )} - - - - - + )} ); } - -const TokenButton = /* @__PURE__ */ styled(Button)(() => { - const theme = useCustomTheme(); - return { - background: theme.colors.tertiaryBg, - border: `1px solid ${theme.colors.borderColor}`, - justifyContent: "flex-start", - transition: "background 0.3s", - padding: spacing.sm, - }; -}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PayWithCrypto.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PayWithCrypto.tsx index 85a86c7b3f3..5cd674d5c07 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PayWithCrypto.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PayWithCrypto.tsx @@ -18,7 +18,7 @@ import { TokenSymbol } from "../../../../components/token/TokenSymbol.js"; import { formatTokenBalance } from "../../formatTokenBalance.js"; import { type NativeToken, isNativeToken } from "../../nativeToken.js"; import { PayTokenIcon } from "../PayTokenIcon.js"; -import { WalletRow } from "../WalletSelectorButton.js"; +import { WalletRow } from "./TokenSelectorScreen.js"; /** * Shows an amount "value" and renders the selected token and chain diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PaymentSelectionScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PaymentSelectionScreen.tsx index b53bfbcb665..95f58ee5738 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PaymentSelectionScreen.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/PaymentSelectionScreen.tsx @@ -1,37 +1,26 @@ -import { IdCardIcon } from "@radix-ui/react-icons"; -import { useQuery } from "@tanstack/react-query"; +import { CardStackIcon } from "@radix-ui/react-icons"; import type { Chain } from "../../../../../../../chains/types.js"; -import { getCachedChain } from "../../../../../../../chains/utils.js"; import type { ThirdwebClient } from "../../../../../../../client/client.js"; -import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js"; import type { Wallet } from "../../../../../../../wallets/interfaces/wallet.js"; -import { - type GetWalletBalanceResult, - getWalletBalance, -} from "../../../../../../../wallets/utils/getWalletBalance.js"; +import type { GetWalletBalanceResult } from "../../../../../../../wallets/utils/getWalletBalance.js"; import type { WalletId } from "../../../../../../../wallets/wallet-types.js"; import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js"; import { iconSize, - radius, spacing, } from "../../../../../../core/design-system/index.js"; import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js"; -import { useChainMetadata } from "../../../../../../core/hooks/others/useChainQuery.js"; -import { useActiveAccount } from "../../../../../../core/hooks/wallets/useActiveAccount.js"; -import { useConnectedWallets } from "../../../../../../core/hooks/wallets/useConnectedWallets.js"; import type { SupportedTokens, TokenInfo, } from "../../../../../../core/utils/defaultTokens.js"; -import { LoadingScreen } from "../../../../../wallets/shared/LoadingScreen.js"; import { Spacer } from "../../../../components/Spacer.js"; +import { TextDivider } from "../../../../components/TextDivider.js"; import { Container } from "../../../../components/basic.js"; import { Button } from "../../../../components/buttons.js"; import { Text } from "../../../../components/text.js"; -import { OutlineWalletIcon } from "../../../icons/OutlineWalletIcon.js"; -import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js"; -import { WalletRowWithBalances } from "../WalletSelectorButton.js"; +import { TokenRow } from "../../../../components/token/TokenRow.js"; +import type { ERC20OrNativeToken } from "../../nativeToken.js"; export type TokenBalance = { balance: GetWalletBalanceResult; @@ -46,184 +35,36 @@ export function PaymentSelectionScreen(props: { sourceSupportedTokens: SupportedTokens | undefined; toChain: Chain; toToken: ERC20OrNativeToken; + fromToken: ERC20OrNativeToken; + fromChain: Chain; tokenAmount: string; wallets: Wallet[] | undefined; - onSelect: (wallet: Wallet, token: TokenInfo, chain: Chain) => void; + onContinue: () => void; onSelectFiat: () => void; onBack: () => void; onConnect: () => void; + onPickToken: () => void; hiddenWallets?: WalletId[]; payWithFiatEnabled: boolean; }) { const theme = useCustomTheme(); - const connectedWallets = useConnectedWallets(); - - // if all wallets are connected and showAll wallets is disabled, hide the connect button - const hideConnectButton = - !props.showAllWallets && - props.wallets?.every((w) => connectedWallets.includes(w)); - - const chainInfo = useChainMetadata(props.toChain); - const activeAccount = useActiveAccount(); - - const walletsAndBalances = useQuery({ - queryKey: [ - "wallets-and-balances", - connectedWallets.map((w) => w.getAccount()?.address), - props.sourceSupportedTokens, - props.toChain.id, - props.toToken, - props.tokenAmount, - props.mode, - activeAccount?.address, - ], - queryFn: async () => { - // in parallel, get the balances of all the wallets on each of the sourceSupportedTokens - const walletBalanceMap = new Map(); - - const balancePromises = connectedWallets.flatMap((wallet) => { - const account = wallet.getAccount(); - if (!account) return []; - walletBalanceMap.set(wallet, []); - - // inject the destination token too since it can be used as well to pay/transfer - const toToken = isNativeToken(props.toToken) - ? { - address: NATIVE_TOKEN_ADDRESS, - name: chainInfo.data?.nativeCurrency.name || "", - symbol: chainInfo.data?.nativeCurrency.symbol || "", - icon: chainInfo.data?.icon?.url, - } - : props.toToken; - - const tokens = { - ...props.sourceSupportedTokens, - [props.toChain.id]: [ - toToken, - ...(props.sourceSupportedTokens?.[props.toChain.id] || []), - ], - }; - - return Object.entries(tokens).flatMap(([chainId, tokens]) => { - return tokens.map(async (token) => { - try { - const chain = getCachedChain(Number(chainId)); - const balance = await getWalletBalance({ - address: account.address, - chain, - tokenAddress: isNativeToken(token) ? undefined : token.address, - client: props.client, - }); - - // show the token if: - // - its not the destination token and balance is greater than 0 - // - its the destination token and balance is greater than the token amount AND we the account is not the default account in fund_wallet mode - const shouldInclude = - token.address === toToken.address && - chain.id === props.toChain.id - ? props.mode === "fund_wallet" && - account.address === activeAccount?.address - ? false - : Number(balance.displayValue) > Number(props.tokenAmount) - : balance.value > 0n; - - if (shouldInclude) { - const existingBalances = walletBalanceMap.get(wallet) || []; - existingBalances.push({ balance, chain, token }); - existingBalances.sort((a, b) => { - if ( - a.chain.id === props.toChain.id && - a.token.address === toToken.address - ) - return -1; - if ( - b.chain.id === props.toChain.id && - b.token.address === toToken.address - ) - return 1; - if (a.chain.id === props.toChain.id) return -1; - if (b.chain.id === props.toChain.id) return 1; - return a.chain.id > b.chain.id ? 1 : -1; - }); - } - } catch (error) { - console.error( - `Failed to fetch balance for wallet ${wallet.id} on chain ${chainId} for token ${token.symbol}:`, - error, - ); - } - }); - }); - }); - - await Promise.all(balancePromises); - return walletBalanceMap; - }, - enabled: !!props.sourceSupportedTokens && !!chainInfo.data, - }); - - if (walletsAndBalances.isLoading || !walletsAndBalances.data) { - return ; - } - - const filteredWallets = Array.from(walletsAndBalances.data?.entries() || []) - .filter(([w]) => !props.hiddenWallets?.includes(w.id)) - .filter(([, balances]) => { - const hasEnoughBalance = balances.some((b) => b.balance.value > 0); - return hasEnoughBalance; - }); return ( - - {filteredWallets.length === 0 && ( - - - Insufficient funds in connected wallets - - - )} - {filteredWallets.map(([w, balances]) => { - const address = w.getAccount()?.address; - if (!address) return null; - return ( - - ); - })} - {!hideConnectButton && ( - - )} + + props.onPickToken()} + token={props.fromToken} + chain={props.fromChain} + /> + + + + + {props.payWithFiatEnabled && ( + + + + ); +} + +function WalletRowWithBalances(props: { + client: ThirdwebClient; + address: string; + wallet: Wallet; + balances: TokenBalance[]; + onClick: (wallet: Wallet, token: TokenInfo, chain: Chain) => void; + hideConnectButton?: boolean; +}) { + const displayedBalances = props.balances; + + return ( + + + + + + {props.balances.length > 0 ? ( + displayedBalances.map((b) => ( + props.onClick(props.wallet, b.token, b.chain)} + key={`${b.token.address}-${b.chain.id}`} + tokenBalance={b} + wallet={props.wallet} + /> + )) + ) : ( + + + Not enough funds + + + )} + + + ); +} + +function TokenBalanceRow(props: { + client: ThirdwebClient; + tokenBalance: TokenBalance; + wallet: Wallet; + onClick: (token: TokenInfo, wallet: Wallet) => void; +}) { + const { tokenBalance, wallet, onClick, client } = props; + const chainInfo = useChainName(tokenBalance.chain); + return ( + onClick(tokenBalance.token, wallet)} + variant="secondary" + > + + + + + {tokenBalance.token.symbol} + + {chainInfo && {chainInfo.name}} + + + + + + {formatTokenBalance(tokenBalance.balance, true, 2)} + + + + {/* */} + + + ); +} + +export function WalletRow(props: { + client: ThirdwebClient; + address: string; + iconSize?: keyof typeof iconSize; + textSize?: keyof typeof fontSize; + walletId?: WalletId; + wallet?: Wallet; +}) { + const { client, address } = props; + const walletId = props.walletId; + const theme = useCustomTheme(); + const ensNameQuery = useEnsName({ + client, + address, + }); + const addressOrENS = ensNameQuery.data || shortenAddress(address); + const ensAvatarQuery = useEnsAvatar({ + client, + ensName: ensNameQuery.data, + }); + return ( + + + {ensAvatarQuery.data ? ( + + ) : walletId ? ( + + ) : ( + + + + )} + + + {addressOrENS || shortenAddress(props.address)} + + + + ); +} + +const StyledButton = /* @__PURE__ */ styled(Button)((_) => { + const theme = useCustomTheme(); + return { + background: theme.colors.tertiaryBg, + justifyContent: "space-between", + flexDirection: "row", + padding: spacing.sm, + border: `1px solid ${theme.colors.borderColor}`, + gap: spacing.sm, + "&:hover": { + background: theme.colors.secondaryButtonBg, + transform: "scale(1.01)", + }, + transition: "background 200ms ease, transform 150ms ease", + }; +}); diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx index e2d1efb7c6f..f54095afb59 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx @@ -26,10 +26,10 @@ import { Button } from "../../../../components/buttons.js"; import { Text } from "../../../../components/text.js"; import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js"; import { Step } from "../Stepper.js"; -import { WalletRow } from "../WalletSelectorButton.js"; import { TokenInfoRow } from "../pay-transactions/TokenInfoRow.js"; import type { PayerInfo } from "../types.js"; import { ConnectorLine } from "./ConfirmationScreen.js"; +import { WalletRow } from "./TokenSelectorScreen.js"; type TransferConfirmationScreenProps = { title: string; diff --git a/packages/thirdweb/src/react/web/ui/components/buttons.tsx b/packages/thirdweb/src/react/web/ui/components/buttons.tsx index 21723261503..425c7c899d4 100644 --- a/packages/thirdweb/src/react/web/ui/components/buttons.tsx +++ b/packages/thirdweb/src/react/web/ui/components/buttons.tsx @@ -93,6 +93,7 @@ export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => { border: `1px solid ${theme.colors.borderColor}`, "&:hover": { borderColor: theme.colors.accentText, + transform: "scale(1.01)", }, '&[aria-selected="true"]': { borderColor: theme.colors.accentText, @@ -105,6 +106,15 @@ export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => { border: "1px solid transparent", "&:hover": { borderColor: theme.colors.accentText, + transform: "scale(1.01)", + }, + }; + } + + if (props.variant === "accent") { + return { + "&:hover": { + transform: "scale(1.01)", }, }; } @@ -113,6 +123,7 @@ export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => { return { "&:hover": { background: theme.colors.secondaryButtonHoverBg, + transform: "scale(1.01)", }, }; } @@ -122,6 +133,7 @@ export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => { padding: 0, "&:hover": { color: theme.colors.primaryText, + transform: "scale(1.01)", }, }; } diff --git a/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx b/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx new file mode 100644 index 00000000000..d9ec116e61b --- /dev/null +++ b/packages/thirdweb/src/react/web/ui/components/token/TokenRow.tsx @@ -0,0 +1,87 @@ +import styled from "@emotion/styled"; +import { ChevronDownIcon } from "@radix-ui/react-icons"; +import type { Chain } from "../../../../../chains/types.js"; +import type { ThirdwebClient } from "../../../../../client/client.js"; +import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js"; +import { + fontSize, + iconSize, + spacing, +} from "../../../../core/design-system/index.js"; +import { useChainName } from "../../../../core/hooks/others/useChainQuery.js"; +import type { ERC20OrNativeToken } from "../../ConnectWallet/screens/nativeToken.js"; +import { Skeleton } from "../Skeleton.js"; +import { TokenIcon } from "../TokenIcon.js"; +import { Container } from "../basic.js"; +import { Button } from "../buttons.js"; +import { Text } from "../text.js"; +import { TokenSymbol } from "./TokenSymbol.js"; + +export function TokenRow(props: { + token: ERC20OrNativeToken; + chain: Chain; + client: ThirdwebClient; + onSelectToken: () => void; + freezeChainAndToken?: boolean; +}) { + const { name } = useChainName(props.chain); + return ( + + + + + + {/* Token Symbol */} + + + {/* Network Name */} + {name ? ( + + {name} + + ) : ( + + )} + + + + + + ); +} + +const TokenButton = /* @__PURE__ */ styled(Button)(() => { + const theme = useCustomTheme(); + return { + background: theme.colors.tertiaryBg, + border: `1px solid ${theme.colors.borderColor}`, + justifyContent: "flex-start", + transition: "background 0.3s", + padding: spacing.sm, + }; +});