diff --git a/src/components/Modals/SelectModals/SelectTokenModal/SelectTokenModal.tsx b/src/components/Modals/SelectModals/SelectTokenModal/SelectTokenModal.tsx index a5b44a7e..0a409930 100644 --- a/src/components/Modals/SelectModals/SelectTokenModal/SelectTokenModal.tsx +++ b/src/components/Modals/SelectModals/SelectTokenModal/SelectTokenModal.tsx @@ -16,7 +16,7 @@ import { Typography, useMediaQuery } from '@mui/material' -import { formatNumberWithSuffix, printBN } from '@utils/utils' +import { formatNumberWithSuffix, getTokenPrice, printBN } from '@utils/utils' import { SwapToken } from '@store/selectors/solanaWallet' import Scrollbars from 'rc-scrollbars' import icons from '@static/icons' @@ -48,6 +48,7 @@ interface RowItemData { isXs: boolean networkUrl: string classes: ReturnType['classes'] + prices: Record } export interface IScroll { @@ -69,9 +70,11 @@ const CustomScrollbarsVirtualList = React.forwardRef const RowItem: React.FC> = React.memo( ({ index, style, data }) => { - const { tokens, onSelect, hideBalances, isXs, networkUrl, classes } = data + const { tokens, onSelect, hideBalances, isXs, networkUrl, classes, prices } = data const token = tokens[index] const tokenBalance = printBN(token.balance, token.decimals) + const price = prices[token.assetAddress.toString()] + const usdBalance = price ? Number(tokenBalance) * price : 0 return ( > = React.memo( }}> + {token.isUnknown && } - {token.symbol ? token.symbol : 'Unknown'} + {token.symbol ? token.symbol : 'Unknown'}{' '} > = React.memo( event.stopPropagation() }}> - {token.assetAddress.toString().slice(0, 4) + + {token.assetAddress.toString().slice(0, isXs ? 3 : 4) + '...' + - token.assetAddress.toString().slice(-5, -1)} + token.assetAddress.toString().slice(isXs ? -4 : -5, -1)} {'Token + {token.name ? token.name.slice(0, isXs ? 20 : 30) : 'Unknown'} {token.name.length > (isXs ? 20 : 30) ? '...' : ''} - + {!hideBalances && Number(tokenBalance) > 0 ? ( <> - Balance: -   {formatNumberWithSuffix(tokenBalance)} + + {formatNumberWithSuffix(tokenBalance)} + + + ${usdBalance.toFixed(2)} + ) : null} @@ -152,10 +157,10 @@ export const SelectTokenModal: React.FC = memo( }) => { const { classes } = useStyles() const isXs = useMediaQuery(theme.breakpoints.down('sm')) - const [value, setValue] = useState('') const [isAddOpen, setIsAddOpen] = useState(false) const [hideUnknown, setHideUnknown] = useState(initialHideUnknownTokensValue) + const [prices, setPrices] = useState>({}) const outerRef = useRef(null) const inputRef = useRef(null) @@ -179,6 +184,21 @@ export const SelectTokenModal: React.FC = memo( ) }, [tokens]) + useEffect(() => { + tokensWithIndexes.forEach(token => { + const balanceStr = printBN(token.balance, token.decimals) + const balance = Number(balanceStr) + if (balance > 0) { + const addr = token.assetAddress.toString() + if (prices[addr] === undefined) { + getTokenPrice(addr).then(price => { + setPrices(prev => ({ ...prev, [addr]: price || 0 })) + }) + } + } + }) + }, [tokensWithIndexes]) + const commonTokensList = useMemo( () => tokensWithIndexes.filter( @@ -188,42 +208,47 @@ export const SelectTokenModal: React.FC = memo( ) const filteredTokens = useMemo(() => { - const list = tokensWithIndexes.filter(token => { - return ( + const list = tokensWithIndexes.filter( + token => token.symbol.toLowerCase().includes(value.toLowerCase()) || token.name.toLowerCase().includes(value.toLowerCase()) || token.strAddress.includes(value) - ) + ) + + const tokensWithPrice = list.filter(token => { + const price = prices[token.assetAddress.toString()] + return price !== undefined && price > 0 }) - const sorted = list.sort((a, b) => { - const aBalance = +printBN(a.balance, a.decimals) - const bBalance = +printBN(b.balance, b.decimals) - if ((aBalance === 0 && bBalance === 0) || (aBalance > 0 && bBalance > 0)) { - if (value.length) { - if ( - a.symbol.toLowerCase().startsWith(value.toLowerCase()) && - !b.symbol.toLowerCase().startsWith(value.toLowerCase()) - ) { - return -1 - } + const tokensNoPrice = list.filter(token => { + const price = prices[token.assetAddress.toString()] + return price === undefined || price === 0 + }) - if ( - b.symbol.toLowerCase().startsWith(value.toLowerCase()) && - !a.symbol.toLowerCase().startsWith(value.toLowerCase()) - ) { - return 1 - } - } + tokensWithPrice.sort((a, b) => { + const aNative = +printBN(a.balance, a.decimals) + const bNative = +printBN(b.balance, b.decimals) + const aPrice = prices[a.assetAddress.toString()]! + const bPrice = prices[b.assetAddress.toString()]! + const aUSD = aNative * aPrice + const bUSD = bNative * bPrice - return a.symbol.toLowerCase().localeCompare(b.symbol.toLowerCase()) - } + if (aUSD !== bUSD) return bUSD - aUSD + if (aNative !== bNative) return bNative - aNative + return a.symbol.toLowerCase().localeCompare(b.symbol.toLowerCase()) + }) - return aBalance === 0 ? 1 : -1 + tokensNoPrice.sort((a, b) => { + const aNative = +printBN(a.balance, a.decimals) + const bNative = +printBN(b.balance, b.decimals) + if (aNative !== bNative) return bNative - aNative + return a.symbol.toLowerCase().localeCompare(b.symbol.toLowerCase()) }) + const sorted = [...tokensWithPrice, ...tokensNoPrice] + return hideUnknown ? sorted.filter(token => !token.isUnknown) : sorted - }, [value, tokens, hideUnknown, open]) + }, [value, tokensWithIndexes, hideUnknown, prices]) const searchToken = (e: React.ChangeEvent) => { setValue(e.target.value) @@ -377,7 +402,8 @@ export const SelectTokenModal: React.FC = memo( hideBalances, isXs, networkUrl, - classes + classes, + prices }}> {RowItem} diff --git a/src/components/Modals/SelectModals/SelectTokenModal/style.ts b/src/components/Modals/SelectModals/SelectTokenModal/style.ts index ba7fdeb8..137f962f 100644 --- a/src/components/Modals/SelectModals/SelectTokenModal/style.ts +++ b/src/components/Modals/SelectModals/SelectTokenModal/style.ts @@ -216,12 +216,14 @@ const useStyles = makeStyles()((theme: Theme) => { }, tokenIcon: { - minWidth: 30, - maxWidth: 30, - height: 30, - marginRight: 16, + minWidth: 36, + maxWidth: 36, + height: 36, + marginRight: 12, borderRadius: '50%', - boxShadow: '0px 0px 10px rgba(216, 255, 181, 0.5)' + [theme.breakpoints.up('sm')]: { + boxShadow: '0px 0px 10px rgba(216, 255, 181, 0.5)' + } }, tokenBalance: { ...typography.body2, diff --git a/src/components/Modals/SelectModals/style.ts b/src/components/Modals/SelectModals/style.ts index 4c71a4d0..78273df4 100644 --- a/src/components/Modals/SelectModals/style.ts +++ b/src/components/Modals/SelectModals/style.ts @@ -21,7 +21,7 @@ const useStyles = makeStyles()((theme: Theme) => { width: 500, [theme.breakpoints.down('sm')]: { maxWidth: '100vw', - padding: '20px 16px' + padding: '20px 12px' }, '.MuiFormControlLabel-label': { color: colors.invariant.lightGrey, @@ -144,7 +144,9 @@ const useStyles = makeStyles()((theme: Theme) => { tokenContainer: { display: 'flex', flexDirection: 'column', - minWidth: 'min-content' + minWidth: 'min-content', + flexGrow: 1, + overflow: 'hidden' }, tokenItem: { @@ -153,7 +155,12 @@ const useStyles = makeStyles()((theme: Theme) => { marginBottom: 4, borderRadius: 24, cursor: 'pointer', - padding: '0 16px ', + padding: '0 12px', + position: 'relative', + overflow: 'hidden', + [theme.breakpoints.down('sm')]: { + padding: 0 + }, '& > p': { whiteSpace: 'nowrap' @@ -166,7 +173,10 @@ const useStyles = makeStyles()((theme: Theme) => { }, tokenName: { color: colors.white.main, - ...typography.heading4 + ...typography.heading3, + [theme.breakpoints.down('sm')]: { + ...typography.heading4 + } }, tokenAddress: { backgroundColor: colors.invariant.newDark, @@ -174,11 +184,15 @@ const useStyles = makeStyles()((theme: Theme) => { padding: '2px 4px', width: 'min-content', height: 'min-content', + whiteSpace: 'nowrap', '& a': { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '12px', + [theme.breakpoints.down('sm')]: { + gap: '6px' + }, textDecoration: 'none', '&:hover': { @@ -194,35 +208,46 @@ const useStyles = makeStyles()((theme: Theme) => { } } }, + tokenDescrpiption: { color: colors.invariant.textGrey, + opacity: 0.5, ...typography.caption2, lineHeight: '16px', whiteSpace: 'nowrap' }, tokenBalanceStatus: { - color: colors.invariant.textGrey, + color: colors.invariant.text, maxHeight: 40, - '& p': { - ...typography.body2 - }, + opacity: 0.85, + ...typography.heading4, + whiteSpace: 'nowrap' + }, - '& p:last-child': { - color: colors.invariant.text - } + tokenBalanceUSDStatus: { + color: colors.invariant.textGrey, + opacity: 0.85, + maxHeight: 40, + whiteSpace: 'nowrap', + ...typography.body2 }, imageContainer: { - minWidth: 30, - maxWidth: 30, - height: 30, + minWidth: 36, + maxWidth: 36, + height: 36, + width: 36, marginRight: 16, + [theme.breakpoints.down('sm')]: { + marginRight: 12 + }, position: 'relative' }, tokenIcon: { - minWidth: 30, - maxWidth: 30, - height: 30, + minWidth: 36, + maxWidth: 36, + height: 36, + width: 36, marginRight: 16, borderRadius: '50%', boxShadow: '0px 0px 10px rgba(216, 255, 181, 0.5)' @@ -237,7 +262,7 @@ const useStyles = makeStyles()((theme: Theme) => { tokenBalance: { ...typography.body2, color: colors.invariant.textGrey, - whiteSpace: 'nowrap' + overflow: 'visible' }, searchIcon: { color: colors.invariant.light