Commit 52bcc45d authored by tom's avatar tom

sort ERC-20 tokens

parent 25ddae54
......@@ -2,17 +2,16 @@ import { Flex, Text, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import HashStringShorten from 'ui/shared/HashStringShorten';
import TokenLogo from 'ui/shared/TokenLogo';
import type { EnhancedData } from './utils';
interface Props {
data: AddressTokenBalance;
usd?: string;
data: EnhancedData;
}
const TokenItem = ({ data, usd }: Props) => {
const TokenItem = ({ data }: Props) => {
const secondRow = (() => {
switch (data.token.type) {
......@@ -20,7 +19,7 @@ const TokenItem = ({ data, usd }: Props) => {
const tokenDecimals = Number(data.token.decimals) || 18;
return (
<>
<Text >{ BigNumber(data.value).dividedBy(10 ** tokenDecimals).dp(2).toFormat() } { data.token.symbol }</Text>
<Text >{ BigNumber(data.value).dividedBy(10 ** tokenDecimals).toFormat(2) } { data.token.symbol }</Text>
{ data.token.exchange_rate && <Text >@{ data.token.exchange_rate }</Text> }
</>
);
......@@ -57,7 +56,7 @@ const TokenItem = ({ data, usd }: Props) => {
<Flex alignItems="center" w="100%">
<TokenLogo hash={ data.token.address } name={ data.token.name } boxSize={ 6 }/>
<Text fontWeight={ 700 } ml={ 2 }>{ data.token.name || <HashStringShorten hash={ data.token.address }/> }</Text>
{ usd && <Text fontWeight={ 700 } ml="auto">${ usd }</Text> }
{ data.usd && <Text fontWeight={ 700 } ml="auto">${ data.usd.toFormat(2) }</Text> }
</Flex>
<Flex alignItems="center" justifyContent="space-between" w="100%">
{ secondRow }
......
......@@ -18,51 +18,17 @@ import searchIcon from 'icons/search.svg';
import TokenItem from './TokenItem';
import TokensButton from './TokensButton';
type Sort = 'desc' | 'asc';
const SORTABLE_TOKENS: Array<TokenType> = [ 'ERC-20', 'ERC-1155' ];
const TOKEN_GROUPS_ORDER: Array<TokenType> = [ 'ERC-20', 'ERC-721', 'ERC-1155' ];
type TokenGroup = [string, Array<AddressTokenBalance>];
const sortTokenGroups = (groupA: TokenGroup, groupB: TokenGroup) => {
return TOKEN_GROUPS_ORDER.indexOf(groupA[0] as TokenType) > TOKEN_GROUPS_ORDER.indexOf(groupB[0] as TokenType) ? 1 : -1;
};
const sortErc1155Tokens = (sort: 'desc' | 'asc') => (dataA: AddressTokenBalance, dataB: AddressTokenBalance) => {
if (dataA.value === dataB.value) {
return 0;
}
if (sort === 'desc') {
return Number(dataA.value) > Number(dataB.value) ? -1 : 1;
}
return Number(dataA.value) > Number(dataB.value) ? 1 : -1;
};
const sortErc20Tokens = () => () => 0;
const sortErc721Tokens = () => () => 0;
const sortingFns = {
'ERC-20': sortErc20Tokens,
'ERC-721': sortErc721Tokens,
'ERC-1155': sortErc1155Tokens,
};
const filterTokens = (searchTerm: string) => ({ token }: AddressTokenBalance) => {
if (!token.name) {
return !searchTerm ? true : token.address.toLowerCase().includes(searchTerm);
}
return token.name?.toLowerCase().includes(searchTerm);
};
import type { Sort } from './utils';
import { SORTABLE_TOKENS, sortTokenGroups, sortingFns, calculateUsdValue, filterTokens } from './utils';
interface Props {
data: Array<AddressTokenBalance>;
}
const AddressTokenSelect = ({ data }: Props) => {
const Tokens = ({ data }: Props) => {
const [ searchTerm, setSearchTerm ] = React.useState('');
const [ erc1155sort, setErc1155Sort ] = React.useState<Sort>('desc');
const [ erc20sort, setErc20Sort ] = React.useState<Sort>('desc');
const { isOpen, onToggle, onClose } = useDisclosure();
const handleInputChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
......@@ -74,19 +40,22 @@ const AddressTokenSelect = ({ data }: Props) => {
if (tokenType === 'ERC-1155') {
setErc1155Sort((prevValue) => prevValue === 'desc' ? 'asc' : 'desc');
}
if (tokenType === 'ERC-20') {
setErc20Sort((prevValue) => prevValue === 'desc' ? 'asc' : 'desc');
}
}, []);
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
const bgColor = useColorModeValue('white', 'gray.900');
const filteredData = data.filter(filterTokens(searchTerm.toLowerCase()));
const groupedData = _groupBy(filteredData, 'token.type');
const modifiedData = data.filter(filterTokens(searchTerm.toLowerCase())).map(calculateUsdValue);
const groupedData = _groupBy(modifiedData, 'token.type');
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<TokensButton isOpen={ isOpen } onClick={ onToggle } data={ data }/>
<TokensButton isOpen={ isOpen } onClick={ onToggle } data={ modifiedData }/>
</PopoverTrigger>
<PopoverContent w="355px" maxH="450px" overflowY="scroll">
<PopoverBody px={ 4 } py={ 6 } bgColor={ bgColor } boxShadow="2xl" >
......@@ -105,12 +74,15 @@ const AddressTokenSelect = ({ data }: Props) => {
<Flex flexDir="column" rowGap={ 6 }>
{ Object.entries(groupedData).sort(sortTokenGroups).map(([ tokenType, tokenInfo ]) => {
const type = tokenType as TokenType;
const arrowTransform = type === 'ERC-1155' && erc1155sort === 'desc' ? 'rotate(90deg)' : 'rotate(-90deg)';
const arrowTransform = (type === 'ERC-1155' && erc1155sort === 'desc') || (type === 'ERC-20' && erc20sort === 'desc') ?
'rotate(90deg)' :
'rotate(-90deg)';
const sortDirection: Sort = (() => {
switch (type) {
case 'ERC-1155':
return erc1155sort;
case 'ERC-20':
return erc20sort;
default:
return 'desc';
}
......@@ -126,16 +98,16 @@ const AddressTokenSelect = ({ data }: Props) => {
</Link>
) }
</Flex>
{ tokenInfo.sort(sortingFns[type](sortDirection)).map((data) => <TokenItem key={ data.token.address } data={ data }/>) }
{ tokenInfo.sort(sortingFns[type](sortDirection)).map((data) => <TokenItem key={ data.token.address + data.token_id } data={ data }/>) }
</Box>
);
}) }
</Flex>
{ filteredData.length === 0 && searchTerm && <Text fontSize="sm">Could not find any matches.</Text> }
{ modifiedData.length === 0 && searchTerm && <Text fontSize="sm">Could not find any matches.</Text> }
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default React.memo(AddressTokenSelect);
export default React.memo(Tokens);
......@@ -2,26 +2,20 @@ import { Button, Icon, Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import arrowIcon from 'icons/arrows/east-mini.svg';
import tokensIcon from 'icons/tokens.svg';
import { ZERO } from 'lib/consts';
import type { EnhancedData } from './utils';
interface Props {
isOpen: boolean;
onClick: () => void;
data: Array<AddressTokenBalance>;
data: Array<EnhancedData>;
}
const TokensButton = ({ isOpen, onClick, data }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const usdBn = data.reduce((result, item) => {
if (!item.token.exchange_rate) {
return result;
}
const decimals = Number(item.token.decimals || '18');
return BigNumber(item.value).div(BigNumber(10 ** decimals)).multipliedBy(BigNumber(item.token.exchange_rate));
}, ZERO);
const totalBn = data.reduce((result, item) => !item.usd ? result : result.plus(BigNumber(item.usd)), ZERO);
return (
<Button
......@@ -33,7 +27,7 @@ const TokensButton = ({ isOpen, onClick, data }: Props, ref: React.ForwardedRef<
>
<Icon as={ tokensIcon } boxSize={ 4 } mr={ 2 }/>
<Text fontWeight={ 600 }>{ data.length }</Text>
<Text whiteSpace="pre" variant="secondary" fontWeight={ 400 }> (${ usdBn.toFixed(2) })</Text>
<Text whiteSpace="pre" variant="secondary" fontWeight={ 400 }> (${ totalBn.toFormat(2) })</Text>
<Icon as={ arrowIcon } transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="normal" boxSize={ 5 } ml={ 3 }/>
</Button>
);
......
import BigNumber from 'bignumber.js';
import type { AddressTokenBalance } from 'types/api/address';
import type { TokenType } from 'types/api/tokenInfo';
export type EnhancedData = AddressTokenBalance & {
usd?: BigNumber ;
}
export type Sort = 'desc' | 'asc';
export const SORTABLE_TOKENS: Array<TokenType> = [ 'ERC-20', 'ERC-1155' ];
const TOKEN_GROUPS_ORDER: Array<TokenType> = [ 'ERC-20', 'ERC-721', 'ERC-1155' ];
type TokenGroup = [string, Array<AddressTokenBalance>];
export const sortTokenGroups = (groupA: TokenGroup, groupB: TokenGroup) => {
return TOKEN_GROUPS_ORDER.indexOf(groupA[0] as TokenType) > TOKEN_GROUPS_ORDER.indexOf(groupB[0] as TokenType) ? 1 : -1;
};
const sortErc1155Tokens = (sort: Sort) => (dataA: AddressTokenBalance, dataB: AddressTokenBalance) => {
if (dataA.value === dataB.value) {
return 0;
}
if (sort === 'desc') {
return Number(dataA.value) > Number(dataB.value) ? -1 : 1;
}
return Number(dataA.value) > Number(dataB.value) ? 1 : -1;
};
const sortErc20Tokens = (sort: Sort) => (dataA: EnhancedData, dataB: EnhancedData) => {
// keep tokens without usd value in the end of the group
if (!dataB.usd) {
return -1;
}
if (!dataA.usd) {
return 0;
}
if (sort === 'desc') {
return dataA.usd.gt(dataB.usd) ? -1 : 1;
}
return dataA.usd.gt(dataB.usd) ? 1 : -1;
};
const sortErc721Tokens = () => () => 0;
export const sortingFns = {
'ERC-20': sortErc20Tokens,
'ERC-721': sortErc721Tokens,
'ERC-1155': sortErc1155Tokens,
};
export const filterTokens = (searchTerm: string) => ({ token }: AddressTokenBalance) => {
if (!token.name) {
return !searchTerm ? true : token.address.toLowerCase().includes(searchTerm);
}
return token.name?.toLowerCase().includes(searchTerm);
};
export const calculateUsdValue = (data: AddressTokenBalance): EnhancedData => {
if (data.token.type !== 'ERC-20') {
return data;
}
const exchangeRate = data.token.exchange_rate;
if (!exchangeRate) {
return data;
}
const decimals = Number(data.token.decimals || '18');
return {
...data,
usd: BigNumber(data.value).div(BigNumber(10 ** decimals)).multipliedBy(BigNumber(exchangeRate)),
};
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment