MP-3338: added highlight to changed data in AccountBalancesTable (#424)

* MP-3338: added highlight to changed data in AccountBalancesTable

# Conflicts:
#	src/components/Account/AccountBalancesTable.tsx

* tidy: refactor

* refactor accountBalancesTable components

* MP-3357: created the Intro component (#427)

* MP-3338: added highlight to changed data in AccountBalancesTable

* refactor accountBalancesTable components

* MP-3338: added highlight to changed data in AccountBalancesTable

* refactor accountBalancesTable components

* refactor accountBalancesTable components

* refactor accountBalancesTable components

* MP-3338: added highlight to changed data in AccountBalancesTable

* refactor accountBalancesTable components

* refactor accountBalancesTable components

* fix test

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
This commit is contained in:
Linkie Link 2023-09-06 09:17:57 +02:00 committed by GitHub
parent a3737ce584
commit b0b957a5b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 177 additions and 114 deletions

View File

@ -41,4 +41,4 @@ describe('<AccountDetails />', () => {
const container = screen.queryByTestId('account-details') const container = screen.queryByTestId('account-details')
expect(container).not.toBeInTheDocument() expect(container).not.toBeInTheDocument()
}) })
}) })

View File

@ -0,0 +1,62 @@
import { BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
import { demagnify, getCoinValue } from 'utils/formatters'
export function getAssetAccountBalanceRow(
type: 'deposits' | 'borrowing' | 'lending',
asset: Asset,
prices: BNCoin[],
position: BNCoin,
apy: number,
prev: BNCoin,
): AccountBalanceRow {
const { amount, denom } = position
const amountChange = position.amount.minus(prev.amount)
return {
type,
symbol: asset.symbol,
size: demagnify(amount, asset),
value: getCoinValue(BNCoin.fromDenomAndBigNumber(denom, amount), prices).toString(),
denom,
amount,
apy,
amountChange,
}
}
export function getVaultAccountBalanceRow(
vault: DepositedVault,
apy: number,
prev: DepositedVault,
): AccountBalanceRow {
const { name } = vault
const totalValue = vault.values.primary.plus(vault.values.secondary)
const prevTotalValue = prev.values.primary.plus(prev.values.secondary)
const amountChange = totalValue.minus(prevTotalValue)
return {
type: 'vault',
symbol: name,
size: 0,
value: totalValue.toString(),
denom: vault.denoms.lp,
amount: BN_ZERO,
apy,
amountChange,
}
}
export function getAmountChangeColor(
type: 'deposits' | 'borrowing' | 'lending' | 'vault',
amount: BigNumber,
) {
if (type === 'borrowing') {
if (amount.isGreaterThan(0)) return 'text-loss'
if (amount.isLessThan(0)) return 'text-profit'
}
if (amount.isGreaterThan(0)) return 'text-profit'
if (amount.isLessThan(0)) return 'text-loss'
return ''
}

View File

@ -7,9 +7,11 @@ import {
useReactTable, useReactTable,
} from '@tanstack/react-table' } from '@tanstack/react-table'
import classNames from 'classnames' import classNames from 'classnames'
import React from 'react' import { useMemo, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { getAmountChangeColor } from 'components/Account/AccountBalancesTable/functions'
import useAccountBalanceData from 'components/Account/AccountBalancesTable/useAccountBalanceData'
import AccountFund from 'components/Account/AccountFund' import AccountFund from 'components/Account/AccountFund'
import ActionButton from 'components/Button/ActionButton' import ActionButton from 'components/Button/ActionButton'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
@ -20,14 +22,11 @@ import { ASSETS } from 'constants/assets'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { ORACLE_DENOM } from 'constants/oracle' import { ORACLE_DENOM } from 'constants/oracle'
import useCurrentAccount from 'hooks/useCurrentAccount' import useCurrentAccount from 'hooks/useCurrentAccount'
import usePrices from 'hooks/usePrices'
import useStore from 'store' import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { getAssetByDenom } from 'utils/assets' import { getAssetByDenom } from 'utils/assets'
import { convertLiquidityRateToAPR, demagnify, getCoinValue } from 'utils/formatters' import { demagnify } from 'utils/formatters'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { convertAprToApy } from 'utils/parsers'
import { getPage, getRoute } from 'utils/route' import { getPage, getRoute } from 'utils/route'
interface Props { interface Props {
@ -37,95 +36,22 @@ interface Props {
tableBodyClassName?: string tableBodyClassName?: string
} }
interface PositionValue { export default function Index(props: Props) {
type: 'deposits' | 'borrowing' | 'lending' const { account, lendingData, borrowingData, tableBodyClassName } = props
symbol: string
size: number
value: string
denom: string
amount: BigNumber
apy: number
}
function calculatePositionValues(
type: 'deposits' | 'borrowing' | 'lending',
asset: Asset,
prices: BNCoin[],
position: BNCoin,
apy: number,
) {
const { amount, denom } = position
return {
type,
symbol: asset.symbol,
size: demagnify(amount, asset),
value: getCoinValue(BNCoin.fromDenomAndBigNumber(denom, amount), prices).toString(),
denom,
amount: type === 'borrowing' ? amount.negated() : amount,
apy,
}
}
function calculateVaultValues(vault: DepositedVault, apy: number) {
const { name } = vault
const totalValue = vault.values.primary.plus(vault.values.secondary)
return {
type: 'vault',
symbol: name,
size: 0,
value: totalValue.toString(),
denom: vault.denoms.lp,
amount: BN_ZERO,
apy,
}
}
export default function AccountBalancesTable(props: Props) {
const { data: prices } = usePrices()
const currentAccount = useCurrentAccount() const currentAccount = useCurrentAccount()
const navigate = useNavigate() const navigate = useNavigate()
const { pathname } = useLocation() const { pathname } = useLocation()
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const [sorting, setSorting] = React.useState<SortingState>([]) const [sorting, setSorting] = useState<SortingState>([])
const balanceData = React.useMemo<AccountBalanceRow[]>(() => { const updatedAccount = useStore((s) => s.updatedAccount)
const accountDeposits = props.account?.deposits ?? [] const accountBalanceData = useAccountBalanceData({
const accountLends = props.account?.lends ?? [] account,
const accountDebts = props.account?.debts ?? [] updatedAccount,
const accountVaults = props.account?.vaults ?? [] lendingData,
borrowingData,
})
const deposits: PositionValue[] = [] const columns = useMemo<ColumnDef<AccountBalanceRow>[]>(
accountDeposits.forEach((deposit) => {
const asset = ASSETS.find(byDenom(deposit.denom))
if (!asset) return
const apy = 0
deposits.push(calculatePositionValues('deposits', asset, prices, deposit, apy))
})
const lends = accountLends.map((lending) => {
const asset = ASSETS.find(byDenom(lending.denom)) ?? ASSETS[0]
const apr = convertLiquidityRateToAPR(
props.lendingData.find((market) => market.asset.denom === lending.denom)
?.marketLiquidityRate ?? 0,
)
const apy = convertAprToApy(apr, 365)
return calculatePositionValues('lending', asset, prices, lending, apy)
})
const vaults = accountVaults.map((vault) => {
const apy = (vault.apy ?? 0) * 100
return calculateVaultValues(vault, apy)
})
const debts = accountDebts.map((debt) => {
const asset = ASSETS.find(byDenom(debt.denom)) ?? ASSETS[0]
const apy =
props.borrowingData.find((market) => market.asset.denom === debt.denom)?.borrowRate ?? 0
return calculatePositionValues('borrowing', asset, prices, debt, apy * -100)
})
return [...deposits, ...lends, ...vaults, ...debts]
}, [prices, props.account, props.borrowingData, props.lendingData])
const columns = React.useMemo<ColumnDef<AccountBalanceRow>[]>(
() => [ () => [
{ {
header: 'Asset', header: 'Asset',
@ -146,11 +72,12 @@ export default function AccountBalancesTable(props: Props) {
accessorKey: 'value', accessorKey: 'value',
id: 'value', id: 'value',
cell: ({ row }) => { cell: ({ row }) => {
const color = getAmountChangeColor(row.original.type, row.original.amountChange)
const coin = new BNCoin({ const coin = new BNCoin({
denom: ORACLE_DENOM, denom: ORACLE_DENOM,
amount: row.original.value.toString(), amount: row.original.value.toString(),
}) })
return <DisplayCurrency coin={coin} className='text-xs text-right' /> return <DisplayCurrency coin={coin} className={classNames('text-xs text-right', color)} />
}, },
}, },
{ {
@ -160,13 +87,14 @@ export default function AccountBalancesTable(props: Props) {
cell: ({ row }) => { cell: ({ row }) => {
if (row.original.amount.isEqualTo(BN_ZERO)) if (row.original.amount.isEqualTo(BN_ZERO))
return <span className='w-full text-xs text-center'>&ndash;</span> return <span className='w-full text-xs text-center'>&ndash;</span>
const color = getAmountChangeColor(row.original.type, row.original.amountChange)
const amount = demagnify( const amount = demagnify(
row.original.amount, row.original.amount,
getAssetByDenom(row.original.denom) ?? ASSETS[0], getAssetByDenom(row.original.denom) ?? ASSETS[0],
) )
return ( return (
<FormattedNumber <FormattedNumber
className='text-xs text-right' className={classNames('text-xs text-right', color)}
amount={Number(BN(amount).abs())} amount={Number(BN(amount).abs())}
options={{ maxDecimals: 4, abbreviated: true }} options={{ maxDecimals: 4, abbreviated: true }}
animate animate
@ -179,7 +107,7 @@ export default function AccountBalancesTable(props: Props) {
accessorKey: 'apy', accessorKey: 'apy',
header: 'APY', header: 'APY',
cell: ({ row }) => { cell: ({ row }) => {
if (row.original.type === 'deposit') if (row.original.type === 'deposits')
return <span className='w-full text-xs text-center'>&ndash;</span> return <span className='w-full text-xs text-center'>&ndash;</span>
return ( return (
<FormattedNumber <FormattedNumber
@ -196,7 +124,7 @@ export default function AccountBalancesTable(props: Props) {
) )
const table = useReactTable({ const table = useReactTable({
data: balanceData, data: accountBalanceData,
columns, columns,
state: { state: {
sorting, sorting,
@ -206,7 +134,7 @@ export default function AccountBalancesTable(props: Props) {
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
}) })
if (balanceData.length === 0) if (accountBalanceData.length === 0)
return ( return (
<div className='w-full p-4'> <div className='w-full p-4'>
<ActionButton <ActionButton
@ -214,8 +142,8 @@ export default function AccountBalancesTable(props: Props) {
text='Fund this Account' text='Fund this Account'
color='tertiary' color='tertiary'
onClick={() => { onClick={() => {
if (currentAccount?.id !== props.account.id) { if (currentAccount?.id !== account.id) {
navigate(getRoute(getPage(pathname), address, props.account.id)) navigate(getRoute(getPage(pathname), address, account.id))
} }
useStore.setState({ useStore.setState({
focusComponent: { focusComponent: {
@ -276,7 +204,7 @@ export default function AccountBalancesTable(props: Props) {
</tr> </tr>
))} ))}
</thead> </thead>
<tbody className={props.tableBodyClassName}> <tbody className={tableBodyClassName}>
{table.getRowModel().rows.map((row) => { {table.getRowModel().rows.map((row) => {
return ( return (
<tr key={row.id} className=' text-white/60'> <tr key={row.id} className=' text-white/60'>
@ -301,4 +229,4 @@ export default function AccountBalancesTable(props: Props) {
</tbody> </tbody>
</table> </table>
) )
} }

View File

@ -0,0 +1,74 @@
import { useMemo } from 'react'
import {
getAssetAccountBalanceRow,
getVaultAccountBalanceRow,
} from 'components/Account/AccountBalancesTable/functions'
import { ASSETS } from 'constants/assets'
import usePrices from 'hooks/usePrices'
import { byDenom } from 'utils/array'
import { convertLiquidityRateToAPR } from 'utils/formatters'
import { convertAprToApy } from 'utils/parsers'
interface Props {
account: Account
updatedAccount?: Account
lendingData: LendingMarketTableData[]
borrowingData: BorrowMarketTableData[]
}
export default function useAccountBalanceData(props: Props) {
const { account, updatedAccount, lendingData, borrowingData } = props
const { data: prices } = usePrices()
return useMemo<AccountBalanceRow[]>(() => {
const usedAccount = updatedAccount ?? account
const accountDeposits = usedAccount?.deposits ?? []
const accountLends = usedAccount?.lends ?? []
const accountDebts = usedAccount?.debts ?? []
const accountVaults = usedAccount?.vaults ?? []
const deposits: AccountBalanceRow[] = []
accountDeposits.forEach((deposit) => {
const asset = ASSETS.find(byDenom(deposit.denom))
if (!asset) return
const apy = 0
const prevDeposit = updatedAccount
? account?.deposits.find((position) => position.denom === deposit.denom) ?? deposit
: deposit
deposits.push(getAssetAccountBalanceRow('deposits', asset, prices, deposit, apy, prevDeposit))
})
const lends = accountLends.map((lending) => {
const asset = ASSETS.find(byDenom(lending.denom)) ?? ASSETS[0]
const apr = convertLiquidityRateToAPR(
lendingData.find((market) => market.asset.denom === lending.denom)?.marketLiquidityRate ??
0,
)
const apy = convertAprToApy(apr, 365)
const prevLending = updatedAccount
? account?.lends.find((position) => position.denom === lending.denom) ?? lending
: lending
return getAssetAccountBalanceRow('lending', asset, prices, lending, apy, prevLending)
})
const vaults = accountVaults.map((vault) => {
const apy = (vault.apy ?? 0) * 100
const prevVault = updatedAccount
? account?.vaults.find((position) => position.name === vault.name) ?? vault
: vault
return getVaultAccountBalanceRow(vault, apy, prevVault)
})
const debts = accountDebts.map((debt) => {
const asset = ASSETS.find(byDenom(debt.denom)) ?? ASSETS[0]
const apy = borrowingData.find((market) => market.asset.denom === debt.denom)?.borrowRate ?? 0
const prevDebt = updatedAccount
? account?.debts.find((position) => position.denom === debt.denom) ?? debt
: debt
return getAssetAccountBalanceRow('borrowing', asset, prices, debt, apy * -100, prevDebt)
})
return [...deposits, ...lends, ...vaults, ...debts]
}, [prices, account, updatedAccount, borrowingData, lendingData])
}

View File

@ -146,7 +146,7 @@ function AccountDetails(props: Props) {
<AccountComposition account={account} /> <AccountComposition account={account} />
<Text className='w-full px-4 py-2 text-white bg-white/10'>Balances</Text> <Text className='w-full px-4 py-2 text-white bg-white/10'>Balances</Text>
<AccountBalancesTable <AccountBalancesTable
account={updatedAccount ? updatedAccount : account} account={account}
borrowingData={borrowAssetsData} borrowingData={borrowAssetsData}
lendingData={lendingAssetsData} lendingData={lendingAssetsData}
/> />
@ -154,4 +154,4 @@ function AccountDetails(props: Props) {
</div> </div>
</div> </div>
) )
} }

View File

@ -89,4 +89,4 @@ export default function AccountOverview() {
<Content /> <Content />
</Suspense> </Suspense>
) )
} }

View File

@ -90,7 +90,7 @@ export default function AccountSummary(props: Props) {
renderContent: () => renderContent: () =>
props.account ? ( props.account ? (
<AccountBalancesTable <AccountBalancesTable
account={updatedAccount ? updatedAccount : props.account} account={props.account}
borrowingData={borrowAssetsData} borrowingData={borrowAssetsData}
lendingData={lendingAssetsData} lendingData={lendingAssetsData}
/> />
@ -126,4 +126,4 @@ function Item(props: ItemProps) {
<div className='flex h-4.5 w-full'>{props.children}</div> <div className='flex h-4.5 w-full'>{props.children}</div>
</div> </div>
) )
} }

View File

@ -5,11 +5,9 @@ import Card from 'components/Card'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData' import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useCurrentAccount from 'hooks/useCurrentAccount' import useCurrentAccount from 'hooks/useCurrentAccount'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData' import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import useStore from 'store'
export default function AccountDetailsCard() { export default function AccountDetailsCard() {
const account = useCurrentAccount() const account = useCurrentAccount()
const updatedAccount = useStore((s) => s.updatedAccount)
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } = const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
useBorrowMarketAssetsTableData() useBorrowMarketAssetsTableData()
const { availableAssets: lendingAvailableAssets, accountLentAssets } = const { availableAssets: lendingAvailableAssets, accountLentAssets } =
@ -33,7 +31,7 @@ export default function AccountDetailsCard() {
return ( return (
<Card className='h-fit' title={tabs}> <Card className='h-fit' title={tabs}>
<AccountBalancesTable <AccountBalancesTable
account={updatedAccount || account} account={account}
borrowingData={borrowAssetsData} borrowingData={borrowAssetsData}
lendingData={lendingAssetsData} lendingData={lendingAssetsData}
tableBodyClassName='gradient-card-content' tableBodyClassName='gradient-card-content'
@ -45,4 +43,4 @@ export default function AccountDetailsCard() {
const className = { const className = {
tabWrapper: 'flex w-full items-center bg-white/10 pt-4 pl-4 font-semibold', tabWrapper: 'flex w-full items-center bg-white/10 pt-4 pl-4 font-semibold',
tab: 'mr-4 pb-3 cursor-pointer select-none flex flex-row border-b-2 border-pink border-solid', tab: 'mr-4 pb-3 cursor-pointer select-none flex flex-row border-b-2 border-pink border-solid',
} }

View File

@ -255,4 +255,4 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
unsubscribeBars(listenerGuid: string): void { unsubscribeBars(listenerGuid: string): void {
// TheGraph doesn't support websockets yet // TheGraph doesn't support websockets yet
} }
} }

View File

@ -14,11 +14,12 @@ interface AccountChange {
} }
interface AccountBalanceRow { interface AccountBalanceRow {
type: string
symbol: string
denom: string
amount: BigNumber amount: BigNumber
value: string | number
size: number
apy: number apy: number
denom: string
size: number
symbol: string
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
value: string
amountChange: BigNumber
} }