Full refactor tables (#556)

* 📈 Improve structure generic Table component

* ♻️ Update Borrow Table and overall structure of Table comp

* ♻️ Update Lend table

*  add loading state for lend table

* 🧪 Fix unit tests
This commit is contained in:
Bob van der Helm 2023-10-18 09:38:24 +02:00 committed by GitHub
parent 7917d24134
commit ccc4a42354
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 838 additions and 771 deletions

View File

@ -1,38 +0,0 @@
import { render } from '@testing-library/react'
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
import { ASSETS } from 'constants/assets'
import { BN } from 'utils/helpers'
const data: LendingMarketTableData = {
asset: ASSETS[0],
marketDepositAmount: BN('890546916'),
accountLentValue: BN('0.5498406009348686811'),
marketLiquidityAmount: BN('629396551'),
marketDepositCap: BN('2500000000000'),
marketLiquidityRate: 0.017,
marketLiquidationThreshold: 0.61,
marketMaxLtv: 0.59,
borrowEnabled: true,
}
jest.mock('hooks/useDisplayCurrencyPrice', () => () => {
const { BN } = require('utils/helpers')
return {
getConversionRate: () => BN(1),
convertAmount: () => BN(1),
symbol: 'MARS',
}
})
describe('<LendingDetails />', () => {
afterAll(() => {
jest.unmock('hooks/usePrices')
})
it('should render', () => {
const { container } = render(<MarketDetails type='lend' data={data} />)
expect(container).toBeInTheDocument()
})
})

View File

@ -14,6 +14,7 @@ const mockedDepositedVault: DepositedVault = {
...TESTNET_VAULTS_META_DATA[0],
status: 'active',
apy: 1,
apr: null,
ltv: {
max: 0.65,
liq: 0.7,

View File

@ -1,88 +0,0 @@
import { render } from '@testing-library/react'
import DisplayCurrency from 'components/DisplayCurrency'
import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings'
import { ASSETS } from 'constants/assets'
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers'
jest.mock('hooks/usePrices', () =>
jest.fn(() => ({
data: [],
})),
)
jest.mock('hooks/usePrice', () => jest.fn(() => '1'))
jest.mock('hooks/useMarketAssets', () =>
jest.fn(() => ({
data: [],
})),
)
jest.mock('hooks/broadcast/useDepositVault', () => jest.fn(() => ({ actions: [] })))
jest.mock('components/DisplayCurrency')
jest.mock('hooks/useHealthComputer', () =>
jest.fn(() => ({
computeMaxBorrowAmount: () => {},
})),
)
const mockedDisplayCurrency = jest
.mocked(DisplayCurrency)
.mockImplementation(() => <div>Display currency</div>)
const mockedVault: Vault = {
...TESTNET_VAULTS_META_DATA[0],
apy: 0,
ltv: {
liq: 0.2,
max: 0.1,
},
cap: {
denom: 'test',
max: BN(10),
used: BN(2),
},
}
describe('<VaultBorrowings />', () => {
const defaultProps: VaultBorrowingsProps = {
primaryAsset: ASSETS[0],
secondaryAsset: ASSETS[1],
vault: mockedVault,
borrowings: [],
deposits: [],
onChangeBorrowings: jest.fn(),
depositActions: [],
depositCapReachedCoins: [],
displayCurrency: 'uosmo',
}
beforeAll(() => {
useStore.setState({
baseCurrency: ASSETS[0],
})
})
afterAll(() => {
useStore.clearState()
mockedDisplayCurrency.mockClear()
})
it('should render', () => {
const { container } = render(<VaultBorrowings {...defaultProps} />)
expect(container).toBeInTheDocument()
})
it('should render DisplayCurrency correctly', () => {
expect(mockedDisplayCurrency).toHaveBeenCalledTimes(1)
expect(mockedDisplayCurrency).toHaveBeenCalledWith(
{ coin: new BNCoin({ denom: 'usd', amount: '0' }) },
expect.anything(),
)
})
})

View File

@ -1,6 +1,5 @@
import { render } from '@testing-library/react'
import { TooltipType } from 'components/Tooltip'
import TooltipContent from 'components/Tooltip/TooltipContent'
describe('<Tooltip />', () => {

View File

@ -37,14 +37,10 @@ export default function AccountComposition(props: Props) {
const { account } = props
const hasChanged = !!updatedAccount
const { data: prices } = usePrices()
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets],

View File

@ -76,14 +76,12 @@ function AccountDetails(props: Props) {
return updatedLeverage
}, [updatedAccount, prices, leverage])
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets],

View File

@ -28,14 +28,10 @@ export default function AccountStats(props: Props) {
[account, prices],
)
const { health } = useHealthComputer(account)
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets],

View File

@ -36,15 +36,11 @@ export default function AccountSummary(props: Props) {
: BN_ZERO,
[props.account, updatedAccount, prices],
)
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets],

View File

@ -1,129 +0,0 @@
import { ColumnDef, Row, Table } from '@tanstack/react-table'
import { useCallback, useMemo } from 'react'
import AmountAndValue from 'components/AmountAndValue'
import AssetImage from 'components/Asset/AssetImage'
import BorrowActionButtons from 'components/Borrow/BorrowActionButtons'
import { FormattedNumber } from 'components/FormattedNumber'
import { ChevronDown, ChevronUp } from 'components/Icons'
import Loading from 'components/Loading'
import AssetListTable from 'components/MarketAssetTable'
import MarketAssetTableRow from 'components/MarketAssetTable/MarketAssetTableRow'
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { BN_ZERO } from 'constants/math'
import { getEnabledMarketAssets } from 'utils/assets'
interface Props {
title: string
data: BorrowMarketTableData[]
}
export default function BorrowTable(props: Props) {
const { title, data } = props
const shouldShowAccountBorrowed = !!data[0]?.debt
const marketAssets = getEnabledMarketAssets()
const rowRenderer = useCallback(
(row: Row<BorrowMarketTableData>, table: Table<BorrowMarketTableData>) => {
return (
<MarketAssetTableRow
key={`borrow-asset-${row.id}`}
isExpanded={row.getIsExpanded()}
resetExpanded={table.resetExpanded}
rowData={row}
expandedActionButtons={<BorrowActionButtons data={row.original} />}
expandedDetails={<MarketDetails data={row.original} type='borrow' />}
/>
)
},
[],
)
const columns = useMemo<ColumnDef<BorrowMarketTableData>[]>(
() => [
{
accessorKey: 'asset.name',
header: 'Asset',
id: 'symbol',
cell: ({ row }) => {
const asset = row.original.asset
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell
title={asset.symbol}
sub={asset.name}
className='text-left min-w-15'
/>
</div>
)
},
},
...(shouldShowAccountBorrowed
? [
{
accessorKey: 'debt',
header: 'Borrowed',
cell: (info: any) => {
const borrowAsset = info.row.original as BorrowMarketTableData
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.asset.denom)
if (!asset) return null
return <AmountAndValue asset={asset} amount={borrowAsset?.debt ?? BN_ZERO} />
},
},
]
: []),
{
accessorKey: 'borrowRate',
header: 'Borrow Rate',
cell: ({ row }) => {
if (row.original.borrowRate === null) {
return <Loading />
}
return (
<FormattedNumber
className='justify-end text-xs'
amount={row.original.borrowRate * 100}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
animate
/>
)
},
},
{
accessorKey: 'liquidity',
header: 'Liquidity Available',
cell: ({ row }) => {
const { liquidity, asset: borrowAsset } = row.original
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.denom)
if (!asset) return null
if (liquidity === null) {
return <Loading />
}
return <AmountAndValue asset={asset} amount={liquidity.amount ?? BN_ZERO} />
},
},
{
accessorKey: 'manage',
enableSorting: false,
header: 'Manage',
cell: ({ row }) => (
<div className='flex items-center justify-end'>
<div className='w-4'>{row.getIsExpanded() ? <ChevronUp /> : <ChevronDown />}</div>
</div>
),
},
],
[shouldShowAccountBorrowed, marketAssets],
)
return <AssetListTable title={title} rowRenderer={rowRenderer} columns={columns} data={data} />
}

View File

@ -0,0 +1,37 @@
import React from 'react'
import AvailableBorrowingsTable from 'components/Borrow/Table/AvailableBorrowingsTable'
import DepositedBorrowingsTable from 'components/Borrow/Table/DepositedBorrowingsTable'
import { BN_ZERO } from 'constants/math'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import { getBorrowEnabledAssets } from 'utils/assets'
export default function Borrowings() {
const { data } = useBorrowMarketAssetsTableData()
if (!data?.allAssets?.length) {
return <Fallback />
}
return (
<>
<DepositedBorrowingsTable data={data.accountBorrowedAssets} isLoading={false} />
<AvailableBorrowingsTable data={data.availableAssets} isLoading={false} />
</>
)
}
function Fallback() {
const assets = getBorrowEnabledAssets()
const data: BorrowMarketTableData[] = assets.map((asset) => ({
asset,
borrowRate: null,
liquidity: null,
marketMaxLtv: 0,
marketDepositAmount: BN_ZERO,
marketLiquidityRate: 0,
marketLiquidityAmount: BN_ZERO,
marketLiquidationThreshold: 0,
}))
return <AvailableBorrowingsTable data={data} isLoading />
}

View File

@ -0,0 +1,40 @@
import { Row } from '@tanstack/react-table'
import { Table as TanstackTable } from '@tanstack/table-core/build/lib/types'
import React, { useCallback } from 'react'
import useAvailableColumns from 'components/Borrow/Table/Columns/useAvailableColumns'
import Card from 'components/Card'
import MarketDetails from 'components/MarketDetails'
import Table from 'components/Table'
type Props = {
data: BorrowMarketTableData[]
isLoading: boolean
}
export default function AvailableBorrowingsTable(props: Props) {
const columns = useAvailableColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(row: Row<BorrowMarketTableData>, _: TanstackTable<BorrowMarketTableData>) => (
<MarketDetails
row={row as Row<BorrowMarketTableData | LendingMarketTableData>}
type='borrow'
/>
),
[],
)
if (!props.data.length) return null
return (
<Card className='w-full h-fit bg-white/5' title={'Available to Borrow'}>
<Table
columns={columns}
data={props.data}
initialSorting={[{ id: 'asset.name', desc: true }]}
renderExpanded={renderExpanded}
/>
</Card>
)
}

View File

@ -0,0 +1,25 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
export const BORROW_RATE_META = { accessorKey: 'borrowRate', header: 'Borrow Rate' }
interface Props {
borrowRate: number | null
}
export default function BorrowRate(props: Props) {
if (props.borrowRate === null) {
return <Loading />
}
return (
<FormattedNumber
className='justify-end text-xs'
amount={props.borrowRate * 100}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
animate
/>
)
}

View File

@ -0,0 +1,22 @@
import React from 'react'
import AmountAndValue from 'components/AmountAndValue'
import { BN_ZERO } from 'constants/math'
import { getEnabledMarketAssets } from 'utils/assets'
export const DEBT_META = {
accessorKey: 'debt',
header: 'Debt',
}
interface Props {
data: BorrowMarketTableData
}
export default function Debt(props: Props) {
const marketAssets = getEnabledMarketAssets()
const asset = marketAssets.find((asset) => asset.denom === props.data.asset.denom)
if (!asset) return null
return <AmountAndValue asset={asset} amount={props.data?.debt ?? BN_ZERO} />
}

View File

@ -0,0 +1,24 @@
import React from 'react'
import AmountAndValue from 'components/AmountAndValue'
import Loading from 'components/Loading'
import { BN_ZERO } from 'constants/math'
import { getEnabledMarketAssets } from 'utils/assets'
export const LIQUIDITY_META = { accessorKey: 'asset.name', header: 'Asset', id: 'symbol' }
interface Props {
data: BorrowMarketTableData
}
export default function Liquidity(props: Props) {
const { liquidity, asset: borrowAsset } = props.data
const marketAssets = getEnabledMarketAssets()
const asset = marketAssets.find((asset) => asset.denom === borrowAsset.denom)
if (!asset) return null
if (liquidity === null) {
return <Loading />
}
return <AmountAndValue asset={asset} amount={liquidity.amount ?? BN_ZERO} />
}

View File

@ -0,0 +1,17 @@
import React from 'react'
import { ChevronDown, ChevronUp } from 'components/Icons'
export const MANAGE_META = { accessorKey: 'manage', enableSorting: false, header: 'Manage' }
interface Props {
isExpanded: boolean
}
export default function Manage(props: Props) {
return (
<div className='flex items-center justify-end'>
<div className='w-4'>{props.isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
</div>
)
}

View File

@ -0,0 +1,20 @@
import React from 'react'
import AssetImage from 'components/Asset/AssetImage'
import TitleAndSubCell from 'components/TitleAndSubCell'
export const NAME_META = { accessorKey: 'asset.name', header: 'Asset', id: 'symbol' }
interface Props {
data: BorrowMarketTableData
}
export default function Name(props: Props) {
const { asset } = props.data
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell title={asset.symbol} sub={asset.name} className='text-left min-w-15' />
</div>
)
}

View File

@ -0,0 +1,34 @@
import { ColumnDef } from '@tanstack/react-table'
import React, { useMemo } from 'react'
import BorrowRate, { BORROW_RATE_META } from 'components/Borrow/Table/Columns/BorrowRate'
import Liquidity, { LIQUIDITY_META } from 'components/Borrow/Table/Columns/Liquidity'
import Manage, { MANAGE_META } from 'components/Borrow/Table/Columns/Manage'
import Name, { NAME_META } from 'components/Borrow/Table/Columns/Name'
interface Props {
isLoading: boolean
}
export default function useAvailableColumns(props: Props) {
return useMemo<ColumnDef<BorrowMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name data={row.original} />,
},
{
...BORROW_RATE_META,
cell: ({ row }) => <BorrowRate borrowRate={row.original.borrowRate} />,
},
{
...LIQUIDITY_META,
cell: ({ row }) => <Liquidity data={row.original} />,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}

View File

@ -0,0 +1,39 @@
import { ColumnDef } from '@tanstack/react-table'
import React, { useMemo } from 'react'
import BorrowRate, { BORROW_RATE_META } from 'components/Borrow/Table/Columns/BorrowRate'
import Debt, { DEBT_META } from 'components/Borrow/Table/Columns/Debt'
import Liquidity, { LIQUIDITY_META } from 'components/Borrow/Table/Columns/Liquidity'
import Manage, { MANAGE_META } from 'components/Borrow/Table/Columns/Manage'
import Name, { NAME_META } from 'components/Borrow/Table/Columns/Name'
interface Props {
isLoading: boolean
}
export default function useDepositedColumns(props: Props) {
return useMemo<ColumnDef<BorrowMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name data={row.original} />,
},
{
...DEBT_META,
cell: ({ row }) => <Debt data={row.original} />,
},
{
...BORROW_RATE_META,
cell: ({ row }) => <BorrowRate borrowRate={row.original.borrowRate} />,
},
{
...LIQUIDITY_META,
cell: ({ row }) => <Liquidity data={row.original} />,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}

View File

@ -0,0 +1,38 @@
import { Row } from '@tanstack/react-table'
import { Table as TanStackTable } from '@tanstack/table-core/build/lib/types'
import React, { useCallback } from 'react'
import useDepositedColumns from 'components/Borrow/Table/Columns/useDepositedColumns'
import Card from 'components/Card'
import MarketDetails from 'components/MarketDetails'
import Table from 'components/Table'
type Props = {
data: BorrowMarketTableData[]
isLoading: boolean
}
export default function DepositedBorrowingsTable(props: Props) {
const columns = useDepositedColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(
row: Row<BorrowMarketTableData | LendingMarketTableData>,
table: TanStackTable<BorrowMarketTableData>,
) => <MarketDetails row={row} type='borrow' />,
[],
)
if (!props.data.length) return null
return (
<Card className='w-full h-fit bg-white/5' title='Borrowed Assets'>
<Table
columns={columns}
data={props.data}
initialSorting={[{ id: 'asset.name', desc: true }]}
renderExpanded={renderExpanded}
/>
</Card>
)
}

View File

@ -11,6 +11,7 @@ type Props = {
export default function AvailableVaultsTable(props: Props) {
const columns = useAvailableColumns({ isLoading: props.isLoading })
return (
<Card className='w-full h-fit bg-white/5' title={'Available vaults'}>
<Table columns={columns} data={props.data} initialSorting={[{ id: 'name', desc: true }]} />

View File

@ -3,6 +3,8 @@ import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
export const APY_META = { accessorKey: 'apy', header: 'APY' }
interface Props {
vault: Vault | DepositedVault
}

View File

@ -6,6 +6,8 @@ import TitleAndSubCell from 'components/TitleAndSubCell'
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
import { getAssetByDenom } from 'utils/assets'
export const DEPOSIT_CAP_META = { accessorKey: 'cap', header: 'Deposit Cap' }
interface Props {
vault: Vault | DepositedVault
isLoading: boolean

View File

@ -4,6 +4,8 @@ import React from 'react'
import { ChevronDown } from 'components/Icons'
import Loading from 'components/Loading'
export const DETAILS_META = { accessorKey: 'details', enableSorting: false, header: 'Deposit' }
interface Props {
isLoading: boolean
isExpanded: boolean

View File

@ -3,6 +3,8 @@ import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
export const LTV_MAX_META = { accessorKey: 'ltv.max', header: 'Max LTV' }
interface Props {
vault: Vault | DepositedVault
isLoading: boolean

View File

@ -7,6 +7,7 @@ import TitleAndSubCell from 'components/TitleAndSubCell'
import { VaultStatus } from 'types/enums/vault'
import { produceCountdown } from 'utils/formatters'
export const NAME_META = { header: 'Vault', accessorKey: 'name' }
interface Props {
vault: Vault | DepositedVault
}

View File

@ -4,6 +4,10 @@ import DisplayCurrency from 'components/DisplayCurrency'
import { ORACLE_DENOM } from 'constants/oracle'
import { BNCoin } from 'types/classes/BNCoin'
export const POSITION_VALUE_META = {
header: 'Pos. Value',
}
interface Props {
vault: DepositedVault
isLoading: boolean

View File

@ -4,6 +4,8 @@ import DisplayCurrency from 'components/DisplayCurrency'
import Loading from 'components/Loading'
import { BNCoin } from 'types/classes/BNCoin'
export const TVL_META = { accessorKey: 'tvl', header: 'TVL' }
interface Props {
vault: Vault | DepositedVault
isLoading: boolean

View File

@ -1,12 +1,14 @@
import { ColumnDef } from '@tanstack/react-table'
import React, { useMemo } from 'react'
import Apy from 'components/Earn/Farm/Table/Columns/Apy'
import Apy, { APY_META } from 'components/Earn/Farm/Table/Columns/Apy'
import { Deposit } from 'components/Earn/Farm/Table/Columns/Deposit'
import DepositCap from 'components/Earn/Farm/Table/Columns/DepositCap'
import MaxLTV from 'components/Earn/Farm/Table/Columns/MaxLTV'
import Name from 'components/Earn/Farm/Table/Columns/Name'
import TVL from 'components/Earn/Farm/Table/Columns/TVL'
import DepositCap, { DEPOSIT_CAP_META } from 'components/Earn/Farm/Table/Columns/DepositCap'
import MaxLTV, { LTV_MAX_META } from 'components/Earn/Farm/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/Earn/Farm/Table/Columns/Name'
import TVL, { TVL_META } from 'components/Earn/Farm/Table/Columns/TVL'
import { DETAILS_META } from './Details'
interface Props {
isLoading: boolean
@ -16,34 +18,27 @@ export default function useAvailableColumns(props: Props) {
return useMemo<ColumnDef<Vault | DepositedVault>[]>(() => {
return [
{
header: 'Vault',
accessorKey: 'name',
...NAME_META,
cell: ({ row }) => <Name vault={row.original as Vault} />,
},
{
accessorKey: 'apy',
header: 'APY',
...APY_META,
cell: ({ row }) => <Apy vault={row.original as Vault} />,
},
{
accessorKey: 'tvl',
header: 'TVL',
...TVL_META,
cell: ({ row }) => <TVL vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
accessorKey: 'cap',
header: 'Deposit Cap',
...DEPOSIT_CAP_META,
cell: ({ row }) => <DepositCap vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
accessorKey: 'ltv.max',
header: 'Max LTV',
...LTV_MAX_META,
cell: ({ row }) => <MaxLTV vault={row.original as Vault} isLoading={props.isLoading} />,
},
{
accessorKey: 'details',
enableSorting: false,
header: 'Deposit',
...DETAILS_META,
cell: ({ row }) => <Deposit vault={row.original as Vault} isLoading={props.isLoading} />,
},
]

View File

@ -1,13 +1,15 @@
import { ColumnDef, Row } from '@tanstack/react-table'
import React, { useMemo } from 'react'
import Apy from 'components/Earn/Farm/Table/Columns/Apy'
import DepositCap from 'components/Earn/Farm/Table/Columns/DepositCap'
import Details from 'components/Earn/Farm/Table/Columns/Details'
import MaxLTV from 'components/Earn/Farm/Table/Columns/MaxLTV'
import Name from 'components/Earn/Farm/Table/Columns/Name'
import PositionValue from 'components/Earn/Farm/Table/Columns/PositionValue'
import TVL from 'components/Earn/Farm/Table/Columns/TVL'
import Apy, { APY_META } from 'components/Earn/Farm/Table/Columns/Apy'
import DepositCap, { DEPOSIT_CAP_META } from 'components/Earn/Farm/Table/Columns/DepositCap'
import Details, { DETAILS_META } from 'components/Earn/Farm/Table/Columns/Details'
import MaxLTV, { LTV_MAX_META } from 'components/Earn/Farm/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/Earn/Farm/Table/Columns/Name'
import PositionValue, {
POSITION_VALUE_META,
} from 'components/Earn/Farm/Table/Columns/PositionValue'
import TVL, { TVL_META } from 'components/Earn/Farm/Table/Columns/TVL'
interface Props {
isLoading: boolean
@ -17,46 +19,39 @@ export default function useDepositedColumns(props: Props) {
return useMemo<ColumnDef<DepositedVault>[]>(() => {
return [
{
header: 'Vault',
accessorKey: 'name',
...NAME_META,
cell: ({ row }) => <Name vault={row.original as DepositedVault} />,
},
{
header: 'Pos. Value',
...POSITION_VALUE_META,
cell: ({ row }: { row: Row<DepositedVault> }) => (
<PositionValue vault={row.original as DepositedVault} isLoading={props.isLoading} />
),
},
{
accessorKey: 'apy',
header: 'APY',
...APY_META,
cell: ({ row }) => <Apy vault={row.original as DepositedVault} />,
},
{
accessorKey: 'tvl',
header: 'TVL',
...TVL_META,
cell: ({ row }) => (
<TVL vault={row.original as DepositedVault} isLoading={props.isLoading} />
),
},
{
accessorKey: 'cap',
header: 'Deposit Cap',
...DEPOSIT_CAP_META,
cell: ({ row }) => (
<DepositCap vault={row.original as DepositedVault} isLoading={props.isLoading} />
),
},
{
accessorKey: 'ltv.max',
header: 'Max LTV',
...LTV_MAX_META,
cell: ({ row }) => (
<MaxLTV vault={row.original as DepositedVault} isLoading={props.isLoading} />
),
},
{
accessorKey: 'details',
enableSorting: false,
header: 'Details',
...DETAILS_META,
cell: ({ row }) => <Details isLoading={props.isLoading} isExpanded={row.getIsExpanded()} />,
},
]

View File

@ -1,7 +1,10 @@
import React from 'react'
import { Row } from '@tanstack/react-table'
import { Table as TanStackTable } from '@tanstack/table-core/build/lib/types'
import React, { useCallback } from 'react'
import Card from 'components/Card'
import useDepositedColumns from 'components/Earn/Farm/Table/Columns/useDepositedColumns'
import VaultExpanded from 'components/Earn/Farm/VaultExpanded'
import Table from 'components/Table'
type Props = {
@ -11,9 +14,22 @@ type Props = {
export default function DepositedVaultsTable(props: Props) {
const columns = useDepositedColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(row: Row<DepositedVault>, table: TanStackTable<DepositedVault>) => (
<VaultExpanded row={row} resetExpanded={table.resetExpanded} />
),
[],
)
return (
<Card className='w-full h-fit bg-white/5' title={'Deposited vaults'}>
<Table columns={columns} data={props.data} initialSorting={[{ id: 'name', desc: true }]} />
<Table
columns={columns}
data={props.data}
initialSorting={[{ id: 'name', desc: true }]}
renderExpanded={renderExpanded}
/>
</Card>
)
}

View File

@ -13,12 +13,12 @@ import useStore from 'store'
import { VaultStatus } from 'types/enums/vault'
interface Props {
row: Row<Vault | DepositedVault>
row: Row<DepositedVault>
resetExpanded: (defaultState?: boolean | undefined) => void
}
export default function VaultExpanded(props: Props) {
const vault = props.row.original as DepositedVault
const vault = props.row.original
const accountId = useAccountId()
const [isConfirming, setIsConfirming] = useState(false)
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
@ -113,7 +113,7 @@ export default function VaultExpanded(props: Props) {
!isExpanded && props.row.toggleExpanded()
}}
>
<td colSpan={!status ? 7 : 8}>
<td colSpan={props.row.getAllCells().length}>
<div className='flex justify-end gap-3 p-4 align-center'>
{status && <DepositMoreButton />}
{status === VaultStatus.ACTIVE && <UnlockButton />}

View File

@ -1,142 +0,0 @@
import { ColumnDef, Row, Table } from '@tanstack/react-table'
import { useCallback, useMemo } from 'react'
import AmountAndValue from 'components/AmountAndValue'
import AssetImage from 'components/Asset/AssetImage'
import AssetRate from 'components/Asset/AssetRate'
import LendingActionButtons from 'components/Earn/Lend/LendingActionButtons'
import { FormattedNumber } from 'components/FormattedNumber'
import { ChevronDown, ChevronUp } from 'components/Icons'
import AssetListTable from 'components/MarketAssetTable'
import MarketAssetTableRow from 'components/MarketAssetTable/MarketAssetTableRow'
import MarketDetails from 'components/MarketAssetTable/MarketDetails'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { BN_ZERO } from 'constants/math'
import { convertLiquidityRateToAPR } from 'utils/formatters'
import { BN } from 'utils/helpers'
interface Props {
title: string
data: LendingMarketTableData[]
}
export default function LendingMarketsTable(props: Props) {
const { title, data } = props
const shouldShowAccountDeposit = !!data[0]?.accountLentValue
const rowRenderer = useCallback(
(row: Row<LendingMarketTableData>, table: Table<LendingMarketTableData>) => {
return (
<MarketAssetTableRow
key={`lend-asset-${row.id}`}
isExpanded={row.getIsExpanded()}
resetExpanded={table.resetExpanded}
rowData={row}
expandedActionButtons={<LendingActionButtons data={row.original} />}
expandedDetails={<MarketDetails data={row.original} type='lend' />}
/>
)
},
[],
)
const columns = useMemo<ColumnDef<LendingMarketTableData>[]>(
() => [
{
accessorKey: 'asset.name',
header: 'Asset',
id: 'symbol',
cell: ({ row }) => {
const asset = row.original.asset
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell
title={asset.symbol}
sub={asset.name}
className='text-left min-w-15'
/>
</div>
)
},
},
...(shouldShowAccountDeposit
? [
{
accessorKey: 'accountDepositValue',
header: 'Deposited',
cell: ({ row }) => {
const amount = row.original.accountLentAmount
return (
<AmountAndValue
asset={row.original.asset}
amount={amount ? BN(amount) : BN_ZERO}
/>
)
},
} as ColumnDef<LendingMarketTableData>,
]
: []),
{
accessorKey: 'marketLiquidityRate',
header: 'APR',
cell: ({ row }) => {
return (
<AssetRate
rate={convertLiquidityRateToAPR(row.original.marketLiquidityRate)}
isEnabled={row.original.borrowEnabled}
className='justify-end text-xs'
type='apr'
orientation='ltr'
/>
)
},
},
{
accessorKey: 'marketDepositCap',
header: 'Depo. Cap',
cell: ({ row }) => {
const { marketDepositCap, marketDepositAmount, asset } = row.original
const percent = marketDepositAmount
.dividedBy(row.original.marketDepositCap)
.multipliedBy(100)
return (
<TitleAndSubCell
className='text-xs'
title={
<FormattedNumber
amount={marketDepositCap.toNumber()}
options={{ abbreviated: true, decimals: asset.decimals }}
animate
/>
}
sub={
<FormattedNumber
amount={percent.toNumber()}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% used' }}
animate
/>
}
/>
)
},
},
{
accessorKey: 'manage',
enableSorting: false,
header: 'Manage',
cell: ({ row }) => (
<div className='flex items-center justify-end'>
<div className='w-4'>{row.getIsExpanded() ? <ChevronUp /> : <ChevronDown />}</div>
</div>
),
},
],
[shouldShowAccountDeposit],
)
return <AssetListTable title={title} rowRenderer={rowRenderer} columns={columns} data={data} />
}

View File

@ -0,0 +1,37 @@
import React from 'react'
import AvailableLendsTable from 'components/Earn/Lend/Table/AvailableLendsTable'
import DepositedLendsTable from 'components/Earn/Lend/Table/DepositedLendsTable'
import { BN_ZERO } from 'constants/math'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import { getLendEnabledAssets } from 'utils/assets'
export default function Lends() {
const { accountLentAssets, availableAssets, allAssets } = useLendingMarketAssetsTableData()
if (!allAssets?.length) {
return <Fallback />
}
return (
<>
<DepositedLendsTable data={accountLentAssets} isLoading={false} />
<AvailableLendsTable data={availableAssets} isLoading={false} />
</>
)
}
function Fallback() {
const assets = getLendEnabledAssets()
const data: LendingMarketTableData[] = assets.map((asset) => ({
asset,
marketDepositCap: BN_ZERO,
borrowEnabled: false,
marketMaxLtv: 0,
marketDepositAmount: BN_ZERO,
marketLiquidityRate: 0,
marketLiquidityAmount: BN_ZERO,
marketLiquidationThreshold: 0,
}))
return <AvailableLendsTable data={data} isLoading />
}

View File

@ -0,0 +1,36 @@
import { Row } from '@tanstack/react-table'
import React, { useCallback } from 'react'
import Card from 'components/Card'
import { NAME_META } from 'components/Earn/Lend/Table/Columns/Name'
import useAvailableColumns from 'components/Earn/Lend/Table/Columns/useAvailableColumns'
import Table from 'components/Table'
import MarketDetails from '../../../MarketDetails'
type Props = {
data: LendingMarketTableData[]
isLoading: boolean
}
export default function AvailableLendsTable(props: Props) {
const columns = useAvailableColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(row: Row<LendingMarketTableData>) => <MarketDetails row={row} type='lend' />,
[],
)
if (!props.data.length) return null
return (
<Card className='w-full h-fit bg-white/5' title={'Available Markets'}>
<Table
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: true }]}
renderExpanded={renderExpanded}
/>
</Card>
)
}

View File

@ -0,0 +1,26 @@
import React from 'react'
import AssetRate from 'components/Asset/AssetRate'
import Loading from 'components/Loading'
import { convertLiquidityRateToAPR } from 'utils/formatters'
export const APR_META = { accessorKey: 'marketLiquidityRate', header: 'APR' }
interface Props {
marketLiquidityRate: number
borrowEnabled: boolean
isLoading: boolean
}
export default function Apr(props: Props) {
if (props.isLoading) return <Loading />
return (
<AssetRate
rate={convertLiquidityRateToAPR(props.marketLiquidityRate)}
isEnabled={props.borrowEnabled}
className='justify-end text-xs'
type='apr'
orientation='ltr'
/>
)
}

View File

@ -0,0 +1,37 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
import TitleAndSubCell from 'components/TitleAndSubCell'
export const DEPOSIT_CAP_META = { accessorKey: 'marketDepositCap', header: 'Depo. Cap' }
interface Props {
isLoading: boolean
data: LendingMarketTableData
}
export default function DepositCap(props: Props) {
if (props.isLoading) return <Loading />
const { marketDepositCap, marketDepositAmount, asset } = props.data
const percent = marketDepositAmount.dividedBy(marketDepositCap).multipliedBy(100)
return (
<TitleAndSubCell
className='text-xs'
title={
<FormattedNumber
amount={marketDepositCap.toNumber()}
options={{ abbreviated: true, decimals: asset.decimals }}
animate
/>
}
sub={
<FormattedNumber
amount={percent.toNumber()}
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% used' }}
animate
/>
}
/>
)
}

View File

@ -0,0 +1,20 @@
import React from 'react'
import AmountAndValue from 'components/AmountAndValue'
import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
export const DEPOSIT_VALUE_META = { accessorKey: 'accountDepositValue', header: 'Deposited' }
interface Props {
asset: Asset
lentAmount?: string
}
export default function DepositValue(props: Props) {
return (
<AmountAndValue
asset={props.asset}
amount={props.lentAmount ? BN(props.lentAmount) : BN_ZERO}
/>
)
}

View File

@ -0,0 +1,16 @@
import React from 'react'
import { ChevronDown, ChevronUp } from 'components/Icons'
export const MANAGE_META = { accessorKey: 'manage', enableSorting: false, header: 'Manage' }
interface Props {
isExpanded: boolean
}
export default function Manage(props: Props) {
return (
<div className='flex items-center justify-end'>
<div className='w-4'>{props.isExpanded ? <ChevronUp /> : <ChevronDown />}</div>
</div>
)
}

View File

@ -0,0 +1,18 @@
import React from 'react'
import AssetImage from 'components/Asset/AssetImage'
import TitleAndSubCell from 'components/TitleAndSubCell'
export const NAME_META = { accessorKey: 'asset.name', header: 'Asset', id: 'symbol' }
interface Props {
asset: Asset
}
export default function Name(props: Props) {
const { asset } = props
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell title={asset.symbol} sub={asset.name} className='text-left min-w-15' />
</div>
)
}

View File

@ -0,0 +1,40 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apr, { APR_META } from 'components/Earn/Lend/Table/Columns/Apr'
import DepositCap, { DEPOSIT_CAP_META } from 'components/Earn/Lend/Table/Columns/DepositCap'
import Manage, { MANAGE_META } from 'components/Earn/Lend/Table/Columns/Manage'
import Name, { NAME_META } from 'components/Earn/Lend/Table/Columns/Name'
interface Props {
isLoading: boolean
}
export default function useAvailableColumns(props: Props) {
return useMemo<ColumnDef<LendingMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name asset={row.original.asset} />,
},
{
...APR_META,
cell: ({ row }) => (
<Apr
isLoading={props.isLoading}
borrowEnabled={row.original.borrowEnabled}
marketLiquidityRate={row.original.marketLiquidityRate}
/>
),
},
{
...DEPOSIT_CAP_META,
cell: ({ row }) => <DepositCap isLoading={props.isLoading} data={row.original} />,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}

View File

@ -0,0 +1,47 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apr, { APR_META } from 'components/Earn/Lend/Table/Columns/Apr'
import DepositCap, { DEPOSIT_CAP_META } from 'components/Earn/Lend/Table/Columns/DepositCap'
import DepositValue, { DEPOSIT_VALUE_META } from 'components/Earn/Lend/Table/Columns/DepositValue'
import Manage, { MANAGE_META } from 'components/Earn/Lend/Table/Columns/Manage'
import Name, { NAME_META } from 'components/Earn/Lend/Table/Columns/Name'
interface Props {
isLoading: boolean
}
export default function useDepositedColumns(props: Props) {
return useMemo<ColumnDef<LendingMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name asset={row.original.asset} />,
},
{
...DEPOSIT_VALUE_META,
cell: ({ row }) => (
<DepositValue asset={row.original.asset} lentAmount={row.original.accountLentAmount} />
),
},
{
...APR_META,
cell: ({ row }) => (
<Apr
isLoading={props.isLoading}
borrowEnabled={row.original.borrowEnabled}
marketLiquidityRate={row.original.marketLiquidityRate}
/>
),
},
{
...DEPOSIT_CAP_META,
cell: ({ row }) => <DepositCap isLoading={props.isLoading} data={row.original} />,
},
{
...MANAGE_META,
cell: ({ row }) => <Manage isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}

View File

@ -0,0 +1,35 @@
import { Row } from '@tanstack/react-table'
import React, { useCallback } from 'react'
import Card from 'components/Card'
import { NAME_META } from 'components/Earn/Lend/Table/Columns/Name'
import useDepositedColumns from 'components/Earn/Lend/Table/Columns/useDepositedColumns'
import MarketDetails from 'components/MarketDetails'
import Table from 'components/Table'
type Props = {
data: LendingMarketTableData[]
isLoading: boolean
}
export default function DepositedLendsTable(props: Props) {
const columns = useDepositedColumns({ isLoading: props.isLoading })
const renderExpanded = useCallback(
(row: Row<LendingMarketTableData>) => <MarketDetails row={row} type='borrow' />,
[],
)
if (!props.data.length) return null
return (
<Card className='w-full h-fit bg-white/5' title={'Lent Assets'}>
<Table
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: true }]}
renderExpanded={renderExpanded}
/>
</Card>
)
}

View File

@ -1,73 +0,0 @@
import { flexRender, Row } from '@tanstack/react-table'
import classNames from 'classnames'
import Text from 'components/Text'
type Props<TData> = {
rowData: Row<TData>
resetExpanded: (defaultState?: boolean | undefined) => void
isExpanded: boolean
expandedActionButtons?: JSX.Element
expandedDetails?: JSX.Element
}
function AssetListTableRow<TData>(props: Props<TData>) {
const renderFullRow = (key: string, content: JSX.Element) => (
<tr key={key} className='bg-black/20'>
<td
className='border-b border-white border-opacity-10 p-4'
colSpan={props.rowData.getAllCells().length}
>
{content}
</td>
</tr>
)
const renderExpanded = () => {
return (
<>
{props.expandedActionButtons &&
renderFullRow(
`${props.rowData.id}-expanded-actions`,
<div className='flex flex-1 flex-row justify-between'>
<Text className='mt-1 flex p-0 font-bold' size='base'>
Details
</Text>
<div>{props.expandedActionButtons}</div>
</div>,
)}
{props.expandedDetails &&
renderFullRow(`${props.rowData.id}-expanded-details`, props.expandedDetails)}
</>
)
}
return (
<>
<tr
key={props.rowData.id}
className={classNames(
'hover:cursor-pointer transition-colors',
props.rowData.getIsExpanded() ? 'bg-black/20' : 'bg-white/0 hover:bg-white/5',
)}
onClick={() => {
const isExpanded = props.rowData.getIsExpanded()
props.resetExpanded()
!isExpanded && props.rowData.toggleExpanded()
}}
>
{props.rowData.getVisibleCells().map((cell) => {
return (
<td key={cell.id} className={'p-4 text-right'}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
)
})}
</tr>
{props.isExpanded && renderExpanded()}
</>
)
}
export default AssetListTableRow

View File

@ -1,102 +0,0 @@
import {
ColumnDef,
flexRender,
getCoreRowModel,
getSortedRowModel,
Row,
SortingState,
Table,
useReactTable,
} from '@tanstack/react-table'
import classNames from 'classnames'
import React from 'react'
import Card from 'components/Card'
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
import Text from 'components/Text'
interface Props<TData> {
title: string
data: TData[]
columns: ColumnDef<TData>[]
sorting?: SortingState
rowRenderer: (row: Row<TData>, table: Table<TData>) => JSX.Element
}
function AssetListTable<TData>(props: Props<TData>) {
const { title, data, columns } = props
const [sorting, setSorting] = React.useState<SortingState>(props.sorting ?? [])
const table = useReactTable({
data,
columns,
state: {
sorting,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
})
const _rowRenderer = (row: Row<TData>) => props.rowRenderer(row, table)
if (!data.length) return null
return (
<Card className='mb-4 h-fit w-full bg-white/5' title={title}>
<table className='w-full'>
<thead className='border-b border-white border-opacity-10 bg-black/20'>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
onClick={header.column.getToggleSortingHandler()}
className={classNames(
'px-4 py-3',
header.column.getCanSort() && 'hover:cursor-pointer',
header.id === 'symbol' ? 'text-left' : 'text-right',
{
'w-32': header.id === 'manage',
'w-48': header.id === 'depositCap',
},
)}
>
<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='xs'
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(_rowRenderer)}</tbody>
</table>
</Card>
)
}
export default AssetListTable

View File

@ -1,3 +1,4 @@
import { Row } from '@tanstack/react-table'
import { useMemo } from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
@ -5,7 +6,7 @@ import TitleAndSubCell from 'components/TitleAndSubCell'
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
interface Props {
data: BorrowMarketTableData | LendingMarketTableData
row: Row<BorrowMarketTableData | LendingMarketTableData>
type: 'borrow' | 'lend'
}
@ -15,7 +16,7 @@ interface Detail {
title: string
}
export default function MarketDetails({ data, type }: Props) {
export default function MarketDetails({ row, type }: Props) {
const {
convertAmount,
getConversionRate,
@ -28,7 +29,7 @@ export default function MarketDetails({ data, type }: Props) {
marketDepositAmount,
marketLiquidityAmount,
marketLiquidationThreshold,
} = data
} = row.original
const totalBorrowed = marketDepositAmount.minus(marketLiquidityAmount)
@ -36,7 +37,6 @@ export default function MarketDetails({ data, type }: Props) {
const isDollar = displayCurrencySymbol === '$'
function getLendingMarketDetails() {
const depositCap = (data as LendingMarketTableData).marketDepositCap
return [
{
amount: convertAmount(asset, marketDepositAmount).toNumber(),
@ -107,7 +107,6 @@ export default function MarketDetails({ data, type }: Props) {
if (type === 'lend') return getLendingMarketDetails()
return getBorrowMarketDetails()
}, [
data,
type,
asset,
marketDepositAmount,
@ -120,23 +119,27 @@ export default function MarketDetails({ data, type }: Props) {
])
return (
<div className='flex justify-between flex-1 bg-white rounded-md bg-opacity-5'>
{details.map((detail, index) => (
<TitleAndSubCell
key={index}
className='text-center'
containerClassName='m-5 ml-10 mr-10 space-y-1'
title={
<FormattedNumber
className='text-xs text-center'
amount={detail.amount}
options={detail.options}
animate
<tr>
<td colSpan={row.getAllCells().length}>
<div className='flex justify-between flex-1 bg-white rounded-md bg-opacity-5'>
{details.map((detail, index) => (
<TitleAndSubCell
key={index}
className='text-center'
containerClassName='m-5 ml-10 mr-10 space-y-1'
title={
<FormattedNumber
className='text-xs text-center'
amount={detail.amount}
options={detail.options}
animate
/>
}
sub={detail.title}
/>
}
sub={detail.title}
/>
))}
</div>
))}
</div>
</td>
</tr>
)
}

View File

@ -1,4 +1,4 @@
import React, { Suspense } from 'react'
import React, { Suspense, useMemo } from 'react'
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
import Card from 'components/Card'
@ -15,7 +15,8 @@ interface Props {
function Content(props: Props) {
const { data: account } = useAccount(props.accountId, true)
const { allAssets: borrowAssets } = useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssets = useMemo(() => data?.allAssets || [], [data])
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
if (!account || !borrowAssets.length || !lendingAssets.length) {

View File

@ -20,7 +20,8 @@ function Content(props: Props) {
const { data: account } = useAccount(props.accountId, true)
const { data: prices } = usePrices()
const { health } = useHealthComputer(account)
const { allAssets: borrowAssets } = useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssets = useMemo(() => data?.allAssets || [], [data])
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
const stats = useMemo(() => {

View File

@ -34,7 +34,8 @@ export default function PortfolioCard(props: Props) {
const { data: prices } = usePrices()
const currentAccountId = useAccountId()
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
const { allAssets: borrowAssets } = useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssets = useMemo(() => data?.allAssets || [], [data])
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
const address = useStore((s) => s.address)

View File

@ -17,7 +17,8 @@ export default function PortfolioSummary() {
const { address: urlAddress } = useParams()
const walletAddress = useStore((s) => s.address)
const { data: prices } = usePrices()
const { allAssets: borrowAssets } = useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssets = useMemo(() => data?.allAssets || [], [data])
const { allAssets: lendingAssets } = useLendingMarketAssetsTableData()
const { data: accounts } = useAccounts(urlAddress || walletAddress)

View File

@ -0,0 +1,42 @@
import { flexRender, Row as TanstackRow, Table as TanstackTable } from '@tanstack/react-table'
import classNames from 'classnames'
interface Props<T> {
row: TanstackRow<T>
table: TanstackTable<T>
renderExpanded?: (row: TanstackRow<T>, table: TanstackTable<T>) => JSX.Element
rowClassName?: string
rowClickHandler?: () => void
}
export default function Row<T>(props: Props<T>) {
return (
<>
<tr
key={props.row.id}
className={classNames(
'bg-white/3 group/row border-b border-t border-white/5 transition-colors hover:bg-white/5',
props.renderExpanded && 'hover:cursor-pointer',
props.row.getIsExpanded() && 'is-expanded',
)}
onClick={(e) => {
e.preventDefault()
const isExpanded = props.row.getIsExpanded()
props.table.resetExpanded()
!isExpanded && props.row.toggleExpanded()
}}
>
{props.row.getVisibleCells().map((cell) => {
return (
<td key={cell.id} className={'p-4 text-right'}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
)
})}
</tr>
{props.row.getIsExpanded() &&
props.renderExpanded &&
props.renderExpanded(props.row, props.table)}
</>
)
}

View File

@ -3,26 +3,26 @@ import {
flexRender,
getCoreRowModel,
getSortedRowModel,
Row,
SortingState,
Row as TanstackRow,
Table as TanstackTable,
useReactTable,
} from '@tanstack/react-table'
import classNames from 'classnames'
import React from 'react'
import VaultExpanded from 'components/Earn/Farm/VaultExpanded'
import { VaultRow } from 'components/Earn/Farm/VaultRow'
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
import Row from 'components/Table/Row'
import Text from 'components/Text'
import Text from '../Text'
interface Props {
columns: ColumnDef<any>[]
data: unknown[]
interface Props<T> {
columns: ColumnDef<T>[]
data: T[]
initialSorting: SortingState
renderExpanded?: (row: TanstackRow<T>, table: TanstackTable<T>) => JSX.Element
}
export default function Table(props: Props) {
export default function Table<T>(props: Props<T>) {
const [sorting, setSorting] = React.useState<SortingState>(props.initialSorting)
const table = useReactTable({
@ -83,48 +83,10 @@ export default function Table(props: Props) {
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => {
if (row.getIsExpanded()) {
return (
<React.Fragment key={`${row.id}_subrow`}>
{getExpandedRowModel('farm', row, table.resetExpanded)}
</React.Fragment>
)
}
return getRowModel('farm', row, table.resetExpanded)
})}
{table.getRowModel().rows.map((row) => (
<Row key={row.id} row={row} table={table} renderExpanded={props.renderExpanded} />
))}
</tbody>
</table>
)
}
function getExpandedRowModel(
type: 'farm',
row: unknown,
resetExpanded: (defaultState?: boolean | undefined) => void,
) {
switch (type) {
case 'farm':
return (
<>
<VaultRow row={row as Row<Vault>} resetExpanded={resetExpanded} />
<VaultExpanded row={row as Row<Vault>} resetExpanded={resetExpanded} />
</>
)
}
}
function getRowModel(
type: 'farm',
row: Row<unknown>,
resetExpanded: (defaultState?: boolean | undefined) => void,
) {
return (
<VaultRow
key={(row as Row<Vault>).original.address}
row={row as Row<Vault>}
resetExpanded={resetExpanded}
/>
)
}

View File

@ -8,14 +8,10 @@ import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableDa
export default function AccountDetailsCard() {
const account = useCurrentAccount()
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData()
const borrowAssetsData = useMemo(
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
[borrowAvailableAssets, accountBorrowedAssets],
)
const lendingAssetsData = useMemo(
() => [...lendingAvailableAssets, ...accountLentAssets],
[lendingAvailableAssets, accountLentAssets],

View File

@ -17,6 +17,7 @@ export const ASSETS: Asset[] = [
logo: '/images/tokens/osmo.svg',
isEnabled: true,
isMarket: true,
isBorrowEnabled: true,
isDisplayCurrency: true,
isAutoLendEnabled: true,
pythPriceFeedId: '5867f5683c757393a0670ef0f701490950fe93fdb006d181c8265a831ac0c5c6',
@ -38,6 +39,7 @@ export const ASSETS: Asset[] = [
isMarket: true,
isDisplayCurrency: true,
isAutoLendEnabled: true,
isBorrowEnabled: true,
pythPriceFeedId: 'b00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e612819',
poolId: 1,
},
@ -71,6 +73,7 @@ export const ASSETS: Asset[] = [
isMarket: ENV.NETWORK !== NETWORK.TESTNET,
isDisplayCurrency: ENV.NETWORK !== NETWORK.TESTNET,
isAutoLendEnabled: true,
isBorrowEnabled: true,
pythPriceFeedId: 'e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
poolId: 712,
},
@ -88,6 +91,7 @@ export const ASSETS: Asset[] = [
isMarket: ENV.NETWORK !== NETWORK.TESTNET,
isDisplayCurrency: ENV.NETWORK !== NETWORK.TESTNET,
isAutoLendEnabled: true,
isBorrowEnabled: true,
pythPriceFeedId: 'ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
poolId: 704,
},
@ -126,6 +130,7 @@ export const ASSETS: Asset[] = [
isMarket: true,
isDisplayCurrency: true,
isStable: true,
isBorrowEnabled: true,
isAutoLendEnabled: true,
pythPriceFeedId: 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a',
poolId: 678,

View File

@ -0,0 +1,6 @@
import useMarketAssets from 'hooks/useMarketAssets'
export default function useBorrowEnabledMarkets() {
const { data: markets } = useMarketAssets()
return markets.filter((market) => market.borrowEnabled)
}

View File

@ -1,7 +1,6 @@
import { useMemo } from 'react'
import useSWR from 'swr'
import useCurrentAccountDebts from 'hooks/useCurrentAccountDebts'
import useDepositEnabledMarkets from 'hooks/useDepositEnabledMarkets'
import useMarketBorrowings from 'hooks/useMarketBorrowings'
import useMarketDeposits from 'hooks/useMarketDeposits'
import useMarketLiquidities from 'hooks/useMarketLiquidities'
@ -9,47 +8,55 @@ import { byDenom } from 'utils/array'
import { getAssetByDenom } from 'utils/assets'
import { BN } from 'utils/helpers'
export default function useBorrowMarketAssetsTableData(): {
accountBorrowedAssets: BorrowMarketTableData[]
availableAssets: BorrowMarketTableData[]
allAssets: BorrowMarketTableData[]
} {
const markets = useDepositEnabledMarkets()
import useBorrowEnabledMarkets from './useBorrowEnabledMarkets'
export default function useBorrowMarketAssetsTableData(suspense = true) {
const markets = useBorrowEnabledMarkets()
const accountDebts = useCurrentAccountDebts()
const { data: borrowData } = useMarketBorrowings()
const { data: marketDeposits } = useMarketDeposits()
const { data: marketLiquidities } = useMarketLiquidities()
return useMemo(() => {
const accountBorrowedAssets: BorrowMarketTableData[] = [],
availableAssets: BorrowMarketTableData[] = []
return useSWR(
'borrowMarketAssetsTableData',
async (): Promise<{
accountBorrowedAssets: BorrowMarketTableData[]
availableAssets: BorrowMarketTableData[]
allAssets: BorrowMarketTableData[]
}> => {
const accountBorrowedAssets: BorrowMarketTableData[] = [],
availableAssets: BorrowMarketTableData[] = []
markets.forEach(({ denom, liquidityRate, liquidationThreshold, maxLtv }) => {
const asset = getAssetByDenom(denom) as Asset
const borrow = borrowData.find((borrow) => borrow.denom === denom)
const marketDepositAmount = BN(marketDeposits.find(byDenom(denom))?.amount ?? 0)
const marketLiquidityAmount = BN(marketLiquidities.find(byDenom(denom))?.amount ?? 0)
markets.forEach(({ denom, liquidityRate, liquidationThreshold, maxLtv }) => {
const asset = getAssetByDenom(denom) as Asset
const borrow = borrowData.find((borrow) => borrow.denom === denom)
const marketDepositAmount = BN(marketDeposits.find(byDenom(denom))?.amount ?? 0)
const marketLiquidityAmount = BN(marketLiquidities.find(byDenom(denom))?.amount ?? 0)
const debt = accountDebts?.find((debt) => debt.denom === denom)
if (!borrow) return
const debt = accountDebts?.find((debt) => debt.denom === denom)
if (!borrow) return
const borrowMarketAsset: BorrowMarketTableData = {
...borrow,
asset,
debt: debt?.amount,
marketDepositAmount,
marketLiquidityAmount,
marketLiquidityRate: liquidityRate,
marketLiquidationThreshold: liquidationThreshold,
marketMaxLtv: maxLtv,
const borrowMarketAsset: BorrowMarketTableData = {
...borrow,
asset,
debt: debt?.amount,
marketDepositAmount,
marketLiquidityAmount,
marketLiquidityRate: liquidityRate,
marketLiquidationThreshold: liquidationThreshold,
marketMaxLtv: maxLtv,
}
;(borrowMarketAsset.debt ? accountBorrowedAssets : availableAssets).push(borrowMarketAsset)
})
return {
accountBorrowedAssets,
availableAssets,
allAssets: [...accountBorrowedAssets, ...availableAssets],
}
;(borrowMarketAsset.debt ? accountBorrowedAssets : availableAssets).push(borrowMarketAsset)
})
return {
accountBorrowedAssets,
availableAssets,
allAssets: [...accountBorrowedAssets, ...availableAssets],
}
}, [accountDebts, borrowData, markets, marketDeposits, marketLiquidities])
},
{
suspense,
},
)
}

View File

@ -1,17 +1,13 @@
import Borrowings from 'components/Borrow/Borrowings'
import BorrowIntro from 'components/Borrow/BorrowIntro'
import BorrowTable from 'components/Borrow/BorrowTable'
import MigrationBanner from 'components/MigrationBanner'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
export default function BorrowPage() {
const { accountBorrowedAssets, availableAssets } = useBorrowMarketAssetsTableData()
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
<BorrowIntro />
<BorrowTable data={accountBorrowedAssets} title='Borrowed Assets' />
<BorrowTable data={availableAssets} title='Available to borrow' />
<Borrowings />
</div>
)
}

View File

@ -1,19 +1,16 @@
import LendingMarketsTable from 'components/Earn/Lend/LendingMarketsTable'
import LendIntro from 'components/Earn/Lend/LendIntro'
import Lends from 'components/Earn/Lend/Lends'
import Tab from 'components/Earn/Tab'
import MigrationBanner from 'components/MigrationBanner'
import { EARN_TABS } from 'constants/pages'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
export default function LendPage() {
const { accountLentAssets, availableAssets } = useLendingMarketAssetsTableData()
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
<Tab tabs={EARN_TABS} activeTabIdx={0} />
<LendIntro />
<LendingMarketsTable data={accountLentAssets} title='Lent Assets' />
<LendingMarketsTable data={availableAssets} title='Available Markets' />
<Lends />
</div>
)
}

View File

@ -50,6 +50,7 @@ interface Asset {
isStable?: boolean
isFavorite?: boolean
isAutoLendEnabled?: boolean
isBorrowEnabled?: boolean
pythPriceFeedId?: string
forceFetchPrice?: boolean
testnetDenom?: string

View File

@ -35,3 +35,7 @@ export function findCoinByDenom(denom: string, coins: BigNumberCoin[]) {
export function getLendEnabledAssets() {
return ASSETS.filter((asset) => asset.isAutoLendEnabled)
}
export function getBorrowEnabledAssets() {
return ASSETS.filter((asset) => asset.isBorrowEnabled)
}