Mp 2354 credit account balances (#192)
This commit is contained in:
parent
ba6df26ebd
commit
5a71ace0d6
@ -38,9 +38,7 @@ export default function Accordion(props: Props) {
|
||||
<Subtract />
|
||||
</div>
|
||||
</summary>
|
||||
<div className='bg-white/5 px-4 py-0 transition-[padding] group-[[open]]:py-4'>
|
||||
{item.content}
|
||||
</div>
|
||||
<div className='bg-white/5 transition-[padding]'>{item.content}</div>
|
||||
</details>
|
||||
))}
|
||||
</Card>
|
||||
|
219
src/components/Account/AccountBalancesTable.tsx
Normal file
219
src/components/Account/AccountBalancesTable.tsx
Normal file
@ -0,0 +1,219 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ASSETS } from 'constants/assets'
|
||||
import useStore from 'store'
|
||||
import { convertToDisplayAmount, demagnify } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
data: Account
|
||||
}
|
||||
|
||||
export const AcccountBalancesTable = (props: Props) => {
|
||||
const displayCurrency = useStore((s) => s.displayCurrency)
|
||||
const prices = useStore((s) => s.prices)
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
|
||||
const balanceData = React.useMemo<AccountBalanceRow[]>(() => {
|
||||
const accountDeposits = props.data?.deposits ?? []
|
||||
const accountLends = props.data?.lends ?? []
|
||||
|
||||
const deposits = accountDeposits.map((deposit) => {
|
||||
const asset = ASSETS.find((asset) => asset.denom === deposit.denom) ?? ASSETS[0]
|
||||
const apy = 0
|
||||
return {
|
||||
type: 'deposit',
|
||||
symbol: asset.symbol,
|
||||
denom: deposit.denom,
|
||||
amount: deposit.amount,
|
||||
size: demagnify(deposit.amount, asset),
|
||||
value: convertToDisplayAmount(
|
||||
{ amount: deposit.amount, denom: deposit.denom },
|
||||
displayCurrency,
|
||||
prices,
|
||||
),
|
||||
apy,
|
||||
}
|
||||
})
|
||||
const lends = accountLends.map((lending) => {
|
||||
const asset = ASSETS.find((asset) => asset.denom === lending.denom) ?? ASSETS[0]
|
||||
const apy = 0
|
||||
return {
|
||||
type: 'lending',
|
||||
symbol: asset.symbol,
|
||||
denom: lending.denom,
|
||||
amount: lending.amount,
|
||||
size: demagnify(lending.amount, asset),
|
||||
value: convertToDisplayAmount(
|
||||
{ amount: lending.amount, denom: lending.denom },
|
||||
displayCurrency,
|
||||
prices,
|
||||
),
|
||||
apy,
|
||||
}
|
||||
})
|
||||
|
||||
return [...deposits, ...lends]
|
||||
}, [displayCurrency, prices, props.data?.deposits, props.data?.lends])
|
||||
|
||||
const columns = React.useMemo<ColumnDef<AccountBalanceRow>[]>(
|
||||
() => [
|
||||
{
|
||||
header: 'Asset',
|
||||
accessorKey: 'symbol',
|
||||
id: 'symbol',
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Text size='xs'>
|
||||
{row.original.symbol}
|
||||
{row.original.type === 'lending' && <span className='text-profit'>(Lent)</span>}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Value',
|
||||
accessorKey: 'value',
|
||||
id: 'value',
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<DisplayCurrency
|
||||
coin={{ denom: row.original.denom, amount: row.original.amount }}
|
||||
className='text-right text-xs'
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'size',
|
||||
accessorKey: 'size',
|
||||
header: 'Size',
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-right text-xs'
|
||||
amount={demagnify(
|
||||
row.original.amount,
|
||||
ASSETS.find((asset) => asset.denom === row.original.denom) ?? ASSETS[0],
|
||||
)}
|
||||
options={{ maxDecimals: 4 }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'apy',
|
||||
accessorKey: 'apy',
|
||||
header: 'APY',
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-xs'
|
||||
amount={row.original.apy}
|
||||
options={{ maxDecimals: 2, minDecimals: 2, suffix: '%' }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data: balanceData,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
debugTable: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<table className='w-full'>
|
||||
<thead className='border-b border-b-white/5'>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header, index) => {
|
||||
return (
|
||||
<th
|
||||
key={header.id}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
className={classNames(
|
||||
'p-2',
|
||||
header.column.getCanSort() && 'cursor-pointer',
|
||||
header.id === 'symbol' ? 'text-left' : 'text-right',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex',
|
||||
header.id === 'symbol' ? 'justify-start' : 'justify-end',
|
||||
'align-center',
|
||||
)}
|
||||
>
|
||||
<span className='h-6 w-6 text-white'>
|
||||
{header.column.getCanSort()
|
||||
? {
|
||||
asc: <SortAsc />,
|
||||
desc: <SortDesc />,
|
||||
false: <SortNone />,
|
||||
}[header.column.getIsSorted() as string] ?? null
|
||||
: null}
|
||||
</span>
|
||||
<Text
|
||||
tag='span'
|
||||
size='sm'
|
||||
className='flex items-center font-normal text-white/40'
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</Text>
|
||||
</div>
|
||||
</th>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((row) => {
|
||||
return (
|
||||
<tr key={row.id} className=' text-white/60'>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
const borderClass =
|
||||
cell.row.original.type === 'deposit' ? 'border-profit' : 'border-loss'
|
||||
return (
|
||||
<td
|
||||
key={cell.id}
|
||||
className={classNames(
|
||||
cell.column.id === 'symbol' ? `border-l ${borderClass}` : 'pl-4 text-right',
|
||||
'p-2',
|
||||
)}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
@ -44,7 +44,7 @@ export default function AccountComposition(props: Props) {
|
||||
const borrowRateChange = props.change ? calculateAccountPnL(props.change, prices) : BN(0)
|
||||
|
||||
return (
|
||||
<div className='w-full flex-wrap'>
|
||||
<div className='w-full flex-wrap p-4'>
|
||||
<Item
|
||||
title='Total Position Value'
|
||||
current={balance}
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import Accordion from 'components/Accordion'
|
||||
import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable'
|
||||
import AccountComposition from 'components/Account/AccountComposition'
|
||||
import AccountHealth from 'components/Account/AccountHealth'
|
||||
import Card from 'components/Card'
|
||||
@ -23,7 +24,7 @@ export default function AccountSummary(props: Props) {
|
||||
if (!props.account) return null
|
||||
|
||||
return (
|
||||
<div className='flex basis-[345px] flex-wrap'>
|
||||
<div className='flex max-w-[345px] basis-[345px] flex-wrap'>
|
||||
<Card className='mb-4 min-w-fit bg-white/10' contentClassName='flex'>
|
||||
<Item>
|
||||
<DisplayCurrency
|
||||
@ -48,7 +49,7 @@ export default function AccountSummary(props: Props) {
|
||||
content: <AccountComposition account={props.account} change={props.change} />,
|
||||
open: true,
|
||||
},
|
||||
{ title: 'Balances', content: <p>My content</p> },
|
||||
{ title: 'Balances', content: <AcccountBalancesTable data={props.account} /> },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,8 +2,7 @@ import { Coin } from '@cosmjs/stargate'
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import useStore from 'store'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { convertToDisplayAmount } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
coin: Coin
|
||||
@ -15,24 +14,10 @@ export default function DisplayCurrency(props: Props) {
|
||||
const displayCurrency = useStore((s) => s.displayCurrency)
|
||||
const prices = useStore((s) => s.prices)
|
||||
|
||||
function convertToDisplayAmount(coin: Coin) {
|
||||
const price = prices.find((price) => price.denom === coin.denom)
|
||||
const asset = getMarketAssets().find((asset) => asset.denom === coin.denom)
|
||||
const displayPrice = prices.find((price) => price.denom === displayCurrency.denom)
|
||||
|
||||
if (!price || !asset || !displayPrice) return '0'
|
||||
|
||||
return BN(coin.amount)
|
||||
.shiftedBy(-1 * asset.decimals)
|
||||
.times(price.amount)
|
||||
.div(displayPrice.amount)
|
||||
.toNumber()
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedNumber
|
||||
className={props.className}
|
||||
amount={convertToDisplayAmount(props.coin)}
|
||||
amount={convertToDisplayAmount(props.coin, displayCurrency, prices)}
|
||||
options={{
|
||||
minDecimals: 0,
|
||||
maxDecimals: 2,
|
||||
|
@ -98,8 +98,7 @@ export default function BorrowModal() {
|
||||
const max = BN(isRepay ? getDebtAmount(modal) : modal?.marketData?.liquidity?.amount ?? '0')
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedAccount)
|
||||
setSelectedAccount(currentAccount)
|
||||
if (!selectedAccount) setSelectedAccount(currentAccount)
|
||||
}, [selectedAccount, currentAccount])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable'
|
||||
import AccountComposition from 'components/Account/AccountComposition'
|
||||
import Card from 'components/Card'
|
||||
import Loading from 'components/Loading'
|
||||
@ -30,13 +31,10 @@ async function Content(props: PageProps) {
|
||||
className={classNames('grid w-full grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
||||
>
|
||||
{account.map((account: Account, index: number) => (
|
||||
<Card
|
||||
className='h-fit w-full bg-white/5'
|
||||
title={`Account ${account.id}`}
|
||||
key={index}
|
||||
contentClassName='px-4 py-6'
|
||||
>
|
||||
<Card className='h-fit w-full bg-white/5' title={`Account ${account.id}`} key={index}>
|
||||
<AccountComposition account={account} />
|
||||
<Text className='mt-3 w-full bg-white/10 px-4 py-2 text-white/40'>Balances</Text>
|
||||
<AcccountBalancesTable data={account} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
@ -50,13 +48,12 @@ function Fallback() {
|
||||
className={classNames('grid w-full grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
||||
>
|
||||
{Array.from({ length: cardCount }, (_, i) => (
|
||||
<Card
|
||||
key={i}
|
||||
className='h-fit w-full bg-white/5'
|
||||
title='Account'
|
||||
contentClassName='px-4 py-6'
|
||||
>
|
||||
<Loading className='h-4 w-50' />
|
||||
<Card key={i} className='h-fit w-full bg-white/5' title='Account' contentClassName='py-6'>
|
||||
<div className='p-4'>
|
||||
<Loading className='h-4 w-50' />
|
||||
</div>
|
||||
<Text className='mt-3 w-full bg-white/10 px-4 py-2 text-white/40'>Balances</Text>
|
||||
<Loading className='h-4 w-full' />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
@ -46,7 +46,7 @@ export default function Select(props: Props) {
|
||||
(option) => option?.value === props.defaultValue || option?.denom === props.defaultValue,
|
||||
),
|
||||
)
|
||||
}, [value, props.defaultValue, props.options])
|
||||
}, [value, props.defaultValue, props.options, selected])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
10
src/types/interfaces/account.d.ts
vendored
10
src/types/interfaces/account.d.ts
vendored
@ -12,3 +12,13 @@ interface AccountChange {
|
||||
lends?: Coin[]
|
||||
vaults?: import('types/generated/mars-mock-credit-manager/MarsMockCreditManager.types').ArrayOfVaultInfoResponse
|
||||
}
|
||||
|
||||
interface AccountBalanceRow {
|
||||
type: string
|
||||
symbol: string
|
||||
denom: string
|
||||
amount: string
|
||||
value: string | number
|
||||
size: number
|
||||
apy: number
|
||||
}
|
||||
|
@ -154,3 +154,17 @@ export function demagnify(amount: number | string | BigNumber, asset: Asset) {
|
||||
const value = BN(amount)
|
||||
return value.isZero() ? 0 : value.shiftedBy(-1 * asset.decimals).toNumber()
|
||||
}
|
||||
|
||||
export function convertToDisplayAmount(coin: Coin, displayCurrency: Asset, prices: Coin[]) {
|
||||
const price = prices.find((price) => price.denom === coin.denom)
|
||||
const asset = getMarketAssets().find((asset) => asset.denom === coin.denom)
|
||||
const displayPrice = prices.find((price) => price.denom === displayCurrency.denom)
|
||||
|
||||
if (!price || !asset || !displayPrice) return '0'
|
||||
|
||||
return BN(coin.amount)
|
||||
.shiftedBy(-1 * asset.decimals)
|
||||
.times(price.amount)
|
||||
.div(displayPrice.amount)
|
||||
.toNumber()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user