import styled from "@emotion/styled"; import { ChevronDownIcon, CrossCircledIcon } from "@radix-ui/react-icons"; import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; import type { Chain } from "../../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../../client/client.js"; import type { Account } from "../../../../../wallets/interfaces/wallet.js"; import { getTokenBalance } from "../../../../../wallets/utils/getTokenBalance.js"; import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js"; import { fontSize, iconSize, spacing, } from "../../../../core/design-system/index.js"; import { useChainIconUrl, useChainName, } from "../../../../core/hooks/others/useChainQuery.js"; import { useTokenInfo } from "../../../../core/hooks/others/useTokenInfo.js"; import { useActiveAccount } from "../../../../core/hooks/wallets/useActiveAccount.js"; import type { TokenInfo } from "../../../../core/utils/defaultTokens.js"; import { Container, Line, ModalHeader } from "../../components/basic.js"; import { Button } from "../../components/buttons.js"; import { ChainIcon } from "../../components/ChainIcon.js"; import { Input } from "../../components/formElements.js"; import { Skeleton } from "../../components/Skeleton.js"; import { Spacer } from "../../components/Spacer.js"; import { Spinner } from "../../components/Spinner.js"; import { TokenIcon } from "../../components/TokenIcon.js"; import { Text } from "../../components/text.js"; import type { ConnectLocale } from "../locale/types.js"; import { ChainButton, NetworkSelectorContent } from "../NetworkSelector.js"; import { formatTokenBalance } from "./formatTokenBalance.js"; import { type ERC20OrNativeToken, isNativeToken, NATIVE_TOKEN, } from "./nativeToken.js"; // Note: TokenSelector can be used when wallet may or may not be connected /** * * @internal */ export function TokenSelector(props: { onTokenSelect: (token: ERC20OrNativeToken) => void; onBack: () => void; tokenList: TokenInfo[]; chain: Chain; chainSelection?: { chains: Chain[]; select: (chain: Chain) => void; }; connectLocale: ConnectLocale; client: ThirdwebClient; modalTitle?: string; }) { const [screen, setScreen] = useState<"base" | "select-chain">("base"); const [input, setInput] = useState(""); const chain = props.chain; const chainNameQuery = useChainName(chain); const chainIconQuery = useChainIconUrl(chain); // if input is undefined, it loads the native token // otherwise it loads the token with given address const tokenQuery = useTokenInfo({ chain: chain, client: props.client, tokenAddress: input, }); const locale = props.connectLocale.sendFundsScreen; let tokenList = props.tokenList; if (tokenQuery.data && input) { tokenList = [ { ...tokenQuery.data, address: input, }, ...tokenList, ]; } const filteredList = input ? tokenList.filter((t) => { const inputStr = input.toLowerCase(); return ( t.name.toLowerCase().includes(inputStr) || t.symbol.toLowerCase().includes(inputStr) || t.address.includes(input) ); }) : tokenList; const { chainSelection } = props; if (screen === "select-chain" && chainSelection) { return ( setScreen("base")} connectLocale={props.connectLocale} // pass swap supported chains networkSelector={{ renderChain(renderChainProps) { return ( { chainSelection.select(renderChainProps.chain); setScreen("base"); }} switchingFailed={false} /> ); }, }} onBack={() => setScreen("base")} showTabs={false} /> ); } return ( {props.chainSelection && ( Select Network { setScreen("select-chain"); }} variant="secondary" > {chainNameQuery.name ? ( {chainNameQuery.name} ) : ( )} Select Token )} { setInput(e.target.value); }} placeholder={locale.searchToken} value={input} variant="outline" /> {(filteredList.length > 0 || !input) && ( {!input && ( { props.onTokenSelect(NATIVE_TOKEN); }} token={NATIVE_TOKEN} /> )} {filteredList.map((token) => { return ( props.onTokenSelect(token)} token={token} /> ); })} )} {filteredList.length === 0 && tokenQuery.isLoading && input && ( )} {filteredList.length === 0 && !tokenQuery.isLoading && input && ( {locale.noTokensFound} )} ); } function SelectTokenButton(props: { token: ERC20OrNativeToken; chain: Chain; onClick: () => void; client: ThirdwebClient; }) { const account = useActiveAccount(); const tokenInfoQuery = useTokenInfo({ chain: props.chain, client: props.client, tokenAddress: isNativeToken(props.token) ? undefined : props.token.address, }); const tokenName = isNativeToken(props.token) ? tokenInfoQuery.data?.name : props.token.name; return ( {tokenName ? ( {tokenName} ) : ( )} {account && ( )} ); } function TokenBalance(props: { account: Account; chain: Chain; client: ThirdwebClient; tokenAddress?: string; }) { const tokenBalanceQuery = useQuery({ queryFn: async () => { return getTokenBalance({ account: props.account, chain: props.chain, client: props.client, tokenAddress: props.tokenAddress, }); }, queryKey: ["tokenBalance", props], }); if (tokenBalanceQuery.data) { return {formatTokenBalance(tokenBalanceQuery.data)}; } return ; } const SelectTokenBtn = /* @__PURE__ */ styled(Button)(() => { const theme = useCustomTheme(); return { "&:hover": { background: theme.colors.secondaryButtonBg, transform: "scale(1.01)", }, background: theme.colors.tertiaryBg, gap: spacing.sm, justifyContent: "flex-start", padding: spacing.sm, transition: "background 200ms ease, transform 150ms ease", }; });