Skip to content

Commit

Permalink
feat: display portfolio change in account detail (#1644)
Browse files Browse the repository at this point in the history
Co-authored-by: Nick <[email protected]>
  • Loading branch information
AMIRKHANEF and Nick-1979 authored Nov 11, 2024
1 parent e0e0b3a commit e30ef9c
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ interface BalanceRowJSXType {
}

const BalanceRow = ({ balanceToShow, isBalanceOutdated, isPriceOutdated, price }: BalanceRowJSXType) => (
<Grid alignItems='center' container fontSize='28px' item xs>
<Grid alignItems='center' container fontSize='26px' item xs>
<Balance balanceToShow={balanceToShow} isBalanceOutdated={isBalanceOutdated} />
<Divider orientation='vertical' sx={{ backgroundColor: 'divider', height: '30px', mx: '10px', my: 'auto' }} />
<Price balanceToShow={balanceToShow} isPriceOutdated={isPriceOutdated} price={price} />
Expand Down Expand Up @@ -106,7 +106,7 @@ const SelectedAssetBox = ({ balanceToShow, genesisHash, isBalanceOutdated, isPri
<Grid item pl='7px'>
<AssetLogo assetSize='42px' baseTokenSize='20px' genesisHash={balanceToShow?.genesisHash} logo={logoInfo?.logo} subLogo={logoInfo?.subLogo} />
</Grid>
<Grid item sx={{ fontSize: '28px', ml: '5px' }}>
<Grid item sx={{ ml: '5px' }}>
<BalanceRow balanceToShow={balanceToShow} isBalanceOutdated={isBalanceOutdated} isPriceOutdated={isPriceOutdated} price={price} />
</Grid>
</>
Expand Down Expand Up @@ -152,7 +152,6 @@ function AccountInformationForDetails ({ accountAssets, address, label, price, p
const theme = useTheme();
const { account, chain, genesisHash, token } = useInfo(address);


const calculatePrice = useCallback((amount: BN, decimal: number, _price: number) => {
return parseFloat(amountToHuman(amount, decimal)) * _price;
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@
/* eslint-disable sort-keys */
/* eslint-disable react/jsx-max-props-per-line */

import type { BN } from '@polkadot/util';
import type { FetchedBalance } from '../../../hooks/useAssetsBalances';
import type { Prices } from '../../../util/types';

import { Divider, Grid, Typography, useTheme } from '@mui/material';
import { Chart, registerables } from 'chart.js';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import CountUp from 'react-countup';

import { AssetLogo } from '../../../components';
import FormatPrice from '../../../components/FormatPrice';
import { useTranslation } from '../../../hooks';
import { useCurrency, useTranslation } from '../../../hooks';
import { calcChange, calcPrice } from '../../../hooks/useYouHave';
import { COIN_GECKO_PRICE_CHANGE_DURATION } from '../../../util/api/getPrices';
import { DEFAULT_COLOR } from '../../../util/constants';
import getLogo2 from '../../../util/getLogo2';
import { amountToHuman } from '../../../util/utils';
import { adjustColor } from '../../homeFullScreen/partials/TotalBalancePieChart';
import { countDecimalPlaces, fixFloatingPoint } from '../../../util/utils';
import { adjustColor, changeSign, PORTFOLIO_CHANGE_DECIMAL } from '../../homeFullScreen/partials/TotalBalancePieChart';

interface Props {
accountAssets: FetchedBalance[] | null | undefined;
Expand All @@ -35,29 +37,32 @@ export default function TotalChart ({ accountAssets, pricesInCurrency }: Props):
const { t } = useTranslation();
const theme = useTheme();
const chartRef = useRef(null);
const currency = useCurrency();

Chart.register(...registerables);

const calPrice = useCallback((assetPrice: number | undefined, balance: BN, decimal: number) => parseFloat(amountToHuman(balance, decimal)) * (assetPrice ?? 0), []);

const priceOf = useCallback((priceId: string): number => pricesInCurrency?.prices?.[priceId]?.value || 0, [pricesInCurrency?.prices]);
const changePriceOf = useCallback((priceId: string): number => pricesInCurrency?.prices?.[priceId]?.change || 0, [pricesInCurrency?.prices]);
const formatNumber = useCallback((num: number): number => parseFloat(Math.trunc(num) === 0 ? num.toFixed(2) : num.toFixed(1)), []);

const { assets, totalWorth } = useMemo(() => {
const { assets, totalChange, totalWorth } = useMemo(() => {
if (accountAssets?.length) {
const _assets = accountAssets as unknown as AssetsToShow[];

let total = 0;
let totalChange = 0;

/** to add asset's worth and color */
accountAssets.forEach((asset, index) => {
const assetWorth = calPrice(priceOf(asset.priceId), asset.totalBalance, asset.decimal);
const assetWorth = calcPrice(priceOf(asset.priceId), asset.totalBalance, asset.decimal);
const assetColor = getLogo2(asset.genesisHash, asset.token)?.color || DEFAULT_COLOR;

_assets[index].worth = assetWorth;
_assets[index].color = adjustColor(asset.token, assetColor, theme);

total += assetWorth;

totalChange += calcChange(priceOf(asset.priceId), Number(asset.totalBalance) / (10 ** asset.decimal), changePriceOf(asset.priceId));
});

/** to add asset's percentage */
Expand All @@ -70,11 +75,11 @@ export default function TotalChart ({ accountAssets, pricesInCurrency }: Props):
_assets.sort((a, b) => b.worth - a.worth);
const nonZeroAssets = _assets.filter((asset) => asset.worth > 0);

return { assets: nonZeroAssets, totalWorth: total };
return { assets: nonZeroAssets, totalChange, totalWorth: total };
}

return { assets: undefined, totalWorth: undefined };
}, [accountAssets, calPrice, formatNumber, priceOf, theme]);
return { assets: undefined, totalChange: 0, totalWorth: undefined };
}, [accountAssets, changePriceOf, formatNumber, priceOf, theme]);

useEffect(() => {
const worths = assets?.map(({ worth }) => worth);
Expand Down Expand Up @@ -111,21 +116,46 @@ export default function TotalChart ({ accountAssets, pricesInCurrency }: Props):
return () => {
chartInstance.destroy();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [assets?.length, theme.palette.divider]);

const accountBalanceTotalChange = useMemo(() => {
if (!totalChange) {
return 0;
}

const value = fixFloatingPoint(totalChange, PORTFOLIO_CHANGE_DECIMAL, false, true);

return parseFloat(value);
}, [totalChange]);

return (
<Grid alignItems='center' container direction='column' item justifyContent='center' sx={{ bgcolor: 'background.paper', borderRadius: '5px', boxShadow: '2px 3px 4px 0px rgba(0, 0, 0, 0.1)', maxHeight: '185px', p: '15px', width: 'inherit' }}>
<Grid alignItems='center' container gap='15px' item justifyContent='center' height='54px'>
<Typography fontSize='18px' fontWeight={400}>
{t('Total')}
</Typography>
<FormatPrice
fontSize='36px'
fontWeight={700}
num={totalWorth}
skeletonHeight={22}
/>
<Grid alignItems='center' container direction='column' item justifyContent='center' sx={{ bgcolor: 'background.paper', borderRadius: '5px', boxShadow: '2px 3px 4px 0px rgba(0, 0, 0, 0.1)', p: '10px 15px', width: 'inherit' }}>
<Grid alignItems='flex-start' container item justifyContent='space-between' mb='10px'>
<Grid alignItems='center' item>
<Typography sx={{ fontSize: '22px', fontVariant: 'small-caps', fontWeight: 400 }}>
{t('Total')}
</Typography>
</Grid>
<Grid alignItems='center' item justifyItems='flex-end'>
<FormatPrice
commify
fontSize='28px'
fontWeight={600}
num={totalWorth}
skeletonHeight={22}
withCountUp
/>
<Typography sx={{ color: !totalChange ? 'secondary.contrastText' : totalChange > 0 ? 'success.main' : 'warning.main', fontSize: '16px', fontWeight: 500 }}>
<CountUp
decimals={countDecimalPlaces(accountBalanceTotalChange) || PORTFOLIO_CHANGE_DECIMAL}
duration={1}
end={accountBalanceTotalChange}
prefix={`${changeSign(totalChange)}${currency?.sign}`}
suffix={`(${COIN_GECKO_PRICE_CHANGE_DURATION}h)`}
/>
</Typography>
</Grid>
</Grid>
{assets && assets.length > 0 &&
<Grid container item sx={{ borderTop: '1px solid', borderTopColor: 'divider', py: '5px' }}>
Expand Down Expand Up @@ -153,7 +183,8 @@ export default function TotalChart ({ accountAssets, pricesInCurrency }: Props):
})
}
</Grid>
</Grid>}
</Grid>
}
</Grid>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import { stars6Black, stars6White } from '../../../assets/icons';
import { AccountsAssetsContext, AssetLogo } from '../../../components';
import FormatPrice from '../../../components/FormatPrice';
import { useCurrency, usePrices, useTranslation, useYouHave } from '../../../hooks';
import { calcPrice } from '../../../hooks/useYouHave';
import { isPriceOutdated } from '../../../popup/home/YouHave';
import { COIN_GECKO_PRICE_CHANGE_DURATION } from '../../../util/api/getPrices';
import { DEFAULT_COLOR, TEST_NETS, TOKENS_WITH_BLACK_LOGO } from '../../../util/constants';
import getLogo2 from '../../../util/getLogo2';
import { amountToHuman, countDecimalPlaces, fixFloatingPoint } from '../../../util/utils';
import { countDecimalPlaces, fixFloatingPoint } from '../../../util/utils';
import Chart from './Chart';

interface Props {
Expand Down Expand Up @@ -57,6 +58,12 @@ export interface AssetsWithUiAndPrice {
votingBalance?: BN
}

export const PORTFOLIO_CHANGE_DECIMAL = 2;

export const changeSign = (change: number | undefined) => !change
? ''
: change > 0 ? '+ ' : '- ';

export function adjustColor (token: string, color: string | undefined, theme: Theme): string {
if (color && (TOKENS_WITH_BLACK_LOGO.find((t) => t === token) && theme.palette.mode === 'dark')) {
const cleanedColor = color.replace(/^#/, '');
Expand Down Expand Up @@ -123,10 +130,6 @@ function TotalBalancePieChart ({ hideNumbers, setGroupedAssets }: Props): React.

const [showMore, setShowMore] = useState<boolean>(false);

const calPrice = useCallback((assetPrice: number | undefined, balance: BN, decimal: number) =>
parseFloat(amountToHuman(balance, decimal)) * (assetPrice ?? 0),
[]);

const formatNumber = useCallback(
(num: number, decimal = 2) =>
parseFloat(Math.trunc(num) === 0 ? num.toFixed(decimal) : num.toFixed(1))
Expand Down Expand Up @@ -157,7 +160,7 @@ function TotalBalancePieChart ({ hideNumbers, setGroupedAssets }: Props): React.
const assetPrice = pricesInCurrencies.prices[assetSample.priceId]?.value;
const accumulatedPricePerAsset = groupedAssets[index].reduce((sum: BN, { totalBalance }: FetchedBalance) => sum.add(new BN(totalBalance)), BN_ZERO) as BN;

const balancePrice = calPrice(assetPrice, accumulatedPricePerAsset, assetSample.decimal ?? 0);
const balancePrice = calcPrice(assetPrice, accumulatedPricePerAsset, assetSample.decimal ?? 0);

const _percent = (balancePrice / youHave.portfolio) * 100;

Expand Down Expand Up @@ -187,7 +190,7 @@ function TotalBalancePieChart ({ hideNumbers, setGroupedAssets }: Props): React.
});

return aggregatedAssets;
}, [accountsAssets, youHave, calPrice, formatNumber, pricesInCurrencies, theme]);
}, [accountsAssets, youHave, formatNumber, pricesInCurrencies, theme]);

useEffect(() => {
assets && setGroupedAssets([...assets]);
Expand All @@ -200,17 +203,13 @@ function TotalBalancePieChart ({ hideNumbers, setGroupedAssets }: Props): React.
return 0;
}

const value = fixFloatingPoint(youHave.change, 2, false, true);
const value = fixFloatingPoint(youHave.change, PORTFOLIO_CHANGE_DECIMAL, false, true);

return parseFloat(value);
}, [youHave?.change]);

const changeSign = !youHave?.change
? ''
: youHave.change > 0 ? '+ ' : '- ';

return (
<Grid alignItems='flex-start' container direction='column' item justifyContent='flex-start' sx={{ bgcolor: 'background.paper', borderRadius: '5px', boxShadow: '2px 3px 4px 0px rgba(0, 0, 0, 0.1)', minHeight: '287px', p: '15px 25px 10px', width: '430px' }}>
<Grid alignItems='flex-start' container direction='column' item justifyContent='flex-start' sx={{ bgcolor: 'background.paper', borderRadius: '5px', boxShadow: '2px 3px 4px 0px rgba(0, 0, 0, 0.1)', height: 'fit-content', p: '15px 25px 10px', width: '430px' }}>
<Grid alignItems='flex-start' container item justifyContent='flex-start'>
<Typography sx={{ fontSize: '23px', fontVariant: 'small-caps', fontWeight: 400 }}>
{t('My Portfolio')}
Expand All @@ -233,10 +232,10 @@ function TotalBalancePieChart ({ hideNumbers, setGroupedAssets }: Props): React.
/>
<Typography sx={{ color: !youHave.change ? 'secondary.contrastText' : youHave.change > 0 ? 'success.main' : 'warning.main', fontSize: '18px', fontWeight: 500 }}>
<CountUp
decimals={countDecimalPlaces(portfolioChange) || 2}
decimals={countDecimalPlaces(portfolioChange) || PORTFOLIO_CHANGE_DECIMAL}
duration={1}
end={portfolioChange}
prefix={`${changeSign}${currency?.sign}`}
prefix={`${changeSign(youHave?.change)}${currency?.sign}`}
suffix={`(${COIN_GECKO_PRICE_CHANGE_DURATION}h)`}
/>
</Typography>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

/* eslint-disable react/jsx-max-props-per-line */

import type { Prices } from '../../../util/types';
import type { CurrencyItemType } from './Currency';
import type { AssetsWithUiAndPrice } from './TotalBalancePieChart';

import { ArrowDropDown as ArrowDropDownIcon, ArrowDropDown as DownIcon, ArrowDropUp as UpIcon } from '@mui/icons-material';
Expand All @@ -15,13 +17,16 @@ import { useCurrency, usePrices, useTranslation } from '../../../hooks';
import getLogo2 from '../../../util/getLogo2';

interface Props {
groupedAssets: AssetsWithUiAndPrice[] | undefined
groupedAssets: AssetsWithUiAndPrice[] | undefined;
}

const AssetPriceChange = ({ asset }: { asset: AssetsWithUiAndPrice }) => {
const currency = useCurrency();
const pricesInCurrencies = usePrices();
interface AssetPriceChangeProps {
asset: AssetsWithUiAndPrice;
currency: CurrencyItemType | undefined;
pricesInCurrencies: Prices | null | undefined;
}

const AssetPriceChange = React.memo(function AssetPriceChange ({ asset, currency, pricesInCurrencies }: AssetPriceChangeProps) {
const logoInfo = useMemo(() => asset && getLogo2(asset.genesisHash, asset.token), [asset]);
const change = pricesInCurrencies ? pricesInCurrencies.prices[asset.priceId]?.change : undefined;

Expand Down Expand Up @@ -53,11 +58,13 @@ const AssetPriceChange = ({ asset }: { asset: AssetsWithUiAndPrice }) => {
</Grid>
</Grid>
);
};
});

function WatchList ({ groupedAssets }: Props): React.ReactElement {
const { t } = useTranslation();
const theme = useTheme();
const currency = useCurrency();
const pricesInCurrencies = usePrices();

const [showMore, setShowMore] = useState<boolean>(false);

Expand All @@ -83,7 +90,9 @@ function WatchList ({ groupedAssets }: Props): React.ReactElement {
{uniqueAssets.slice(0, 3).map((asset, index) => (
<AssetPriceChange
asset={asset}
currency={currency}
key={index}
pricesInCurrencies={pricesInCurrencies}
/>
))}
{uniqueAssets.length > 3 &&
Expand All @@ -92,7 +101,9 @@ function WatchList ({ groupedAssets }: Props): React.ReactElement {
{uniqueAssets.slice(3).map((asset, index) => (
<AssetPriceChange
asset={asset}
currency={currency}
key={index}
pricesInCurrencies={pricesInCurrencies}
/>
))}
</Collapse>
Expand Down
Loading

0 comments on commit e30ef9c

Please sign in to comment.