✨ added portfolio summary for all accounts (#512)
This commit is contained in:
parent
fac07787c5
commit
44196f1a10
@ -1,26 +1,16 @@
|
||||
import React, { Suspense, useMemo } from 'react'
|
||||
|
||||
import HealthBar from 'components/Account/HealthBar'
|
||||
import Card from 'components/Card'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Heart } from 'components/Icons'
|
||||
import Loading from 'components/Loading'
|
||||
import Text from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import Skeleton from 'components/Portfolio/SummarySkeleton'
|
||||
import { MAX_AMOUNT_DECIMALS } from 'constants/math'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useAccount from 'hooks/useAccount'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
import useHealthComputer from 'hooks/useHealthComputer'
|
||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import {
|
||||
calculateAccountApr,
|
||||
calculateAccountLeverage,
|
||||
getAccountPositionValues,
|
||||
} from 'utils/accounts'
|
||||
import { getAccountSummaryStats } from 'utils/accounts'
|
||||
import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants'
|
||||
|
||||
interface Props {
|
||||
accountId: string
|
||||
@ -34,40 +24,27 @@ function Content(props: Props) {
|
||||
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
|
||||
|
||||
const stats = useMemo(() => {
|
||||
if (!account || !borrowAssets.length || !lendingAssets.length) return STATS
|
||||
if (!account || !borrowAssets.length || !lendingAssets.length) return DEFAULT_PORTFOLIO_STATS
|
||||
|
||||
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices)
|
||||
const positionValue = deposits.plus(lends).plus(vaults)
|
||||
const apr = calculateAccountApr(account, borrowAssets, lendingAssets, prices)
|
||||
const leverage = calculateAccountLeverage(account, prices)
|
||||
const { positionValue, debts, netWorth, apr, leverage } = getAccountSummaryStats(
|
||||
account,
|
||||
prices,
|
||||
borrowAssets,
|
||||
lendingAssets,
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
title: (
|
||||
<DisplayCurrency
|
||||
className='text-xl'
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue)}
|
||||
/>
|
||||
),
|
||||
sub: STATS[0].sub,
|
||||
title: <DisplayCurrency className='text-xl' coin={positionValue} />,
|
||||
sub: DEFAULT_PORTFOLIO_STATS[0].sub,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<DisplayCurrency
|
||||
className='text-xl'
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, debts)}
|
||||
/>
|
||||
),
|
||||
sub: STATS[1].sub,
|
||||
title: <DisplayCurrency className='text-xl' coin={debts} />,
|
||||
sub: DEFAULT_PORTFOLIO_STATS[1].sub,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<DisplayCurrency
|
||||
className='text-xl'
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue.minus(debts))}
|
||||
/>
|
||||
),
|
||||
sub: STATS[2].sub,
|
||||
title: <DisplayCurrency className='text-xl' coin={netWorth} />,
|
||||
sub: DEFAULT_PORTFOLIO_STATS[2].sub,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
@ -81,7 +58,7 @@ function Content(props: Props) {
|
||||
}}
|
||||
/>
|
||||
),
|
||||
sub: STATS[3].sub,
|
||||
sub: DEFAULT_PORTFOLIO_STATS[3].sub,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
@ -91,56 +68,18 @@ function Content(props: Props) {
|
||||
options={{ suffix: 'x' }}
|
||||
/>
|
||||
),
|
||||
sub: STATS[4].sub,
|
||||
sub: DEFAULT_PORTFOLIO_STATS[4].sub,
|
||||
},
|
||||
]
|
||||
}, [account, borrowAssets, lendingAssets, prices])
|
||||
|
||||
return <Skeleton stats={stats} health={health} {...props} />
|
||||
return <Skeleton stats={stats} health={health} title={`Credit account ${props.accountId}`} />
|
||||
}
|
||||
|
||||
export default function Summary(props: Props) {
|
||||
return (
|
||||
<Suspense fallback={<Skeleton stats={STATS} health={0} {...props} />}>
|
||||
<Suspense fallback={<Skeleton health={0} title={`Credit account ${props.accountId}`} />}>
|
||||
<Content {...props} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
interface SkeletonProps extends Props {
|
||||
stats: { title: React.ReactNode | null; sub: string }[]
|
||||
health: number
|
||||
}
|
||||
|
||||
function Skeleton(props: SkeletonProps) {
|
||||
return (
|
||||
<div className='flex flex-col w-full gap-8'>
|
||||
<div className='flex justify-between'>
|
||||
<Text size='2xl'>Credit Account {props.accountId}</Text>
|
||||
<div className='flex gap-1 max-w-[300px] flex-grow'>
|
||||
<Heart width={20} />
|
||||
<HealthBar health={props.health} className='h-full' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='grid grid-cols-2 gap-4 lg:grid-cols-5 md:grid-cols-4 sm:grid-cols-3'>
|
||||
{props.stats.map((stat) => (
|
||||
<Card key={stat.sub} className='p-6 text-center bg-white/5 flex-grow-1'>
|
||||
<TitleAndSubCell
|
||||
title={stat.title || <Loading className='w-20 h-6 mx-auto mb-2' />}
|
||||
sub={stat.sub}
|
||||
className='mb-1'
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const STATS = [
|
||||
{ title: null, sub: 'Total Balance' },
|
||||
{ title: null, sub: 'Total Debt' },
|
||||
{ title: null, sub: 'Net Worth' },
|
||||
{ title: null, sub: 'APR' },
|
||||
{ title: null, sub: 'Account Leverage' },
|
||||
]
|
||||
|
93
src/components/Portfolio/Overview/Summary.tsx
Normal file
93
src/components/Portfolio/Overview/Summary.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import SummarySkeleton from 'components/Portfolio/SummarySkeleton'
|
||||
import { MAX_AMOUNT_DECIMALS } from 'constants/math'
|
||||
import useAccounts from 'hooks/useAccounts'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { getAccountSummaryStats } from 'utils/accounts'
|
||||
import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants'
|
||||
|
||||
export default function PortfolioSummary() {
|
||||
const { address: urlAddress } = useParams()
|
||||
const walletAddress = useStore((s) => s.address)
|
||||
const { data: prices } = usePrices()
|
||||
const { allAssets: borrowAssets } = useBorrowMarketAssetsTableData()
|
||||
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
|
||||
const { data: accounts } = useAccounts(urlAddress || walletAddress)
|
||||
|
||||
const stats = useMemo(() => {
|
||||
if (!accounts?.length) return
|
||||
const combinedAccount = accounts.reduce(
|
||||
(combinedAccount, account) => {
|
||||
combinedAccount.debts = combinedAccount.debts.concat(account.debts)
|
||||
combinedAccount.deposits = combinedAccount.deposits.concat(account.deposits)
|
||||
combinedAccount.lends = combinedAccount.lends.concat(account.lends)
|
||||
combinedAccount.vaults = combinedAccount.vaults.concat(account.vaults)
|
||||
return combinedAccount
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
deposits: [],
|
||||
lends: [],
|
||||
debts: [],
|
||||
vaults: [],
|
||||
} as Account,
|
||||
)
|
||||
|
||||
const { positionValue, debts, netWorth, apr, leverage } = getAccountSummaryStats(
|
||||
combinedAccount,
|
||||
prices,
|
||||
borrowAssets,
|
||||
lendingAssets,
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
title: <DisplayCurrency className='text-xl' coin={positionValue} />,
|
||||
sub: DEFAULT_PORTFOLIO_STATS[0].sub,
|
||||
},
|
||||
{
|
||||
title: <DisplayCurrency className='text-xl' coin={debts} />,
|
||||
sub: DEFAULT_PORTFOLIO_STATS[1].sub,
|
||||
},
|
||||
{
|
||||
title: <DisplayCurrency className='text-xl' coin={netWorth} />,
|
||||
sub: DEFAULT_PORTFOLIO_STATS[2].sub,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<FormattedNumber
|
||||
className='text-xl'
|
||||
amount={apr.toNumber()}
|
||||
options={{
|
||||
suffix: '%',
|
||||
maxDecimals: apr.abs().isLessThan(0.1) ? MAX_AMOUNT_DECIMALS : 2,
|
||||
minDecimals: 2,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
sub: 'Combined APR',
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<FormattedNumber
|
||||
className='text-xl'
|
||||
amount={leverage.toNumber()}
|
||||
options={{ suffix: 'x' }}
|
||||
/>
|
||||
),
|
||||
sub: 'Combined leverage',
|
||||
},
|
||||
]
|
||||
}, [accounts, borrowAssets, lendingAssets, prices])
|
||||
|
||||
if (!walletAddress && !urlAddress) return null
|
||||
|
||||
return <SummarySkeleton title='Portfolio Summary' stats={stats} />
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames'
|
||||
import { useCallback } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||
@ -16,7 +16,7 @@ import useStore from 'store'
|
||||
import { defaultFee } from 'utils/constants'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default function Content() {
|
||||
export default function AccountSummary() {
|
||||
const { address: urlAddress } = useParams()
|
||||
const walletAddress = useStore((s) => s.address)
|
||||
const { data: accountIds, isLoading } = useAccountIds(urlAddress)
|
||||
@ -65,12 +65,17 @@ export default function Content() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('grid w-full grid-cols-1 gap-6', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
||||
>
|
||||
{accountIds.map((accountId: string, index: number) => {
|
||||
return <PortfolioCard key={accountId} accountId={accountId} />
|
||||
})}
|
||||
<div className='w-full mt-4'>
|
||||
<Text size='2xl' className='mb-8'>
|
||||
Credit Accounts
|
||||
</Text>
|
||||
<div
|
||||
className={classNames('grid w-full grid-cols-1 gap-6', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
||||
>
|
||||
{accountIds.map((accountId: string, index: number) => {
|
||||
return <PortfolioCard key={accountId} accountId={accountId} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
43
src/components/Portfolio/SummarySkeleton.tsx
Normal file
43
src/components/Portfolio/SummarySkeleton.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React from 'react'
|
||||
|
||||
import HealthBar from 'components/Account/HealthBar'
|
||||
import Card from 'components/Card'
|
||||
import { Heart } from 'components/Icons'
|
||||
import Loading from 'components/Loading'
|
||||
import Text from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants'
|
||||
|
||||
interface Props {
|
||||
stats?: { title: React.ReactNode | null; sub: string }[]
|
||||
health?: number
|
||||
title: string
|
||||
}
|
||||
|
||||
export default function SummarySkeleton(props: Props) {
|
||||
const stats = props.stats || DEFAULT_PORTFOLIO_STATS
|
||||
return (
|
||||
<div className='flex flex-col w-full gap-8'>
|
||||
<div className='flex justify-between'>
|
||||
<Text size='2xl'>{props.title}</Text>
|
||||
{props.health !== undefined && (
|
||||
<div className='flex gap-1 max-w-[300px] flex-grow'>
|
||||
<Heart width={20} />
|
||||
<HealthBar health={props.health} className='h-full' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='grid grid-cols-2 gap-4 lg:grid-cols-5 md:grid-cols-4 sm:grid-cols-3'>
|
||||
{stats.map((stat) => (
|
||||
<Card key={stat.sub} className='p-6 text-center bg-white/5 flex-grow-1'>
|
||||
<TitleAndSubCell
|
||||
title={stat.title || <Loading className='w-20 h-6 mx-auto mb-2' />}
|
||||
sub={stat.sub}
|
||||
className='mb-1'
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import AccountOverview from 'components/Portfolio/Overview'
|
||||
import PortfolioSummary from 'components/Portfolio/Overview/Summary'
|
||||
import PortfolioIntro from 'components/Portfolio/PortfolioIntro'
|
||||
|
||||
export default function PortfolioPage() {
|
||||
@ -7,6 +8,7 @@ export default function PortfolioPage() {
|
||||
<div className='flex flex-wrap w-full gap-6'>
|
||||
<MigrationBanner />
|
||||
<PortfolioIntro />
|
||||
<PortfolioSummary />
|
||||
<AccountOverview />
|
||||
</div>
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import {
|
||||
Positions,
|
||||
@ -249,3 +250,23 @@ export function computeHealthGaugePercentage(health: number) {
|
||||
|
||||
return 100 - (health / ATTENTION_CUTOFF) * UNHEALTHY_BAR_SIZE
|
||||
}
|
||||
|
||||
export function getAccountSummaryStats(
|
||||
account: Account,
|
||||
prices: BNCoin[],
|
||||
borrowAssets: BorrowMarketTableData[],
|
||||
lendingAssets: LendingMarketTableData[],
|
||||
) {
|
||||
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices)
|
||||
const positionValue = deposits.plus(lends).plus(vaults)
|
||||
const apr = calculateAccountApr(account, borrowAssets, lendingAssets, prices)
|
||||
const leverage = calculateAccountLeverage(account, prices)
|
||||
|
||||
return {
|
||||
positionValue: BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue),
|
||||
debts: BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, debts),
|
||||
netWorth: BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue.minus(debts)),
|
||||
apr,
|
||||
leverage,
|
||||
}
|
||||
}
|
||||
|
@ -14,3 +14,11 @@ export const LTV_BUFFER = 0.99
|
||||
|
||||
export const DEPOSIT_CAP_BUFFER = 0.999
|
||||
export const VAULT_DEPOSIT_BUFFER = 0.999
|
||||
|
||||
export const DEFAULT_PORTFOLIO_STATS = [
|
||||
{ title: null, sub: 'Total Balance' },
|
||||
{ title: null, sub: 'Total Debt' },
|
||||
{ title: null, sub: 'Net Worth' },
|
||||
{ title: null, sub: 'APR' },
|
||||
{ title: null, sub: 'Account Leverage' },
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user