diff --git a/src/appConstants/apiFields.ts b/src/appConstants/apiFields.ts
index 2f760344f..f67264a97 100644
--- a/src/appConstants/apiFields.ts
+++ b/src/appConstants/apiFields.ts
@@ -46,3 +46,19 @@ export const NODE_STATUS_PREVIEW_FIELDS = [
'auctionQualified',
'isInDangerZone'
];
+
+export const ACCOUNT_TOKENS_FIELDS = [
+ 'type',
+ 'identifier',
+ 'collection',
+ 'name',
+ 'ticker',
+ 'decimals',
+ 'assets',
+ 'price',
+ 'totalLiquidity',
+ 'isLowLiquidity',
+ 'lowLiquidityThresholdPercent',
+ 'balance',
+ 'valueUsd'
+];
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/components/LowLiquidityTooltip/LowLiquidityTooltip.tsx b/src/components/LowLiquidityTooltip/LowLiquidityTooltip.tsx
index cff25e9bf..4cd7c6ea0 100644
--- a/src/components/LowLiquidityTooltip/LowLiquidityTooltip.tsx
+++ b/src/components/LowLiquidityTooltip/LowLiquidityTooltip.tsx
@@ -1,4 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import BigNumber from 'bignumber.js';
import classNames from 'classnames';
import { FormatUSD, Overlay } from 'components';
import { faSquareInfo } from 'icons/solid';
@@ -18,17 +19,23 @@ export const LowLiquidityTooltip = ({
return null;
}
- const { totalLiquidity, isLowLiquidity } = token;
+ const { totalLiquidity, isLowLiquidity, lowLiquidityThresholdPercent } =
+ token;
+
if (!isLowLiquidity) {
return null;
}
+ const displayTresholdPercent = new BigNumber(
+ lowLiquidityThresholdPercent ?? 0.5
+ ).toFormat();
+
return (
- Less than 0.5% of total Token Supply captured in xExchange Liquidity
- Pools.
+ Less than {displayTresholdPercent}% of total Token Supply captured in
+ xExchange Liquidity Pools.
{showTotalLiquidity && totalLiquidity && (
<>
()
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/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
new file mode 100644
index 000000000..d56fcb14f
--- /dev/null
+++ b/src/pages/AccountDetails/AccountTokensTable/AccountTokensTable.tsx
@@ -0,0 +1,233 @@
+import { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { useParams } from 'react-router-dom';
+
+import { ZERO, MAX_RESULTS, ACCOUNT_TOKENS_FIELDS } from 'appConstants';
+import {
+ Pager,
+ PageSize,
+ PageState,
+ FormatAmount,
+ TokenLink,
+ FormatUSD,
+ LowLiquidityTooltip,
+ FormatNumber,
+ Sort,
+ Loader,
+ Overlay
+} from 'components';
+import { isValidTokenValue } from 'helpers';
+import { useAdapter } from 'hooks';
+import { faCoins } from 'icons/solid';
+import { AccountTabs } from 'layouts/AccountLayout/AccountTabs';
+import { activeNetworkSelector, accountSelector } from 'redux/selectors';
+import { TokenType, SortOrderEnum } from 'types';
+
+import { AccountTokensTableHeader } from './components';
+import { SortTokenFieldEnum } from './helpers';
+import { usePageTokens, useProcessTokens } from './hooks';
+
+const ColSpanWrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children} |
+
+);
+
+export const AccountTokensTable = () => {
+ const { id: activeNetworkId } = useSelector(activeNetworkSelector);
+ const { account } = useSelector(accountSelector);
+ const { txCount } = account;
+ const { getAccountTokens } = useAdapter();
+ const { hash: address } = useParams() as any;
+
+ const [isDataReady, setIsDataReady] = useState();
+ const [accountTokens, setAccountTokens] = useState([]);
+
+ const fetchAccountTokens = async () => {
+ const { data, success } = await getAccountTokens({
+ address,
+ includeMetaESDT: true,
+ size: MAX_RESULTS,
+ fields: ACCOUNT_TOKENS_FIELDS.join(',')
+ });
+ if (success && data) {
+ setAccountTokens(data);
+ }
+ setIsDataReady(success);
+ };
+
+ const hasValidValues = accountTokens.some((token) =>
+ isValidTokenValue(token)
+ );
+ const processedAccountTokens = useProcessTokens(accountTokens);
+ const pagedTokens = usePageTokens(processedAccountTokens);
+
+ useEffect(() => {
+ fetchAccountTokens();
+ }, [txCount, activeNetworkId, address]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ Portofolio %}
+ />
+ |
+
+
+
+ {isDataReady === undefined && (
+
+
+
+ )}
+ {isDataReady === false && (
+
+
+
+ )}
+ {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) ? (
+
+ ) : (
+ -
+ )}
+ |
+
+ );
+ })}
+ >
+ ) : (
+ <>
+
+
+
+ >
+ )}
+ >
+ )}
+
+
+
+
+
+
+ );
+};
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..d87167aa2
--- /dev/null
+++ b/src/pages/AccountDetails/AccountTokensTable/components/AccountTokensTableHeader.tsx
@@ -0,0 +1,89 @@
+import classNames from 'classnames';
+import { useSearchParams } from 'react-router-dom';
+
+import { Pager, TableSearch } from 'components';
+import { TokenTypeEnum } from 'types';
+
+export interface AccountTokensTableHeaderUIType {
+ tokenCount?: number;
+}
+
+export const AccountTokensTableHeader = ({
+ tokenCount = 0
+}: 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 (
+ <>
+
+
+
+
+
+ >
+ );
+};
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/helpers/filterTokens.ts b/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts
new file mode 100644
index 000000000..661a2b097
--- /dev/null
+++ b/src/pages/AccountDetails/AccountTokensTable/helpers/filterTokens.ts
@@ -0,0 +1,38 @@
+import { TokenTypeEnum } from 'types';
+
+import { ProcessedTokenType } from '../helpers';
+
+export interface FilterTokensType {
+ tokens: ProcessedTokenType[];
+ type?: TokenTypeEnum;
+ search?: string;
+}
+
+export const filterTokens = ({ tokens, type, search }: FilterTokensType) => {
+ const searchTerm = (term?: string) => {
+ 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 }) => {
+ 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..3902ce851
--- /dev/null
+++ b/src/pages/AccountDetails/AccountTokensTable/helpers/sortTokens.ts
@@ -0,0 +1,128 @@
+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;
+
+ 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;
+ }
+ }
+
+ 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..ac9358af0
--- /dev/null
+++ b/src/pages/AccountDetails/AccountTokensTable/hooks/index.ts
@@ -0,0 +1,2 @@
+export * from './useProcessTokens';
+export * from './usePageTokens';
diff --git a/src/pages/AccountDetails/AccountTokensTable/hooks/usePageTokens.ts b/src/pages/AccountDetails/AccountTokensTable/hooks/usePageTokens.ts
new file mode 100644
index 000000000..8727c4311
--- /dev/null
+++ b/src/pages/AccountDetails/AccountTokensTable/hooks/usePageTokens.ts
@@ -0,0 +1,19 @@
+import { useMemo } from 'react';
+
+import { getItemsPage } from 'helpers';
+import { useGetPage } from 'hooks';
+import { ProcessedTokenType } from '../helpers';
+
+export const usePageTokens = (accountTokens: ProcessedTokenType[]) => {
+ const { page, size } = useGetPage();
+
+ return useMemo(() => {
+ const processedTokens = getItemsPage({
+ items: accountTokens,
+ currentPage: page,
+ itemsPerPage: size
+ });
+
+ return processedTokens;
+ }, [accountTokens, page, size]);
+};
diff --git a/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts
new file mode 100644
index 000000000..5aa2de6fe
--- /dev/null
+++ b/src/pages/AccountDetails/AccountTokensTable/hooks/useProcessTokens.ts
@@ -0,0 +1,90 @@
+import { useMemo } from 'react';
+import BigNumber from 'bignumber.js';
+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, SortOrderEnum } from 'types';
+
+import {
+ filterTokens,
+ sortTokens,
+ ProcessedTokenType,
+ SortTokenFieldEnum
+} from '../helpers';
+
+export const useProcessTokens = (accountTokens: TokenType[]) => {
+ const dispatch = useDispatch();
+
+ const [searchParams] = useSearchParams();
+ const { hash: address } = useParams() as any;
+ const { accountExtra, isFetched: isAccountExtraFetched } =
+ useSelector(accountExtraSelector);
+ const { address: extraAddress } = accountExtra;
+ const { search } = useGetSearch();
+ const { sort, order } = useGetSort();
+ const { type } = Object.fromEntries(searchParams);
+
+ const validTokenValues = accountTokens.filter((token: TokenType) =>
+ isValidTokenValue(token)
+ );
+ const tokenBalance = getTotalTokenUsdValue(validTokenValues);
+
+ if (!isAccountExtraFetched && address === extraAddress) {
+ const accountExtraDetails = getInitialAccountExtraState().accountExtra;
+ accountExtraDetails.tokenBalance = tokenBalance;
+ dispatch(
+ setAccountExtra({
+ accountExtra: { ...accountExtraDetails, address },
+ isFetched: true
+ })
+ );
+ }
+
+ const processTokens = ({ tokens }: { tokens: ProcessedTokenType[] }) => {
+ const filteredTokens = filterTokens({
+ tokens,
+ type: type as TokenTypeEnum,
+ 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: currentSort as SortTokenFieldEnum,
+ order: currentOrder,
+ tokenBalance
+ });
+
+ return sortedTokens;
+ };
+
+ const processedAccountTokens = useMemo(() => {
+ const processedSortArray = accountTokens.map((token) => {
+ const portofolioPercentage =
+ token.valueUsd && tokenBalance
+ ? 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;
+};
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/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/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,
diff --git a/src/types/token.types.ts b/src/types/token.types.ts
index 3f6cc0405..6caf22c16 100644
--- a/src/types/token.types.ts
+++ b/src/types/token.types.ts
@@ -28,6 +28,7 @@ export interface TokenType {
assets?: TokenAssetType;
totalLiquidity?: number;
isLowLiquidity?: boolean;
+ lowLiquidityThresholdPercent?: number;
transfersCount?: number;
roles?: TokenRolesType[];
}
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 (
|