feat: added account details and fund account button to empty accounts (#393)

This commit is contained in:
Linkie Link 2023-08-24 18:30:54 +02:00 committed by GitHub
parent 50f365044c
commit eeb49ba2ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 65 deletions

View File

@ -10,6 +10,8 @@ jest.mock('hooks/useHealthComputer', () =>
health: 0, health: 0,
})), })),
) )
// AccountBalancesTable component has wallet provider dependency, so we mock it
jest.mock('components/Account/AccountBalancesTable', () => jest.fn(() => null))
const mockedUseCurrentAccount = useCurrentAccount as jest.Mock const mockedUseCurrentAccount = useCurrentAccount as jest.Mock

View File

@ -8,7 +8,10 @@ import {
} from '@tanstack/react-table' } from '@tanstack/react-table'
import classNames from 'classnames' import classNames from 'classnames'
import React from 'react' import React from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import AccountFund from 'components/Account/AccountFund'
import Button from 'components/Button'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { SortAsc, SortDesc, SortNone } from 'components/Icons' import { SortAsc, SortDesc, SortNone } from 'components/Icons'
@ -16,14 +19,17 @@ import Text from 'components/Text'
import { ASSETS } from 'constants/assets' import { ASSETS } from 'constants/assets'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore' import { DISPLAY_CURRENCY_KEY } from 'constants/localStore'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { getAssetByDenom } from 'utils/assets' import { getAssetByDenom } from 'utils/assets'
import { convertLiquidityRateToAPR, convertToDisplayAmount, demagnify } from 'utils/formatters' import { convertLiquidityRateToAPR, convertToDisplayAmount, demagnify } from 'utils/formatters'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { convertAprToApy } from 'utils/parsers' import { convertAprToApy } from 'utils/parsers'
import { getPage, getRoute } from 'utils/route'
interface Props { interface Props {
account: Account account: Account
@ -62,7 +68,10 @@ export default function AccountBalancesTable(props: Props) {
DEFAULT_SETTINGS.displayCurrency, DEFAULT_SETTINGS.displayCurrency,
) )
const { data: prices } = usePrices() const { data: prices } = usePrices()
const currentAccount = useCurrentAccount()
const navigate = useNavigate()
const { pathname } = useLocation()
const address = useStore((s) => s.address)
const [sorting, setSorting] = React.useState<SortingState>([]) const [sorting, setSorting] = React.useState<SortingState>([])
const balanceData = React.useMemo<AccountBalanceRow[]>(() => { const balanceData = React.useMemo<AccountBalanceRow[]>(() => {
const accountDeposits = props.account?.deposits ?? [] const accountDeposits = props.account?.deposits ?? []
@ -172,6 +181,30 @@ export default function AccountBalancesTable(props: Props) {
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
}) })
if (balanceData.length === 0)
return (
<div className='w-full p-4'>
<Button
className='w-full'
text='Fund this Account'
color='tertiary'
onClick={() => {
if (currentAccount?.id !== props.account.id) {
navigate(getRoute(getPage(pathname), address, props.account.id))
}
useStore.setState({
focusComponent: {
component: <AccountFund />,
onClose: () => {
useStore.setState({ getStartedModal: true })
},
},
})
}}
/>
</div>
)
return ( return (
<table className='w-full'> <table className='w-full'>
<thead className='border-b border-white/5'> <thead className='border-b border-white/5'>

View File

@ -85,7 +85,7 @@ export default function AccountComposition(props: Props) {
) )
return ( return (
<div className='flex-wrap w-full p-4'> <div className='flex-wrap w-full p-4 pb-1'>
<Item <Item
title='Total Position Value' title='Total Position Value'
current={balance} current={balance}

View File

@ -1,10 +1,13 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useMemo } from 'react' import { useMemo } from 'react'
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
import AccountComposition from 'components/Account/AccountComposition'
import Card from 'components/Card'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { Gauge } from 'components/Gauge' import { Gauge } from 'components/Gauge'
import { ArrowRight, Heart } from 'components/Icons' import { ArrowRight, ChevronLeft, ChevronRight, Heart } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData' import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
@ -12,6 +15,7 @@ import useCurrentAccount from 'hooks/useCurrentAccount'
import useHealthComputer from 'hooks/useHealthComputer' import useHealthComputer from 'hooks/useHealthComputer'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData' import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { import {
@ -35,18 +39,20 @@ interface Props {
} }
function AccountDetails(props: Props) { function AccountDetails(props: Props) {
const { account } = props
const updatedAccount = useStore((s) => s.updatedAccount) const updatedAccount = useStore((s) => s.updatedAccount)
const { health } = useHealthComputer(props.account) const [isExpanded, setIsExpanded] = useToggle()
const { health } = useHealthComputer(account)
const { health: updatedHealth } = useHealthComputer(updatedAccount) const { health: updatedHealth } = useHealthComputer(updatedAccount)
const { data: prices } = usePrices() const { data: prices } = usePrices()
const accountBalanceValue = useMemo( const accountBalanceValue = useMemo(
() => calculateAccountBalanceValue(updatedAccount ? updatedAccount : props.account, prices), () => calculateAccountBalanceValue(updatedAccount ? updatedAccount : account, prices),
[updatedAccount, props.account, prices], [updatedAccount, account, prices],
) )
const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalanceValue) const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalanceValue)
const leverage = useMemo( const leverage = useMemo(
() => calculateAccountLeverage(updatedAccount ? updatedAccount : props.account, prices), () => calculateAccountLeverage(updatedAccount ? updatedAccount : account, prices),
[props.account, updatedAccount, prices], [account, updatedAccount, prices],
) )
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } = const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
useBorrowMarketAssetsTableData() useBorrowMarketAssetsTableData()
@ -63,76 +69,110 @@ function AccountDetails(props: Props) {
const apr = useMemo( const apr = useMemo(
() => () =>
calculateAccountApr( calculateAccountApr(
props.account, account,
accountBalanceValue, accountBalanceValue,
borrowAssetsData, borrowAssetsData,
lendingAssetsData, lendingAssetsData,
prices, prices,
), ),
[props.account, accountBalanceValue, borrowAssetsData, lendingAssetsData, prices], [account, accountBalanceValue, borrowAssetsData, lendingAssetsData, prices],
) )
return ( return (
<div <div
data-testid='account-details' data-testid='account-details'
className='w-16 border rounded-base border-white/20 bg-white/5 backdrop-blur-sticky' className={classNames(
isExpanded ? 'right-6' : '-right-80',
'w-100 flex items-start gap-6 absolute top-6',
'transition-all duration-300',
)}
> >
<div className='flex flex-wrap justify-center w-full py-4'> <div
<Gauge tooltip='Health Factor' percentage={health} icon={<Heart />} /> className={classNames(
<Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'> 'flex flex-wrap w-16 group/details relative',
Health 'border rounded-base border-white/20',
</Text> 'bg-white/5 backdrop-blur-sticky transition-colors duration-300',
<div className='flex'> 'hover:bg-white/20 hover:cursor-pointer ',
<FormattedNumber )}
className={'w-full text-center text-xs'} onClick={() => setIsExpanded(!isExpanded)}
amount={health} >
options={{ maxDecimals: 0, minDecimals: 0, suffix: '%' }} <div
animate className={classNames(
/> 'absolute block opacity-0 top-1/2 transition-[opacity]',
{updatedHealth > 0 && health !== updatedHealth && ( 'group-hover/details:opacity-100 duration-300 delay-100',
<> isExpanded ? '-right-4' : '-right-3',
<ArrowRight
width={16}
className={classNames(health > updatedHealth ? 'text-loss' : 'text-success')}
/>
<FormattedNumber
className={'w-full text-center text-xs'}
amount={updatedHealth}
options={{ maxDecimals: 0, minDecimals: 0, suffix: '%' }}
animate
/>
</>
)} )}
>
{isExpanded ? <ChevronRight className='w-2' /> : <ChevronLeft className='w-2' />}
</div> </div>
</div> <div className='flex flex-wrap justify-center w-full py-4'>
<div className='w-full py-4 border-t border-white/20'> <Gauge tooltip='Health Factor' percentage={health} icon={<Heart />} />
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'> <Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'>
Leverage Health
</Text> </Text>
<Text size='2xs' className='text-center'> <div className='flex'>
<FormattedNumber
className={'w-full text-center text-xs'}
amount={health}
options={{ maxDecimals: 0, minDecimals: 0, suffix: '%' }}
animate
/>
{updatedHealth > 0 && health !== updatedHealth && (
<>
<ArrowRight
width={16}
className={classNames(health > updatedHealth ? 'text-loss' : 'text-success')}
/>
<FormattedNumber
className={'w-full text-center text-xs'}
amount={updatedHealth}
options={{ maxDecimals: 0, minDecimals: 0, suffix: '%' }}
animate
/>
</>
)}
</div>
</div>
<div className='w-full py-4 border-t border-white/20'>
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
Leverage
</Text>
<Text size='2xs' className='text-center'>
<FormattedNumber
className={'w-full text-center text-2xs'}
amount={leverage.toNumber()}
options={{ maxDecimals: 2, minDecimals: 2, suffix: 'x' }}
animate
/>
</Text>
</div>
<div className='w-full py-4 border-t border-white/20'>
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
Net worth
</Text>
<DisplayCurrency coin={coin} className='w-full text-center truncate text-2xs ' />
</div>
<div className='w-full py-4 border-t border-white/20'>
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
APR
</Text>
<FormattedNumber <FormattedNumber
className={'w-full text-center text-2xs'} className={'w-full text-center text-2xs'}
amount={leverage.toNumber()} amount={apr.toNumber()}
options={{ maxDecimals: 2, minDecimals: 2, suffix: 'x' }} options={{ maxDecimals: 2, minDecimals: 2, suffix: '%' }}
animate animate
/> />
</Text> </div>
</div> </div>
<div className='w-full py-4 border-t border-white/20'> <div className='flex w-80'>
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'> <Card className='w-full bg-white/5' title={`Account ${account.id}`}>
Net worth <AccountComposition account={account} />
</Text> <Text className='w-full px-4 py-2 bg-white/10 text-white/40'>Balances</Text>
<DisplayCurrency coin={coin} className='w-full text-center truncate text-2xs ' /> <AccountBalancesTable
</div> account={account}
<div className='w-full py-4 border-t border-white/20'> borrowingData={borrowAssetsData}
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'> lendingData={lendingAssetsData}
APR />
</Text> </Card>
<FormattedNumber
className={'w-full text-center text-2xs'}
amount={apr.toNumber()}
options={{ maxDecimals: 2, minDecimals: 2, suffix: '%' }}
animate
/>
</div> </div>
</div> </div>
) )

View File

@ -48,7 +48,7 @@ function Content() {
{account.map((account: Account, index: number) => ( {account.map((account: Account, index: number) => (
<Card className='w-full h-fit bg-white/5' title={`Account ${account.id}`} key={index}> <Card className='w-full h-fit bg-white/5' title={`Account ${account.id}`} key={index}>
<AccountComposition account={account} /> <AccountComposition account={account} />
<Text className='w-full px-4 py-2 mt-3 bg-white/10 text-white/40'>Balances</Text> <Text className='w-full px-4 py-2 bg-white/10 text-white/40'>Balances</Text>
<AccountBalancesTable <AccountBalancesTable
account={account} account={account}
borrowingData={borrowAssetsData} borrowingData={borrowAssetsData}

View File

@ -91,7 +91,7 @@ export default function FundAccount(props: Props) {
if (assetToUpdateIdx > -1) { if (assetToUpdateIdx > -1) {
prevAssets[assetToUpdateIdx].amount = amount prevAssets[assetToUpdateIdx].amount = amount
} }
setChange({ [isAutoLendEnabled ? "lends" : "deposits"]: prevAssets }) setChange({ [isAutoLendEnabled ? 'lends' : 'deposits']: prevAssets })
return prevAssets return prevAssets
}) })
}, },
@ -99,7 +99,7 @@ export default function FundAccount(props: Props) {
) )
useEffect(() => { useEffect(() => {
setChange({ [isAutoLendEnabled ? "lends" : "deposits"]: fundingAssets }) setChange({ [isAutoLendEnabled ? 'lends' : 'deposits']: fundingAssets })
}, [isAutoLendEnabled, fundingAssets, setChange]) }, [isAutoLendEnabled, fundingAssets, setChange])
useEffect(() => { useEffect(() => {

View File

@ -48,7 +48,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
className={classNames( className={classNames(
'lg:min-h-[calc(100vh-65px)]', 'lg:min-h-[calc(100vh-65px)]',
'lg:mt-[65px]', 'lg:mt-[65px]',
'min-h-screen gap-6 p-6 w-full', 'min-h-screen gap-6 p-6 w-full relative',
focusComponent || isMobile focusComponent || isMobile
? 'flex justify-center' ? 'flex justify-center'
: 'grid grid-cols-[auto_min-content] place-items-start', : 'grid grid-cols-[auto_min-content] place-items-start',