From 197fab4e4dc4693f0147bcf1f3d5fad274076bff Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Tue, 27 Aug 2024 17:32:05 +0300 Subject: [PATCH 01/10] base structue for the account tokens table --- src/assets/scss/components/_pages.scss | 1 + src/assets/scss/elements/_badges.scss | 6 + src/components/Filters/TableSearch.tsx | 3 +- .../FormatDisplayValue/FormatDisplayValue.tsx | 17 +- .../FormatValue/FormatNumber/FormatNumber.tsx | 7 +- .../AccountTokensTable/AccountTokensTable.tsx | 225 ++++++++++++++++++ .../accountTokensTable.styles.scss | 14 ++ .../components/AccountTokensTableHeader.tsx | 99 ++++++++ .../AccountTokensTable/components/index.ts | 1 + .../AccountTokensTable/index.ts | 1 + src/routes/layouts/accountLayout.ts | 4 +- 11 files changed, 364 insertions(+), 14 deletions(-) create mode 100644 src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx create mode 100644 src/pages/AccountDetails/AccountTokensTable/accountTokensTable.styles.scss create mode 100644 src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx create mode 100644 src/pages/AccountDetails/AccountTokensTable/components/index.ts create mode 100644 src/pages/AccountDetails/AccountTokensTable/index.ts diff --git a/src/assets/scss/components/_pages.scss b/src/assets/scss/components/_pages.scss index 25af80445..f71135710 100644 --- a/src/assets/scss/components/_pages.scss +++ b/src/assets/scss/components/_pages.scss @@ -1,4 +1,5 @@ @import '../../../pages/AccountDetails/AccountStaking/accountStaking.styles.scss'; +@import '../../../pages/AccountDetails/AccountTokensTable/accountTokensTable.styles.scss'; @import '../../../pages/Analytics/analytics.styles.scss'; @import '../../../pages/BlockDetails/blockDetails.styles.scss'; @import '../../../pages/Identities/identities.styles.scss'; diff --git a/src/assets/scss/elements/_badges.scss b/src/assets/scss/elements/_badges.scss index 86e556808..870696a31 100644 --- a/src/assets/scss/elements/_badges.scss +++ b/src/assets/scss/elements/_badges.scss @@ -144,6 +144,12 @@ button.badge { rgba(215, 221, 232, 0.3) ); } + &.active { + @extend .badge-grey; + &:before { + display: none; + } + } } &-primary { color: var(--primary); diff --git a/src/components/Filters/TableSearch.tsx b/src/components/Filters/TableSearch.tsx index 3440bbe38..e76da7556 100644 --- a/src/components/Filters/TableSearch.tsx +++ b/src/components/Filters/TableSearch.tsx @@ -61,7 +61,8 @@ export const TableSearch = ({
diff --git a/src/components/FormatValue/FormatDisplayValue/FormatDisplayValue.tsx b/src/components/FormatValue/FormatDisplayValue/FormatDisplayValue.tsx index 4fe5343dd..5f3315bfb 100644 --- a/src/components/FormatValue/FormatDisplayValue/FormatDisplayValue.tsx +++ b/src/components/FormatValue/FormatDisplayValue/FormatDisplayValue.tsx @@ -11,6 +11,7 @@ export interface FormatDisplayValueUIType symbol?: React.ReactNode; label?: React.ReactNode; details?: React.ReactNode; + hideLessThanOne?: boolean; showTooltipSymbol?: boolean; showTooltipLabel?: boolean; spacedLabel?: boolean; @@ -26,14 +27,15 @@ export const FormatDisplayValue = (props: FormatDisplayValueUIType) => { egldLabel, details, digits = DIGITS, - showLastNonZeroDecimal = false, + showLastNonZeroDecimal, + hideLessThanOne, showLabel = true, showTooltip = true, - showSymbol = false, - superSuffix = false, - showTooltipSymbol = false, - showTooltipLabel = false, - spacedLabel = false, + showSymbol, + superSuffix, + showTooltipSymbol, + showTooltipLabel, + spacedLabel, decimalOpacity = true, className } = props; @@ -43,6 +45,9 @@ export const FormatDisplayValue = (props: FormatDisplayValueUIType) => { const displayLabel = label ?? (token ? token : egldLabel); const DisplayValue = () => { + if (hideLessThanOne) { + return {'< 1'}; + } const completeValueParts = String(completeValue).split('.'); const decimalArray = completeValueParts?.[1]?.split('') ?? []; const areAllDigitsZeroes = decimalArray.every((digit) => digit === ZERO); diff --git a/src/components/FormatValue/FormatNumber/FormatNumber.tsx b/src/components/FormatValue/FormatNumber/FormatNumber.tsx index 50b4d935e..8f7de7c24 100644 --- a/src/components/FormatValue/FormatNumber/FormatNumber.tsx +++ b/src/components/FormatValue/FormatNumber/FormatNumber.tsx @@ -33,14 +33,10 @@ export const FormatNumber = (props: FormatNumberUIType) => { ); } - let formattedValue = bNamount.isInteger() + const formattedValue = bNamount.isInteger() ? completeValue : formatBigNumber({ value: bNamount, maxDigits }); - if (hideLessThanOne && bNamount.isLessThan(1)) { - formattedValue = '< 1'; - } - return ( { completeValue={completeValue} symbol={symbol} egldLabel={label} + hideLessThanOne={hideLessThanOne && bNamount.isLessThan(1)} showSymbol={Boolean(symbol)} showLabel={Boolean(label)} showTooltipSymbol={Boolean(symbol)} diff --git a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx new file mode 100644 index 000000000..41bb05e59 --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx @@ -0,0 +1,225 @@ +import { useEffect, useState } from 'react'; +import BigNumber from 'bignumber.js'; +import { useSelector } from 'react-redux'; +import { useParams, useSearchParams } from 'react-router-dom'; + +import { ZERO, LOW_LIQUIDITY_DISPLAY_TRESHOLD } from 'appConstants'; +import { + Pager, + PageSize, + PageState, + FormatAmount, + TokenLink, + FormatUSD, + LowLiquidityTooltip, + TableWrapper, + FormatNumber +} from 'components'; +import { useAdapter, useGetPage, useGetSearch } from 'hooks'; +import { faCoins } from 'icons/solid'; +import { AccountTabs } from 'layouts/AccountLayout/AccountTabs'; +import { + activeNetworkSelector, + accountSelector, + accountExtraSelector +} from 'redux/selectors'; +import { TokenType, TokenTypeEnum } from 'types'; + +import { AccountTokensTableHeader } from './components'; + +export const AccountTokensTable = () => { + const { id: activeNetworkId } = useSelector(activeNetworkSelector); + const [searchParams] = useSearchParams(); + const { account } = useSelector(accountSelector); + const { accountExtra, isFetched: isAccountExtraFetched } = + useSelector(accountExtraSelector); + const { txCount } = account; + const { tokenBalance, address: extraAddress } = accountExtra; + const { page, size } = useGetPage(); + const { search } = useGetSearch(); + const { getAccountTokens, getAccountTokensCount } = useAdapter(); + + const { hash: address } = useParams() as any; + const { type } = Object.fromEntries(searchParams); + + const [dataReady, setDataReady] = useState(); + const [accountTokens, setAccountTokens] = useState([]); + const [dataChanged, setDataChanged] = useState(false); + const [accountTokensCount, setAccountTokensCount] = useState(0); + + const fetchAccountTokens = () => { + setDataChanged(true); + Promise.all([ + getAccountTokens({ + page, + size, + address, + search, + type, + includeMetaESDT: type !== TokenTypeEnum.FungibleESDT + }), + getAccountTokensCount({ + address, + search, + type, + includeMetaESDT: type !== TokenTypeEnum.FungibleESDT + }) + ]) + .then(([accountTokensData, accountTokensCountData]) => { + if (accountTokensData.success && accountTokensCountData.success) { + setAccountTokens(accountTokensData.data); + setAccountTokensCount(accountTokensCountData.data); + } + setDataReady( + accountTokensData.success && accountTokensCountData.success + ); + }) + .finally(() => { + setDataChanged(false); + }); + }; + + useEffect(() => { + fetchAccountTokens(); + }, [txCount, activeNetworkId, address, searchParams]); + + return ( +
+ {dataReady === false && ( + + )} + {dataReady === true && ( +
+
+
+ {accountTokens && accountTokens.length > 0 ? ( + <> +
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + {accountTokens.map((token) => { + const isValidValue = + token.valueUsd && + (!token.isLowLiquidity || + new BigNumber(token.valueUsd).isLessThan( + LOW_LIQUIDITY_DISPLAY_TRESHOLD + )); + const portofolioPercent = + token.valueUsd && + tokenBalance && + address === extraAddress + ? new BigNumber(token.valueUsd) + .dividedBy(tokenBalance) + .times(100) + : new BigNumber(0); + + return ( + + + + + + + + ); + })} + +
TokenBalancePriceValuePortofolio %
+
+ + + ({token.assets?.name ?? token.name}) + +
+
+ + + {token.price ? ( + + ) : ( + - + )} + + {isValidValue ? ( +
+ + +
+ ) : ( + - + )} +
+ {portofolioPercent.isGreaterThan(0) ? ( + + ) : ( + - + )} +
+
+
+ +
+ + 0} + /> +
+ + ) : ( + + )} +
+
+
+ )} +
+ ); +}; diff --git a/src/pages/AccountDetails/AccountTokensTable/accountTokensTable.styles.scss b/src/pages/AccountDetails/AccountTokensTable/accountTokensTable.styles.scss new file mode 100644 index 000000000..6e66ea822 --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/accountTokensTable.styles.scss @@ -0,0 +1,14 @@ +.account-tokens-table { + td { + width: 12.5%; + &:first-of-type { + width: 30%; + } + &:nth-child(2) { + width: 30%; + } + &:nth-child(4) { + width: 15%; + } + } +} diff --git a/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx b/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx new file mode 100644 index 000000000..5daa8fba8 --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx @@ -0,0 +1,99 @@ +import classNames from 'classnames'; +import { useSearchParams } from 'react-router-dom'; + +import { Pager, TableSearch } from 'components'; +import { TokenType, TokenTypeEnum } from 'types'; + +export interface AccountTokensTableHeaderUIType { + accountTokensCount?: number; + accountTokens?: TokenType[]; +} + +export const AccountTokensTableHeader = ({ + accountTokens = [], + accountTokensCount +}: AccountTokensTableHeaderUIType) => { + const [searchParams, setSearchParams] = useSearchParams(); + const { type } = Object.fromEntries(searchParams); + + const updateTokenType = (typeValue?: TokenTypeEnum) => { + const { type, page, size, ...rest } = Object.fromEntries(searchParams); + const nextUrlParams = { + ...rest, + ...(typeValue ? { type: typeValue } : {}) + }; + + setSearchParams(nextUrlParams); + }; + + return ( + <> +
+ +
  • + +
  • +
  • + +
  • +
  • + +
  • +
    +
    + +
    +
    + {accountTokens.length > 0 && ( + 0} + className='d-flex ms-auto me-auto me-sm-0' + /> + )} + + ); +}; diff --git a/src/pages/AccountDetails/AccountTokensTable/components/index.ts b/src/pages/AccountDetails/AccountTokensTable/components/index.ts new file mode 100644 index 000000000..22188ef15 --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/components/index.ts @@ -0,0 +1 @@ +export * from './AccountTokensTableHeader'; diff --git a/src/pages/AccountDetails/AccountTokensTable/index.ts b/src/pages/AccountDetails/AccountTokensTable/index.ts new file mode 100644 index 000000000..3a2626910 --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/index.ts @@ -0,0 +1 @@ +export * from './AccountTokensTable'; diff --git a/src/routes/layouts/accountLayout.ts b/src/routes/layouts/accountLayout.ts index 621536d84..0e3a8e440 100644 --- a/src/routes/layouts/accountLayout.ts +++ b/src/routes/layouts/accountLayout.ts @@ -7,7 +7,7 @@ import { AccountNodes } from 'pages/AccountDetails/AccountNodes'; import { AccountCollectionRoles } from 'pages/AccountDetails/AccountRoles/AccountCollectionRoles'; import { AccountTokenRoles } from 'pages/AccountDetails/AccountRoles/AccountTokenRoles'; import { AccountStaking } from 'pages/AccountDetails/AccountStaking'; -import { AccountTokens } from 'pages/AccountDetails/AccountTokens'; +import { AccountTokensTable } from 'pages/AccountDetails/AccountTokensTable'; import { AccountTransactions } from 'pages/AccountDetails/AccountTransactions'; import { AccountUpgrades } from 'pages/AccountDetails/AccountUpgrades'; import { OldRouteRedirect } from 'pages/AccountDetails/OldRouteRedirect'; @@ -87,7 +87,7 @@ export const accountLayout: TitledRouteObject[] = [ path: accountsRoutes.accountTokens, title: 'Account Tokens', preventScroll: true, - Component: AccountTokens + Component: AccountTokensTable }, { path: accountsRoutes.accountNfts, From b17e67f9c2c68a260eecee7307594d44e71f787c Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 28 Aug 2024 18:34:43 +0300 Subject: [PATCH 02/10] added sorting and filtering based on existing results --- .../ProvidersTable/ProvidersTable.tsx | 10 +- src/helpers/getValue/getItemsPage.ts | 48 +++ src/helpers/getValue/index.ts | 1 + .../AccountTokensTable/AccountTokensTable.tsx | 352 ++++++++++-------- .../components/AccountTokensTableHeader.tsx | 22 +- .../helpers/filterTokens.ts | 33 ++ .../AccountTokensTable/helpers/index.ts | 2 + .../AccountTokensTable/helpers/sortTokens.ts | 124 ++++++ .../AccountTokensTable/hooks/index.ts | 1 + .../hooks/useProcessTokens.ts | 60 +++ 10 files changed, 478 insertions(+), 175 deletions(-) create mode 100644 src/helpers/getValue/getItemsPage.ts create mode 100644 src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts create mode 100644 src/pages/AccountDetails/AccountTokensTable/helpers/index.ts create mode 100644 src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts create mode 100644 src/pages/AccountDetails/AccountTokensTable/hooks/index.ts create mode 100644 src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts diff --git a/src/components/ProvidersTable/ProvidersTable.tsx b/src/components/ProvidersTable/ProvidersTable.tsx index 57ca9ed3c..1e3823b3b 100644 --- a/src/components/ProvidersTable/ProvidersTable.tsx +++ b/src/components/ProvidersTable/ProvidersTable.tsx @@ -18,21 +18,21 @@ export const ProvidersTable = (props: ProvidersTableUIType) => { const { providers, showIndex = true, showIdentity = true } = props; const [displayProviders, setDisplayProviders] = useState(providers); - const sort = useGetSort(); + const { sort, order } = useGetSort(); useEffect(() => { - if (sort.sort && sort.order) { + if (sort && order) { setDisplayProviders((existing) => sortProviders({ - field: sort.sort as SortProviderFieldEnum, - order: sort.order, + field: sort as SortProviderFieldEnum, + order: order, sortArray: [...existing] }) ); } else { setDisplayProviders(providers); } - }, [sort.sort, sort.order]); + }, [sort, order]); return (
    { + const itemsPerPageBigNumber = new BigNumber(itemsPerPage); + const currentPageBigNumber = new BigNumber(currentPage); + const itemsLengthBigNumber = new BigNumber(items.length); + + const totalPages = Math.ceil( + itemsLengthBigNumber.dividedBy(itemsPerPage).toNumber() + ); + + const totalPagesArray = Array.from({ length: totalPages }); + const ranges = totalPagesArray.map((_, index) => [ + itemsPerPageBigNumber.times(index), + itemsPerPageBigNumber.times(new BigNumber(index).plus(1)) + ]); + + const rangesLengthBigNumber = new BigNumber(ranges.length); + const currentRange = ranges.find((_, index) => { + if (rangesLengthBigNumber.lte(currentPage)) { + return rangesLengthBigNumber.minus(1).isEqualTo(index); + } + + return currentPageBigNumber.minus(1).isEqualTo(index); + }); + + if (!currentRange) { + return items; + } + + const [currentRangeStart, currentRangeEnd] = currentRange; + const slicedTokensArray = items.slice( + currentRangeStart.toNumber(), + currentRangeEnd.toNumber() + ); + + return slicedTokensArray; +}; diff --git a/src/helpers/getValue/index.ts b/src/helpers/getValue/index.ts index f5ebe40aa..f2b5ed3ba 100644 --- a/src/helpers/getValue/index.ts +++ b/src/helpers/getValue/index.ts @@ -4,6 +4,7 @@ export * from './getAccountStakingDetails'; export * from './getAccountValidatorStakeDetails'; export * from './getColors'; export * from './getDisplayReceiver'; +export * from './getItemsPage'; export * from './getNftText'; export * from './getNodeIcon'; export * from './getNodeIssue'; diff --git a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx index 41bb05e59..69e8ea4e4 100644 --- a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx +++ b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx @@ -1,9 +1,13 @@ import { useEffect, useState } from 'react'; import BigNumber from 'bignumber.js'; import { useSelector } from 'react-redux'; -import { useParams, useSearchParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; -import { ZERO, LOW_LIQUIDITY_DISPLAY_TRESHOLD } from 'appConstants'; +import { + ZERO, + LOW_LIQUIDITY_DISPLAY_TRESHOLD, + MAX_RESULTS +} from 'appConstants'; import { Pager, PageSize, @@ -13,9 +17,12 @@ import { FormatUSD, LowLiquidityTooltip, TableWrapper, - FormatNumber + FormatNumber, + Sort, + Loader } from 'components'; -import { useAdapter, useGetPage, useGetSearch } from 'hooks'; +import { getItemsPage } from 'helpers'; +import { useAdapter, useGetPage } from 'hooks'; import { faCoins } from 'icons/solid'; import { AccountTabs } from 'layouts/AccountLayout/AccountTabs'; import { @@ -23,154 +30,175 @@ import { accountSelector, accountExtraSelector } from 'redux/selectors'; -import { TokenType, TokenTypeEnum } from 'types'; +import { TokenType, SortOrderEnum } from 'types'; import { AccountTokensTableHeader } from './components'; +import { SortTokenFieldEnum } from './helpers'; +import { useProcessTokens } from './hooks'; + +const ColSpanWrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); export const AccountTokensTable = () => { const { id: activeNetworkId } = useSelector(activeNetworkSelector); - const [searchParams] = useSearchParams(); const { account } = useSelector(accountSelector); - const { accountExtra, isFetched: isAccountExtraFetched } = + const { isFetched: isAccountExtraFetched } = useSelector(accountExtraSelector); const { txCount } = account; - const { tokenBalance, address: extraAddress } = accountExtra; + const { getAccountTokens } = useAdapter(); const { page, size } = useGetPage(); - const { search } = useGetSearch(); - const { getAccountTokens, getAccountTokensCount } = useAdapter(); const { hash: address } = useParams() as any; - const { type } = Object.fromEntries(searchParams); - const [dataReady, setDataReady] = useState(); + const [isDataReady, setIsDataReady] = useState(); const [accountTokens, setAccountTokens] = useState([]); const [dataChanged, setDataChanged] = useState(false); - const [accountTokensCount, setAccountTokensCount] = useState(0); const fetchAccountTokens = () => { setDataChanged(true); - Promise.all([ - getAccountTokens({ - page, - size, - address, - search, - type, - includeMetaESDT: type !== TokenTypeEnum.FungibleESDT - }), - getAccountTokensCount({ - address, - search, - type, - includeMetaESDT: type !== TokenTypeEnum.FungibleESDT - }) - ]) - .then(([accountTokensData, accountTokensCountData]) => { - if (accountTokensData.success && accountTokensCountData.success) { + getAccountTokens({ + address, + includeMetaESDT: true, + size: MAX_RESULTS + }) + .then((accountTokensData) => { + if (accountTokensData.success) { setAccountTokens(accountTokensData.data); - setAccountTokensCount(accountTokensCountData.data); } - setDataReady( - accountTokensData.success && accountTokensCountData.success - ); + setIsDataReady(accountTokensData.success); }) .finally(() => { setDataChanged(false); }); }; + const processedAccountTokens = useProcessTokens(accountTokens); + const pagedTokens = getItemsPage({ + items: processedAccountTokens, + currentPage: page, + itemsPerPage: size + }); + useEffect(() => { fetchAccountTokens(); - }, [txCount, activeNetworkId, address, searchParams]); + }, [txCount, activeNetworkId, address]); return (
    - {dataReady === false && ( - - )} - {dataReady === true && ( -
    -
    -
    - {accountTokens && accountTokens.length > 0 ? ( - <> -
    -
    - -
    -
    - -
    -
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    -
    - - - - - - - - - - - - - {accountTokens.map((token) => { - const isValidValue = - token.valueUsd && - (!token.isLowLiquidity || - new BigNumber(token.valueUsd).isLessThan( - LOW_LIQUIDITY_DISPLAY_TRESHOLD - )); - const portofolioPercent = - token.valueUsd && - tokenBalance && - address === extraAddress - ? new BigNumber(token.valueUsd) - .dividedBy(tokenBalance) - .times(100) - : new BigNumber(0); +
    + +
    TokenBalancePriceValuePortofolio %
    + + + + + + + + + + + {(isDataReady === undefined || !isAccountExtraFetched) && ( + + + + )} + {isDataReady === false && ( + + + + )} + {isDataReady === true && ( + <> + {pagedTokens.length > 0 ? ( + <> + {pagedTokens.map((token) => { + const isValidValue = + token.valueUsd && + (!token.isLowLiquidity || + new BigNumber(token.valueUsd).isLessThan( + LOW_LIQUIDITY_DISPLAY_TRESHOLD + )); - return ( - - - - - + + + + - - - ); - })} - -
    + + + + + + + + + +
    -
    - - - ({token.assets?.name ?? token.name}) - -
    -
    - - - {token.price ? ( - - ) : ( - - - )} - - {isValidValue ? ( + return ( +
    + + + ({token.assets?.name ?? token.name}) + +
    +
    + + + {token.price ? ( +
    + + +
    + ) : ( + + - + + )} +
    + {isValidValue ? ( { showLastNonZeroDecimal className='text-neutral-400' /> - - - ) : ( - - - )} - - {portofolioPercent.isGreaterThan(0) ? ( - - ) : ( - - - )} -
    -
    -
    + ) : ( + + - + + )} + + + {token.portofolioPercentage && + token.portofolioPercentage.isGreaterThan( + 0 + ) ? ( + + ) : ( + + - + + )} + + + ); + })} + + ) : ( + <> + + + + + )} + + )} + + + +
    -
    - - 0} - /> -
    - - ) : ( - - )} +
    + + 0} + />
    - )} +
    ); }; diff --git a/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx b/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx index 5daa8fba8..cd919c2a3 100644 --- a/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx +++ b/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx @@ -2,16 +2,14 @@ import classNames from 'classnames'; import { useSearchParams } from 'react-router-dom'; import { Pager, TableSearch } from 'components'; -import { TokenType, TokenTypeEnum } from 'types'; +import { TokenTypeEnum } from 'types'; export interface AccountTokensTableHeaderUIType { - accountTokensCount?: number; - accountTokens?: TokenType[]; + tokenCount?: number; } export const AccountTokensTableHeader = ({ - accountTokens = [], - accountTokensCount + tokenCount = 0 }: AccountTokensTableHeaderUIType) => { const [searchParams, setSearchParams] = useSearchParams(); const { type } = Object.fromEntries(searchParams); @@ -81,19 +79,17 @@ export const AccountTokensTableHeader = ({
    - {accountTokens.length > 0 && ( - 0} - className='d-flex ms-auto me-auto me-sm-0' - /> - )} + ); }; diff --git a/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts b/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts new file mode 100644 index 000000000..b3383abdc --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts @@ -0,0 +1,33 @@ +import { TokenTypeEnum } from 'types'; + +import { ProcessedTokenType } from './processTokens'; + +export interface FilterTokensType { + tokens: ProcessedTokenType[]; + type?: TokenTypeEnum; + search?: string; +} + +export const filterTokens = ({ tokens, type, search }: FilterTokensType) => { + const searchTerm = (term?: string) => { + if (!search || !term) { + return true; + } + + return term.toLowerCase().includes(search.toLowerCase()); + }; + return tokens + .filter((token) => !type || token.type === type) + .filter(({ name, identifier, assets }) => { + if (!search) { + return true; + } + + return ( + searchTerm(name) || + searchTerm(identifier) || + searchTerm(assets?.description) || + searchTerm(assets?.name) + ); + }); +}; diff --git a/src/pages/AccountDetails/AccountTokensTable/helpers/index.ts b/src/pages/AccountDetails/AccountTokensTable/helpers/index.ts new file mode 100644 index 000000000..078568b77 --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './filterTokens'; +export * from './sortTokens'; diff --git a/src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts b/src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts new file mode 100644 index 000000000..783be957f --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts @@ -0,0 +1,124 @@ +import BigNumber from 'bignumber.js'; + +import { LOW_LIQUIDITY_DISPLAY_TRESHOLD } from 'appConstants'; +import { formatAmount } from 'helpers'; +import { TokenType, SortOrderEnum } from 'types'; + +export enum SortTokenFieldEnum { + name = 'name', + balance = 'balance', + price = 'price', + value = 'value', + portofolioPercent = 'portofolioPercent' +} + +export interface ProcessedTokenType extends TokenType { + portofolioPercentage: BigNumber; +} + +export interface SortTokensType { + field?: SortTokenFieldEnum; + order?: SortOrderEnum; + tokens: ProcessedTokenType[]; + tokenBalance?: string; +} + +const getTokenDisplayValue = ({ + valueUsd, + isLowLiquidity +}: ProcessedTokenType) => { + if ( + valueUsd && + (!isLowLiquidity || + new BigNumber(valueUsd).isLessThan(LOW_LIQUIDITY_DISPLAY_TRESHOLD)) + ) { + return new BigNumber(valueUsd); + } + + return new BigNumber(0); +}; + +const getPortofolioPercent = ({ + token, + tokenBalance +}: { + token: ProcessedTokenType; + tokenBalance: string; +}) => { + return token.valueUsd && tokenBalance + ? new BigNumber(token.valueUsd).dividedBy(tokenBalance).times(100) + : new BigNumber(0); +}; + +export const sortTokens = ({ + field = SortTokenFieldEnum.value, + order = SortOrderEnum.desc, + tokens = [], + tokenBalance +}: SortTokensType) => { + if (field && order) { + const sortParams = order === SortOrderEnum.asc ? [1, -1] : [-1, 1]; + + switch (true) { + case field === SortTokenFieldEnum.name: + tokens.sort((a, b) => { + const aName = a.assets?.name ?? a.name; + const bName = b.assets?.name ?? b.name; + return aName.toLowerCase() > bName.toLowerCase() + ? sortParams[0] + : sortParams[1]; + }); + break; + + case field === SortTokenFieldEnum.value: + tokens.sort((a, b) => { + const aValue = getTokenDisplayValue(a); + const bValue = getTokenDisplayValue(b); + return aValue.isGreaterThan(bValue) ? sortParams[0] : sortParams[1]; + }); + break; + + case field === SortTokenFieldEnum.balance: + tokens.sort((a, b) => { + const aBalance = formatAmount({ + input: new BigNumber(a.balance ?? 0).toString(10), + decimals: a.decimals, + showLastNonZeroDecimal: true + }); + const bBalance = formatAmount({ + input: new BigNumber(b.balance ?? 0).toString(10), + decimals: b.decimals, + showLastNonZeroDecimal: true + }); + return new BigNumber(aBalance).isGreaterThan(bBalance) + ? sortParams[0] + : sortParams[1]; + }); + break; + + case field === SortTokenFieldEnum.portofolioPercent: + if (!tokenBalance) { + return tokens; + } + + tokens.sort((a, b) => { + const aPercent = getPortofolioPercent({ token: a, tokenBalance }); + const bPercent = getPortofolioPercent({ token: b, tokenBalance }); + return new BigNumber(aPercent).isGreaterThan(bPercent) + ? sortParams[0] + : sortParams[1]; + }); + break; + + default: + tokens.sort((a, b) => { + return new BigNumber(a.price ?? 0).isGreaterThan(b.price ?? 0) + ? sortParams[0] + : sortParams[1]; + }); + break; + } + } + + return tokens; +}; diff --git a/src/pages/AccountDetails/AccountTokensTable/hooks/index.ts b/src/pages/AccountDetails/AccountTokensTable/hooks/index.ts new file mode 100644 index 000000000..043479870 --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/hooks/index.ts @@ -0,0 +1 @@ +export * from './useProcessTokens'; diff --git a/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts new file mode 100644 index 000000000..5c50b9f72 --- /dev/null +++ b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts @@ -0,0 +1,60 @@ +import { useMemo } from 'react'; +import BigNumber from 'bignumber.js'; +import { useSelector } from 'react-redux'; +import { useParams, useSearchParams } from 'react-router-dom'; + +import { useGetSearch, useGetSort } from 'hooks'; +import { accountExtraSelector } from 'redux/selectors'; +import { TokenTypeEnum, TokenType } from 'types'; + +import { + filterTokens, + sortTokens, + ProcessedTokenType, + SortTokenFieldEnum +} from '../helpers'; + +export const useProcessTokens = (accountTokens: TokenType[]) => { + const { hash: address } = useParams() as any; + + const [searchParams] = useSearchParams(); + const { accountExtra } = useSelector(accountExtraSelector); + + const { tokenBalance, address: extraAddress } = accountExtra; + + const { search } = useGetSearch(); + const { sort, order } = useGetSort(); + const { type } = Object.fromEntries(searchParams); + + const processTokens = ({ tokens }: { tokens: ProcessedTokenType[] }) => { + const filteredTokens = filterTokens({ + tokens, + type: type as TokenTypeEnum, + search + }); + + const sortedTokens = sortTokens({ + tokens: filteredTokens, + field: sort as SortTokenFieldEnum, + order, + tokenBalance + }); + + return sortedTokens; + }; + + const processedAccountTokens = useMemo(() => { + const processedSortArray = accountTokens.map((token) => { + const portofolioPercentage = + token.valueUsd && tokenBalance && address === extraAddress + ? new BigNumber(token.valueUsd).dividedBy(tokenBalance).times(100) + : new BigNumber(0); + return { ...token, portofolioPercentage }; + }); + + const processedTokens = processTokens({ tokens: processedSortArray }); + return [...processedTokens]; + }, [accountTokens, type, search, sort, order, tokenBalance]); + + return processedAccountTokens; +}; From de364e38847124589be234dcb4559185ff111ed1 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 29 Aug 2024 14:42:05 +0300 Subject: [PATCH 03/10] updated filters --- .../AccountTokensTable/helpers/filterTokens.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts b/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts index b3383abdc..661a2b097 100644 --- a/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts +++ b/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts @@ -1,6 +1,6 @@ import { TokenTypeEnum } from 'types'; -import { ProcessedTokenType } from './processTokens'; +import { ProcessedTokenType } from '../helpers'; export interface FilterTokensType { tokens: ProcessedTokenType[]; @@ -10,12 +10,17 @@ export interface FilterTokensType { export const filterTokens = ({ tokens, type, search }: FilterTokensType) => { const searchTerm = (term?: string) => { - if (!search || !term) { + if (!search) { return true; } + if (!term) { + return false; + } + return term.toLowerCase().includes(search.toLowerCase()); }; + return tokens .filter((token) => !type || token.type === type) .filter(({ name, identifier, assets }) => { From 13a2954dd8ed83fb865ca1d7187f51e4b33b6b80 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 29 Aug 2024 15:33:54 +0300 Subject: [PATCH 04/10] show only valid values --- src/helpers/index.ts | 1 + src/helpers/isValidTokenValue.ts | 14 + .../AccountDetailsCard/AccountDetailsCard.tsx | 17 +- src/layouts/TokenLayout/TokenDetailsCard.tsx | 5 +- src/pages/AccountDetails/AccountTokens.tsx | 35 +-- .../AccountTokensTable/AccountTokensTable.tsx | 286 ++++++++---------- .../hooks/useProcessTokens.ts | 25 +- .../components/TokensTable/TokensTable.tsx | 29 +- src/widgets/StatsCard/SmallStatsCard.tsx | 4 + 9 files changed, 205 insertions(+), 211 deletions(-) create mode 100644 src/helpers/isValidTokenValue.ts diff --git a/src/helpers/index.ts b/src/helpers/index.ts index e51b46834..4cd02b7a2 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -16,6 +16,7 @@ export * from './isEllipsisActive'; export * from './isHash'; export * from './isMetachain'; export * from './isUtf8'; +export * from './isValidTokenValue'; export * from './parseAmount'; export * from './parseJwt'; export * from './partitionBy'; diff --git a/src/helpers/isValidTokenValue.ts b/src/helpers/isValidTokenValue.ts new file mode 100644 index 000000000..4252f7767 --- /dev/null +++ b/src/helpers/isValidTokenValue.ts @@ -0,0 +1,14 @@ +import BigNumber from 'bignumber.js'; + +import { LOW_LIQUIDITY_DISPLAY_TRESHOLD } from 'appConstants'; +import { TokenType } from 'types'; + +export const isValidTokenValue = (token: TokenType) => { + return Boolean( + token.valueUsd && + (!token.isLowLiquidity || + new BigNumber(token.valueUsd).isLessThan( + LOW_LIQUIDITY_DISPLAY_TRESHOLD + )) + ); +}; diff --git a/src/layouts/AccountLayout/AccountDetailsCard/AccountDetailsCard.tsx b/src/layouts/AccountLayout/AccountDetailsCard/AccountDetailsCard.tsx index d2af317b8..6960b3f28 100644 --- a/src/layouts/AccountLayout/AccountDetailsCard/AccountDetailsCard.tsx +++ b/src/layouts/AccountLayout/AccountDetailsCard/AccountDetailsCard.tsx @@ -1,13 +1,8 @@ import React, { useEffect } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import BigNumber from 'bignumber.js'; import { useDispatch, useSelector } from 'react-redux'; -import { - ELLIPSIS, - MAX_ACOUNT_TOKENS_BALANCE, - LOW_LIQUIDITY_DISPLAY_TRESHOLD -} from 'appConstants'; +import { ELLIPSIS, MAX_ACOUNT_TOKENS_BALANCE } from 'appConstants'; import { NativeTokenSymbol } from 'components'; import { CardItem, @@ -22,7 +17,8 @@ import { urlBuilder, formatHerotag, formatBigNumber, - getTotalTokenUsdValue + getTotalTokenUsdValue, + isValidTokenValue } from 'helpers'; import { useAdapter, useIsSovereign } from 'hooks'; import { faClock, faExclamationTriangle } from 'icons/regular'; @@ -118,12 +114,7 @@ export const AccountDetailsCard = () => { } if (accountTokensValueData.success) { const validTokenValues = accountTokensValueData.data.filter( - (token: TokenType) => - token.valueUsd && - (!token.isLowLiquidity || - new BigNumber(token.valueUsd).isLessThan( - LOW_LIQUIDITY_DISPLAY_TRESHOLD - )) + (token: TokenType) => isValidTokenValue(token) ); const tokenBalance = getTotalTokenUsdValue(validTokenValues); accountExtraDetails.tokenBalance = tokenBalance; diff --git a/src/layouts/TokenLayout/TokenDetailsCard.tsx b/src/layouts/TokenLayout/TokenDetailsCard.tsx index b49b7adc3..ab9db6d0a 100644 --- a/src/layouts/TokenLayout/TokenDetailsCard.tsx +++ b/src/layouts/TokenLayout/TokenDetailsCard.tsx @@ -109,13 +109,14 @@ export const TokenDetailsCard = () => { ]; const smallStatsCards = [ - supply + supply && new BigNumber(supply).isGreaterThanOrEqualTo(0) ? { title: 'Supply', value: new BigNumber(supply).toFormat(0) } : {}, - circulatingSupply + circulatingSupply && + new BigNumber(circulatingSupply).isGreaterThanOrEqualTo(0) ? { title: 'Circulating', value: new BigNumber(circulatingSupply).toFormat(0) diff --git a/src/pages/AccountDetails/AccountTokens.tsx b/src/pages/AccountDetails/AccountTokens.tsx index c66a012f8..9171d3a27 100644 --- a/src/pages/AccountDetails/AccountTokens.tsx +++ b/src/pages/AccountDetails/AccountTokens.tsx @@ -1,9 +1,8 @@ import { useEffect, useRef, useState } from 'react'; -import BigNumber from 'bignumber.js'; import { useSelector } from 'react-redux'; import { useParams, useSearchParams } from 'react-router-dom'; -import { ZERO, LOW_LIQUIDITY_DISPLAY_TRESHOLD } from 'appConstants'; +import { ZERO } from 'appConstants'; import { DetailItem, Loader, @@ -15,6 +14,7 @@ import { FormatUSD, LowLiquidityTooltip } from 'components'; +import { isValidTokenValue } from 'helpers'; import { useAdapter, useGetPage } from 'hooks'; import { faCoins } from 'icons/solid'; import { AccountTabs } from 'layouts/AccountLayout/AccountTabs'; @@ -90,6 +90,7 @@ export const AccountTokens = () => { {dataReady === true && accountTokens.length > 0 && ( <> {accountTokens.map((token) => { + const isValidDisplayValue = isValidTokenValue(token); return ( { showLastNonZeroDecimal />
    - {token.valueUsd && - (!token.isLowLiquidity || - new BigNumber(token.valueUsd).isLessThan( - LOW_LIQUIDITY_DISPLAY_TRESHOLD - )) && ( - - ( - - ) - - )} + {isValidDisplayValue && ( + + ( + + ) + + )}
    diff --git a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx index 69e8ea4e4..1453555d9 100644 --- a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx +++ b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx @@ -1,13 +1,8 @@ import { useEffect, useState } from 'react'; -import BigNumber from 'bignumber.js'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; -import { - ZERO, - LOW_LIQUIDITY_DISPLAY_TRESHOLD, - MAX_RESULTS -} from 'appConstants'; +import { ZERO, MAX_RESULTS } from 'appConstants'; import { Pager, PageSize, @@ -16,12 +11,12 @@ import { TokenLink, FormatUSD, LowLiquidityTooltip, - TableWrapper, FormatNumber, Sort, - Loader + Loader, + Overlay } from 'components'; -import { getItemsPage } from 'helpers'; +import { getItemsPage, isValidTokenValue } from 'helpers'; import { useAdapter, useGetPage } from 'hooks'; import { faCoins } from 'icons/solid'; import { AccountTabs } from 'layouts/AccountLayout/AccountTabs'; @@ -50,29 +45,22 @@ export const AccountTokensTable = () => { const { txCount } = account; const { getAccountTokens } = useAdapter(); const { page, size } = useGetPage(); - const { hash: address } = useParams() as any; const [isDataReady, setIsDataReady] = useState(); const [accountTokens, setAccountTokens] = useState([]); - const [dataChanged, setDataChanged] = useState(false); const fetchAccountTokens = () => { - setDataChanged(true); getAccountTokens({ address, includeMetaESDT: true, size: MAX_RESULTS - }) - .then((accountTokensData) => { - if (accountTokensData.success) { - setAccountTokens(accountTokensData.data); - } - setIsDataReady(accountTokensData.success); - }) - .finally(() => { - setDataChanged(false); - }); + }).then((accountTokensData) => { + if (accountTokensData.success) { + setAccountTokens(accountTokensData.data); + } + setIsDataReady(accountTokensData.success); + }); }; const processedAccountTokens = useProcessTokens(accountTokens); @@ -103,149 +91,137 @@ export const AccountTokensTable = () => {
    - - - - - - - - - + + + ); + })} + + ) : ( + <> + + + + + )} + + )} + +
    - - - - - - - - + + + + + + + + - - - - {(isDataReady === undefined || !isAccountExtraFetched) && ( - - - - )} - {isDataReady === false && ( - - - - )} - {isDataReady === true && ( - <> - {pagedTokens.length > 0 ? ( - <> - {pagedTokens.map((token) => { - const isValidValue = - token.valueUsd && - (!token.isLowLiquidity || - new BigNumber(token.valueUsd).isLessThan( - LOW_LIQUIDITY_DISPLAY_TRESHOLD - )); + + + + + + {isDataReady === undefined && ( + + + + )} + {isDataReady === false && ( + + + + )} + {isDataReady === true && ( + <> + {pagedTokens.length > 0 ? ( + <> + {pagedTokens.map((token) => { + const isValidDisplayValue = + isValidTokenValue(token); - return ( - - + + + - - - - - - ); - })} - - ) : ( - <> - - - - - )} - - )} - -
    + + + + + + + + + -
    + return ( +
    +
    + + + ({token.assets?.name ?? token.name}) + +
    +
    + + + {token.price ? (
    - - - ({token.assets?.name ?? token.name}) - -
    -
    - - - {token.price ? ( -
    - - -
    - ) : ( - - - - - )} -
    - {isValidValue ? ( - ) : ( - - - - - )} - - {token.portofolioPercentage && - token.portofolioPercentage.isGreaterThan( - 0 - ) ? ( - - ) : ( - - - - - )} -
    - + + + ) : ( + - + )} + +
    + {isValidDisplayValue ? ( + + ) : ( + - + )} + + {token.portofolioPercentage && + token.portofolioPercentage.isGreaterThan( + 0 + ) ? ( + + ) : ( + - + )} +
    diff --git a/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts index 5c50b9f72..f66b8b27a 100644 --- a/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts +++ b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts @@ -1,10 +1,12 @@ import { useMemo } from 'react'; import BigNumber from 'bignumber.js'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useParams, useSearchParams } from 'react-router-dom'; +import { isValidTokenValue, getTotalTokenUsdValue } from 'helpers'; import { useGetSearch, useGetSort } from 'hooks'; import { accountExtraSelector } from 'redux/selectors'; +import { setAccountExtra, getInitialAccountExtraState } from 'redux/slices'; import { TokenTypeEnum, TokenType } from 'types'; import { @@ -15,10 +17,12 @@ import { } from '../helpers'; export const useProcessTokens = (accountTokens: TokenType[]) => { - const { hash: address } = useParams() as any; + const dispatch = useDispatch(); const [searchParams] = useSearchParams(); - const { accountExtra } = useSelector(accountExtraSelector); + const { hash: address } = useParams() as any; + const { accountExtra, isFetched: isAccountExtraFetched } = + useSelector(accountExtraSelector); const { tokenBalance, address: extraAddress } = accountExtra; @@ -26,6 +30,21 @@ export const useProcessTokens = (accountTokens: TokenType[]) => { const { sort, order } = useGetSort(); const { type } = Object.fromEntries(searchParams); + if (!isAccountExtraFetched) { + const validTokenValues = accountTokens.filter((token: TokenType) => + isValidTokenValue(token) + ); + const tokenBalance = getTotalTokenUsdValue(validTokenValues); + const accountExtraDetails = getInitialAccountExtraState().accountExtra; + accountExtraDetails.tokenBalance = tokenBalance; + dispatch( + setAccountExtra({ + accountExtra: { ...accountExtraDetails, address }, + isFetched: true + }) + ); + } + const processTokens = ({ tokens }: { tokens: ProcessedTokenType[] }) => { const filteredTokens = filterTokens({ tokens, diff --git a/src/pages/Tokens/components/TokensTable/TokensTable.tsx b/src/pages/Tokens/components/TokensTable/TokensTable.tsx index 6976eec18..f03b4e552 100644 --- a/src/pages/Tokens/components/TokensTable/TokensTable.tsx +++ b/src/pages/Tokens/components/TokensTable/TokensTable.tsx @@ -2,10 +2,7 @@ import { Fragment } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import BigNumber from 'bignumber.js'; -import { - ELLIPSIS, - LOW_LIQUIDITY_MARKET_CAP_DISPLAY_TRESHOLD -} from 'appConstants'; +import { ELLIPSIS } from 'appConstants'; import { NetworkLink, FormatAmount, @@ -13,7 +10,7 @@ import { LowLiquidityTooltip, FormatUSD } from 'components'; -import { urlBuilder } from 'helpers'; +import { isValidTokenValue, urlBuilder } from 'helpers'; import { useGetSort, useGetSearch, useIsNativeTokenSearched } from 'hooks'; import { faDiamond } from 'icons/regular'; import { TokenType, TokenSortEnum, SortOrderEnum } from 'types'; @@ -144,20 +141,14 @@ export const TokensTable = ({ )} - {token.marketCap && - (!token.isLowLiquidity || - new BigNumber(token.marketCap).isLessThan( - LOW_LIQUIDITY_MARKET_CAP_DISPLAY_TRESHOLD - )) && ( - <> - - - )} + {isValidTokenValue(token) && token.marketCap && ( + + )} {token.accounts diff --git a/src/widgets/StatsCard/SmallStatsCard.tsx b/src/widgets/StatsCard/SmallStatsCard.tsx index 53d31267b..ab9445d8d 100644 --- a/src/widgets/StatsCard/SmallStatsCard.tsx +++ b/src/widgets/StatsCard/SmallStatsCard.tsx @@ -6,6 +6,10 @@ export const SmallStatsCard = ({ value, className }: StatsCardUIType) => { + if (!(title && value)) { + return null; + } + return (
    Date: Thu, 29 Aug 2024 15:57:51 +0300 Subject: [PATCH 05/10] sort by name in case there are no usd values --- .../AccountTokensTable/AccountTokensTable.tsx | 22 ++++++++++++++++--- .../AccountTokensTable/helpers/sortTokens.ts | 6 ++++- .../hooks/useProcessTokens.ts | 18 ++++++++++++--- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx index 1453555d9..1e86e3e78 100644 --- a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx +++ b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx @@ -63,6 +63,9 @@ export const AccountTokensTable = () => { }); }; + const hasValidValues = accountTokens.some((token) => + isValidTokenValue(token) + ); const processedAccountTokens = useProcessTokens(accountTokens); const pagedTokens = getItemsPage({ items: processedAccountTokens, @@ -95,7 +98,16 @@ export const AccountTokensTable = () => { - + @@ -107,8 +119,12 @@ export const AccountTokensTable = () => { diff --git a/src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts b/src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts index 783be957f..3902ce851 100644 --- a/src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts +++ b/src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts @@ -110,13 +110,17 @@ export const sortTokens = ({ }); break; - default: + case field === SortTokenFieldEnum.price: tokens.sort((a, b) => { return new BigNumber(a.price ?? 0).isGreaterThan(b.price ?? 0) ? sortParams[0] : sortParams[1]; }); break; + + default: + return tokens; + break; } } diff --git a/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts index f66b8b27a..30dd31fa1 100644 --- a/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts +++ b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts @@ -7,7 +7,7 @@ import { isValidTokenValue, getTotalTokenUsdValue } from 'helpers'; import { useGetSearch, useGetSort } from 'hooks'; import { accountExtraSelector } from 'redux/selectors'; import { setAccountExtra, getInitialAccountExtraState } from 'redux/slices'; -import { TokenTypeEnum, TokenType } from 'types'; +import { TokenTypeEnum, TokenType, SortOrderEnum } from 'types'; import { filterTokens, @@ -52,10 +52,22 @@ export const useProcessTokens = (accountTokens: TokenType[]) => { search }); + let currentSort = sort; + let currentOrder = order; + if (!(sort && order)) { + const hasValidValues = filteredTokens.some((token) => + isValidTokenValue(token) + ); + if (!hasValidValues) { + currentSort = SortTokenFieldEnum.name; + currentOrder = SortOrderEnum.asc; + } + } + const sortedTokens = sortTokens({ tokens: filteredTokens, - field: sort as SortTokenFieldEnum, - order, + field: currentSort as SortTokenFieldEnum, + order: currentOrder, tokenBalance }); From 2591d6a93f7d8693513db0674915b6068d0de96f Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 29 Aug 2024 16:06:01 +0300 Subject: [PATCH 06/10] simplify accountExtra logic --- .../hooks/useProcessTokens.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts index 30dd31fa1..5aa2de6fe 100644 --- a/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts +++ b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts @@ -23,18 +23,17 @@ export const useProcessTokens = (accountTokens: TokenType[]) => { const { hash: address } = useParams() as any; const { accountExtra, isFetched: isAccountExtraFetched } = useSelector(accountExtraSelector); - - const { tokenBalance, address: extraAddress } = accountExtra; - + const { address: extraAddress } = accountExtra; const { search } = useGetSearch(); const { sort, order } = useGetSort(); const { type } = Object.fromEntries(searchParams); - if (!isAccountExtraFetched) { - const validTokenValues = accountTokens.filter((token: TokenType) => - isValidTokenValue(token) - ); - const tokenBalance = getTotalTokenUsdValue(validTokenValues); + const validTokenValues = accountTokens.filter((token: TokenType) => + isValidTokenValue(token) + ); + const tokenBalance = getTotalTokenUsdValue(validTokenValues); + + if (!isAccountExtraFetched && address === extraAddress) { const accountExtraDetails = getInitialAccountExtraState().accountExtra; accountExtraDetails.tokenBalance = tokenBalance; dispatch( @@ -77,7 +76,7 @@ export const useProcessTokens = (accountTokens: TokenType[]) => { const processedAccountTokens = useMemo(() => { const processedSortArray = accountTokens.map((token) => { const portofolioPercentage = - token.valueUsd && tokenBalance && address === extraAddress + token.valueUsd && tokenBalance ? new BigNumber(token.valueUsd).dividedBy(tokenBalance).times(100) : new BigNumber(0); return { ...token, portofolioPercentage }; From 7e1b3dc09cc7d082087efb311e8ed8ab4c9c927a Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 29 Aug 2024 16:13:30 +0300 Subject: [PATCH 07/10] updated layout --- .../AccountTokensTable/AccountTokensTable.tsx | 321 +++++++++--------- 1 file changed, 155 insertions(+), 166 deletions(-) diff --git a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx index 1e86e3e78..073c82dbf 100644 --- a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx +++ b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx @@ -78,177 +78,166 @@ export const AccountTokensTable = () => { }, [txCount, activeNetworkId, address]); return ( -
    -
    -
    -
    -
    -
    - -
    -
    - +
    +
    + +
    +
    + +
    +
    + +
    + + + + + + + + + + + + {isDataReady === undefined && ( + + + + )} + {isDataReady === false && ( + + + + )} + {isDataReady === true && ( + <> + {pagedTokens.length > 0 ? ( + <> + {pagedTokens.map((token) => { + const isValidDisplayValue = isValidTokenValue(token); -
    -
    + + + + + + + + + Portofolio %} /> - - +
    - - - - - - - - - - - {isDataReady === undefined && ( - - - - )} - {isDataReady === false && ( + return ( + + + + + + + + ); + })} + + ) : ( + <> - + - )} - {isDataReady === true && ( - <> - {pagedTokens.length > 0 ? ( - <> - {pagedTokens.map((token) => { - const isValidDisplayValue = - isValidTokenValue(token); - - return ( - - - - - - - - ); - })} - - ) : ( - <> - - - - - )} - - )} - -
    - - - - - - - - - - - -
    +
    + + + ({token.assets?.name ?? token.name}) + +
    +
    + + + {token.price ? ( +
    + + +
    + ) : ( + - + )} +
    + {isValidDisplayValue ? ( + + ) : ( + - + )} + + {token.portofolioPercentage && + token.portofolioPercentage.isGreaterThan(0) ? ( + + ) : ( + - + )} +
    -
    - - - ({token.assets?.name ?? token.name}) - -
    -
    - - - {token.price ? ( -
    - - -
    - ) : ( - - - )} -
    - {isValidDisplayValue ? ( - - ) : ( - - - )} - - {token.portofolioPercentage && - token.portofolioPercentage.isGreaterThan( - 0 - ) ? ( - - ) : ( - - - )} -
    -
    + + )} + + )} + + +
    -
    - - 0} - /> -
    -
    -
    +
    + + 0} + />
    ); From b19ee5d7361382a0512089b3306a0d85b54a658f Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 29 Aug 2024 17:00:58 +0300 Subject: [PATCH 08/10] fixes after review --- .../AccountTokensTable/AccountTokensTable.tsx | 21 +++++++------------ .../components/AccountTokensTableHeader.tsx | 14 ++++--------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx index 073c82dbf..a52689c63 100644 --- a/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx +++ b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx @@ -20,11 +20,7 @@ import { getItemsPage, isValidTokenValue } from 'helpers'; import { useAdapter, useGetPage } from 'hooks'; import { faCoins } from 'icons/solid'; import { AccountTabs } from 'layouts/AccountLayout/AccountTabs'; -import { - activeNetworkSelector, - accountSelector, - accountExtraSelector -} from 'redux/selectors'; +import { activeNetworkSelector, accountSelector } from 'redux/selectors'; import { TokenType, SortOrderEnum } from 'types'; import { AccountTokensTableHeader } from './components'; @@ -40,8 +36,6 @@ const ColSpanWrapper = ({ children }: { children: React.ReactNode }) => ( export const AccountTokensTable = () => { const { id: activeNetworkId } = useSelector(activeNetworkSelector); const { account } = useSelector(accountSelector); - const { isFetched: isAccountExtraFetched } = - useSelector(accountExtraSelector); const { txCount } = account; const { getAccountTokens } = useAdapter(); const { page, size } = useGetPage(); @@ -50,17 +44,16 @@ export const AccountTokensTable = () => { const [isDataReady, setIsDataReady] = useState(); const [accountTokens, setAccountTokens] = useState([]); - const fetchAccountTokens = () => { - getAccountTokens({ + const fetchAccountTokens = async () => { + const { data, success } = await getAccountTokens({ address, includeMetaESDT: true, size: MAX_RESULTS - }).then((accountTokensData) => { - if (accountTokensData.success) { - setAccountTokens(accountTokensData.data); - } - setIsDataReady(accountTokensData.success); }); + if (success && data) { + setAccountTokens(data); + } + setIsDataReady(success); }; const hasValidValues = accountTokens.some((token) => diff --git a/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx b/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx index cd919c2a3..d87167aa2 100644 --- a/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx +++ b/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx @@ -14,7 +14,7 @@ export const AccountTokensTableHeader = ({ const [searchParams, setSearchParams] = useSearchParams(); const { type } = Object.fromEntries(searchParams); - const updateTokenType = (typeValue?: TokenTypeEnum) => { + const updateTokenType = (typeValue?: TokenTypeEnum) => () => { const { type, page, size, ...rest } = Object.fromEntries(searchParams); const nextUrlParams = { ...rest, @@ -31,9 +31,7 @@ export const AccountTokensTableHeader = ({