Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add portfolio price change #1641

Merged
merged 7 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/extension-polkagate/src/components/FormatPrice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import React, { useMemo } from 'react';

import { useCurrency } from '../hooks';
import { ASSETS_AS_CURRENCY_LIST } from '../util/currencyList';
import { amountToHuman } from '../util/utils';
import { amountToHuman, fixFloatingPoint } from '../util/utils';

interface Props {
amount?: BN | null;
decimalPoint?: number;
decimals?: number;
commify?: boolean;
fontSize?: string;
fontWeight?: number;
lineHeight?: number;
Expand Down Expand Up @@ -53,7 +54,7 @@ export function nFormatter (num: number, decimalPoint: number) {

const DECIMAL_POINTS_FOR_CRYPTO_AS_CURRENCY = 4;

function FormatPrice ({ amount, decimalPoint = 2, decimals, fontSize, fontWeight, height, lineHeight = 1, mt = '0px', num, price, sign, skeletonHeight = 15, textAlign = 'left', textColor, width = '90px' }: Props): React.ReactElement<Props> {
function FormatPrice ({ amount, commify, decimalPoint = 2, decimals, fontSize, fontWeight, height, lineHeight = 1, mt = '0px', num, price, sign, skeletonHeight = 15, textAlign = 'left', textColor, width = '90px' }: Props): React.ReactElement<Props> {
const currency = useCurrency();

const total = useMemo(() => {
Expand Down Expand Up @@ -90,7 +91,7 @@ function FormatPrice ({ amount, decimalPoint = 2, decimals, fontSize, fontWeight
lineHeight={lineHeight}
sx={{ color: textColor }}
>
{sign || currency?.sign || ''}{nFormatter(total as number, _decimalPoint)}
{sign || currency?.sign || ''}{ commify ? fixFloatingPoint(total as number, _decimalPoint, true) : nFormatter(total as number, _decimalPoint)}
</Typography>
: <Skeleton
animation='wave'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import { AccountsAssetsContext, AssetLogo } from '../../../components';
import FormatPrice from '../../../components/FormatPrice';
import { usePrices, useTranslation, useYouHave } from '../../../hooks';
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 } from '../../../util/utils';
import { amountToHuman, fixFloatingPoint } from '../../../util/utils';
import Chart from './Chart';

interface Props {
Expand Down Expand Up @@ -121,7 +122,7 @@ 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),
parseFloat(amountToHuman(balance, decimal)) * (assetPrice ?? 0),
[]);

const formatNumber = useCallback(
Expand All @@ -140,13 +141,13 @@ function TotalBalancePieChart ({ hideNumbers, setGroupedAssets }: Props): React.
Object.keys(balances).forEach((address) => {
Object.keys(balances?.[address]).forEach((genesisHash) => {
if (!TEST_NETS.includes(genesisHash)) {
//@ts-ignore
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider using a more stable alternative to Object.groupBy.

The Object.groupBy method is an experimental feature that might not be supported in all environments. Consider using a more stable alternative like reduce or lodash.groupBy.

Additionally, the @ts-ignore comments might be hiding potential type issues. Consider addressing the underlying type problems instead.

Here's a suggested implementation using reduce:

- // @ts-ignore
- const groupedAssets = Object.groupBy(allAccountsAssets, ({ genesisHash, token }: { genesisHash: string, token: string }) => `${token}_${genesisHash}`);
+ const groupedAssets = allAccountsAssets.reduce((acc, asset) => {
+   const key = `${asset.token}_${asset.genesisHash}`;
+   acc[key] = acc[key] || [];
+   acc[key].push(asset);
+   return acc;
+ }, {} as Record<string, typeof allAccountsAssets>);

Also applies to: 150-150

allAccountsAssets = allAccountsAssets.concat(balances[address][genesisHash]);
}
});
});

//@ts-ignore
// @ts-ignore
const groupedAssets = Object.groupBy(allAccountsAssets, ({ genesisHash, token }: { genesisHash: string, token: string }) => `${token}_${genesisHash}`);
const aggregatedAssets = Object.keys(groupedAssets).map((index) => {
const assetSample = groupedAssets[index][0] as AssetsWithUiAndPrice;
Expand Down Expand Up @@ -193,25 +194,32 @@ function TotalBalancePieChart ({ hideNumbers, setGroupedAssets }: Props): React.
const toggleAssets = useCallback(() => setShowMore(!showMore), [showMore]);

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)', height: 'fit-content', p: '15px 25px 10px', width: '430px' }}>
<Grid alignItems='center' container gap='15px' item justifyContent='center'>
<Typography sx={{ fontSize: '28px', fontVariant: 'small-caps', fontWeight: 400 }}>
<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 item justifyContent='flex-start'>
<Typography sx={{ fontSize: '24px', fontVariant: 'small-caps', fontWeight: 400 }}>
{t('My Portfolio')}
</Typography>
{hideNumbers || hideNumbers === undefined
? <Box
component='img'
src={(theme.palette.mode === 'dark' ? stars6White : stars6Black) as string}
sx={{ height: '60px', width: '154px' }}
/>
: <FormatPrice
fontSize='40px'
fontWeight={700}
lineHeight={1.5}
num={youHave?.portfolio}
textColor= { isPriceOutdated(youHave) ? 'primary.light' : 'text.primary'}
/>
}
<Grid alignItems='center' container item justifyContent = 'space-between' sx={{ my: '13px' }}>
{hideNumbers || hideNumbers === undefined || !youHave
? <Box
component='img'
src={(theme.palette.mode === 'dark' ? stars6White : stars6Black) as string}
sx={{ height: '40px', width: '154px' }}
/>
: <>
<FormatPrice
commify
fontSize='34px'
fontWeight={700}
num={youHave?.portfolio}
textColor= { isPriceOutdated(youHave) ? 'primary.light' : 'text.primary'}
/>
<Typography sx={{ color: youHave.change > 0 ? 'success.main' : 'warning.main', fontSize: '20px', fontWeight: 500 }}>
{youHave.change > 0 ? '+' : '-'} { fixFloatingPoint(youHave?.change, 2, true)} {`(${COIN_GECKO_PRICE_CHANGE_DURATION}h)`}
</Typography>
</>
}
</Grid>
</Grid>
{youHave?.portfolio !== 0 && assets && assets.length > 0 &&
<Grid container item sx={{ borderTop: '1px solid', borderTopColor: 'divider', pt: '10px' }}>
Expand Down
27 changes: 21 additions & 6 deletions packages/extension-polkagate/src/hooks/useYouHave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { amountToHuman } from '../util/utils';
import { usePrices } from '.';

export interface YouHaveType {
portfolio: number,
date: number
change: number;
date: number;
portfolio: number;
}

/**
Expand All @@ -23,11 +24,19 @@ export default function useYouHave (): YouHaveType | undefined | null {
const pricesInCurrencies = usePrices();
const { accountsAssets } = useContext(AccountsAssetsContext);

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

const calcChange = useCallback((currentAssetPrice: number, change: number) => {
if (change === -100) {
return 0;
}

return currentAssetPrice && change ? currentAssetPrice / (1 + (change / 100)) : 0;
}, []);

const youHave = useMemo(() => {
if (!accountsAssets?.balances) {
return null;
Expand All @@ -38,19 +47,25 @@ export default function useYouHave (): YouHaveType | undefined | null {
}

let totalPrice = 0;
let totalBeforeChange = 0;
const balances = accountsAssets.balances;
const date = Math.min(accountsAssets.timeStamp, pricesInCurrencies.date);

Object.keys(balances).forEach((address) => {
Object.keys(balances?.[address]).forEach((genesisHash) => {
balances?.[address]?.[genesisHash].forEach((asset) => {
totalPrice += calPrice(pricesInCurrencies.prices[asset.priceId]?.value ?? 0, asset.totalBalance, asset.decimal);
const currentAssetPrice = calcPrice(pricesInCurrencies.prices[asset.priceId]?.value ?? 0, asset.totalBalance, asset.decimal);

totalPrice += currentAssetPrice;
totalBeforeChange += calcChange(currentAssetPrice, pricesInCurrencies.prices[asset.priceId]?.change);
});
});
});

return { date, portfolio: totalPrice } as unknown as YouHaveType;
}, [accountsAssets, calPrice, pricesInCurrencies]);
const change = totalPrice - totalBeforeChange;

return { change, date, portfolio: totalPrice } as unknown as YouHaveType;
}, [accountsAssets, calcChange, calcPrice, pricesInCurrencies]);

return youHave;
}
4 changes: 3 additions & 1 deletion packages/extension-polkagate/src/util/api/getPrices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ export const EXTRA_PRICE_IDS: Record<string, string> = {
pendulum: 'pendulum-chain'
};

export const COIN_GECKO_PRICE_CHANGE_DURATION = 24;

export default async function getPrices (priceIds: string[], currencyCode = 'usd') {
console.log(' getting prices for:', priceIds.sort());

const revisedPriceIds = priceIds.map((item) => (EXTRA_PRICE_IDS[item] || item));

const prices = await getReq(`https://api.coingecko.com/api/v3/simple/price?ids=${revisedPriceIds}&vs_currencies=${currencyCode}&include_24hr_change=true`, {});
const prices = await getReq(`https://api.coingecko.com/api/v3/simple/price?ids=${revisedPriceIds}&vs_currencies=${currencyCode}&include_${COIN_GECKO_PRICE_CHANGE_DURATION}hr_change=true`, {});

const outputObjectPrices: PricesType = {};

Expand Down
Loading