Miscellaneous (#737)

* fix: sorted assets by value

* tidy: refactored AssetsSelectTable

* fix: fixed the multipleVaultWithdraw Modal
This commit is contained in:
Linkie Link 2024-01-18 09:36:26 +01:00 committed by GitHub
parent fd924c885e
commit 000aa71e06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 270 additions and 274 deletions

View File

@ -1,6 +1,6 @@
import { useCallback, useMemo, useState } from 'react'
import AssetSelectTable from 'components/Modals/AssetsSelect/AssetSelectTable'
import AssetsSelect from 'components/Modals/AssetsSelect'
import SearchBar from 'components/common/SearchBar'
import Text from 'components/common/Text'
import useMarketBorrowings from 'hooks/markets/useMarketBorrowings'
@ -89,11 +89,11 @@ export default function AddVaultAssetsModalContent(props: Props) {
Leverage will be set at 50% for both assets by default
</Text>
</div>
<AssetSelectTable
isBorrow={true}
<AssetsSelect
assets={poolAssets}
onChangeSelected={onChangePoolDenoms}
selectedDenoms={selectedPoolDenoms}
isBorrow
/>
<div className='p-4'>
<Text>Assets not in the liquidity pool</Text>
@ -102,11 +102,11 @@ export default function AddVaultAssetsModalContent(props: Props) {
these assets below.
</Text>
</div>
<AssetSelectTable
isBorrow={true}
<AssetsSelect
assets={stableAssets}
onChangeSelected={onChangeOtherDenoms}
selectedDenoms={selectedOtherDenoms}
isBorrow
/>
</div>
</>

View File

@ -1,154 +0,0 @@
import {
flexRender,
getCoreRowModel,
getSortedRowModel,
RowSelectionState,
SortingState,
useReactTable,
} from '@tanstack/react-table'
import classNames from 'classnames'
import { useEffect, useMemo, useState } from 'react'
import { SortAsc, SortDesc, SortNone } from 'components/common/Icons'
import useAssetTableColumns from 'components/Modals/AssetsSelect/useAssetTableColumns'
import Text from 'components/common/Text'
import useMarketAssets from 'hooks/markets/useMarketAssets'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
assets: Asset[] | BorrowAsset[]
selectedDenoms: string[]
onChangeSelected: (denoms: string[]) => void
isBorrow: boolean
}
export default function AssetSelectTable(props: Props) {
const { data: markets } = useMarketAssets()
const defaultSelected = useMemo(() => {
const assets = props.assets as BorrowAsset[]
return assets.reduce(
(acc, asset, index) => {
if (props.selectedDenoms?.includes(asset.denom)) {
acc[index] = true
}
return acc
},
{} as { [key: number]: boolean },
)
}, [props.selectedDenoms, props.assets])
const [sorting, setSorting] = useState<SortingState>([{ id: 'symbol', desc: false }])
const [selected, setSelected] = useState<RowSelectionState>(defaultSelected)
const balances = useStore((s) => s.balances)
const columns = useAssetTableColumns(props.isBorrow)
const tableData: AssetTableRow[] = useMemo(() => {
return props.assets.map((asset) => {
const balancesForAsset = balances.find(byDenom(asset.denom))
return {
asset,
balance: balancesForAsset?.amount ?? '0',
market: markets.find((market) => market.denom === asset.denom),
}
})
}, [balances, props.assets, markets])
const table = useReactTable({
data: tableData,
columns,
state: {
sorting,
rowSelection: selected,
},
onRowSelectionChange: setSelected,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
})
useEffect(() => {
const newSelectedDenoms = props.assets
.filter((_, index) => selected[index])
.map((asset) => asset.denom)
if (
props.selectedDenoms.length === newSelectedDenoms.length &&
newSelectedDenoms.every((denom) => props.selectedDenoms.includes(denom))
)
return
props.onChangeSelected(newSelectedDenoms)
}, [selected, props])
return (
<table className='w-full'>
<thead className='border-b border-white/10'>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
onClick={header.column.getToggleSortingHandler()}
className={classNames(
'p-2',
header.column.getCanSort() && 'hover:cursor-pointer',
header.id === 'symbol' ? 'text-left' : 'text-right',
)}
>
<div
className={classNames(
'flex',
header.id === 'symbol' ? 'justify-start' : 'justify-end',
'align-center',
)}
>
<span className='w-6 h-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='hover:cursor-pointer text-white/60'
onClick={() => row.toggleSelected()}
>
{row.getVisibleCells().map((cell) => {
return (
<td
key={cell.id}
className={classNames(
cell.column.id === 'select' ? `` : 'text-right',
'px-4 py-3',
)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
)
}

View File

@ -0,0 +1,55 @@
import { Row } from '@tanstack/react-table'
import Checkbox from 'components/common/Checkbox'
import Text from 'components/common/Text'
import AssetImage from 'components/common/assets/AssetImage'
import AssetRate from 'components/common/assets/AssetRate'
export const ASSET_META = { id: 'name', header: 'Asset', accessorKey: 'asset.symbol' }
interface Props {
row: Row<AssetTableRow>
}
function isBorrowAsset(object?: any): object is BorrowAsset {
if (!object) return false
return 'borrowRate' in object
}
export default function Asset(props: Props) {
const { row } = props
const asset = row.original.asset
const market = row.original.market
const isBorrow = isBorrowAsset(asset)
const showRate = !isBorrow && market?.borrowEnabled
const apy = isBorrow ? market?.apy.borrow : market?.apy.deposit
return (
<div className='flex items-center'>
<Checkbox
name={`asset-${asset.id.toLowerCase()}`}
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
noMouseEvents
/>
<AssetImage asset={asset} size={24} className='ml-4' />
<div className='ml-2 text-left'>
<Text size='sm' className='mb-0.5 text-white'>
{asset.symbol}
</Text>
{showRate && market ? (
<AssetRate
rate={apy ?? 0}
isEnabled={market.borrowEnabled}
className='text-xs'
type='apy'
orientation='rtl'
suffix
/>
) : (
<Text size='xs'>{asset.name}</Text>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,38 @@
import { Row } from '@tanstack/react-table'
import DisplayCurrency from 'components/common/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber'
import { BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
import { demagnify } from 'utils/formatters'
import { BN } from 'utils/helpers'
export const BALANCE_META = { id: 'value', header: 'Balance', accessorKey: 'value' }
interface Props {
row: Row<AssetTableRow>
}
export const valueSortingFn = (a: Row<AssetTableRow>, b: Row<AssetTableRow>): number => {
const valueA = a.original.value ?? BN_ZERO
const valueB = b.original.value ?? BN_ZERO
return valueA.minus(valueB).toNumber()
}
export default function Balance(props: Props) {
const { row } = props
const asset = row.original.asset
const balance = BN(row.original.balance ?? '0')
const coin = BNCoin.fromDenomAndBigNumber(asset.denom, balance)
return (
<div className='flex flex-wrap items-center'>
<DisplayCurrency coin={coin} className='mb-0.5 w-full text-white' />
<FormattedNumber
className='w-full text-xs'
options={{ minDecimals: 2, maxDecimals: asset.decimals }}
amount={demagnify(balance, asset)}
/>
</div>
)
}

View File

@ -0,0 +1,25 @@
import { Row } from '@tanstack/react-table'
import Text from 'components/common/Text'
import { formatPercent } from 'utils/formatters'
export const BORROW_RATE_META = {
id: 'asset.borrowRate',
header: 'BorrowRate',
accessorKey: 'asset.borrowRate',
}
interface Props {
row: Row<AssetTableRow>
}
export default function BorrowRate(props: Props) {
const { row } = props
const asset = row.original.asset as BorrowAsset
return (
<Text size='sm' className='mb-0.5 text-white'>
{formatPercent(asset.borrowRate ?? 0)}
</Text>
)
}

View File

@ -0,0 +1,30 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Asset, { ASSET_META } from 'components/Modals/AssetsSelect/Columns/Asset'
import Balance, {
BALANCE_META,
valueSortingFn,
} from 'components/Modals/AssetsSelect/Columns/Balance'
import BorrowRate, { BORROW_RATE_META } from 'components/Modals/AssetsSelect/Columns/BorrowRate'
export default function useAssetSelectColumns(isBorrow?: boolean) {
return useMemo<ColumnDef<AssetTableRow>[]>(() => {
return [
{
...ASSET_META,
cell: ({ row }) => <Asset row={row} />,
},
isBorrow
? {
...BORROW_RATE_META,
cell: ({ row }) => <BorrowRate row={row} />,
}
: {
...BALANCE_META,
cell: ({ row }) => <Balance row={row} />,
sortingFn: valueSortingFn,
},
]
}, [isBorrow])
}

View File

@ -0,0 +1,81 @@
import { RowSelectionState } from '@tanstack/react-table'
import { useEffect, useMemo, useState } from 'react'
import useAssetSelectColumns from 'components/Modals/AssetsSelect/Columns/useAssetSelectColumns'
import Table from 'components/common/Table'
import useGetCoinValue from 'hooks/assets/useGetCoinValue'
import useMarketAssets from 'hooks/markets/useMarketAssets'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
interface Props {
assets: Asset[]
onChangeSelected: (selected: string[]) => void
selectedDenoms: string[]
isBorrow?: boolean
}
export default function AssetsSelect(props: Props) {
const { assets, onChangeSelected, selectedDenoms, isBorrow } = props
const columns = useAssetSelectColumns(isBorrow)
const { data: markets } = useMarketAssets()
const getCoinValue = useGetCoinValue()
const defaultSelected = useMemo(() => {
const selectableAssets = assets
return selectableAssets.reduce(
(acc, asset, index) => {
if (selectedDenoms?.includes(asset.denom)) {
acc[index] = true
}
return acc
},
{} as { [key: number]: boolean },
)
}, [selectedDenoms, assets])
const [selected, setSelected] = useState<RowSelectionState>(defaultSelected)
const balances = useStore((s) => s.balances)
const tableData: AssetTableRow[] = useMemo(() => {
return assets.map((asset) => {
const balancesForAsset = balances.find(byDenom(asset.denom))
const coin = BNCoin.fromDenomAndBigNumber(asset.denom, BN(balancesForAsset?.amount ?? '0'))
const value = getCoinValue(coin)
return {
asset,
balance: balancesForAsset?.amount ?? '0',
value,
market: markets.find((market) => market.denom === asset.denom),
}
})
}, [balances, assets, markets, getCoinValue])
useEffect(() => {
const selectedAssets = assets.filter((_, index) => selected[index])
const newSelectedDenoms = selectedAssets
.sort((a, b) => a.symbol.localeCompare(b.symbol))
.map((asset) => asset.denom)
if (
selectedDenoms.length === newSelectedDenoms.length &&
newSelectedDenoms.every((denom) => selectedDenoms.includes(denom))
)
return
onChangeSelected(newSelectedDenoms)
}, [selected, props, assets, selectedDenoms, onChangeSelected])
return (
<Table
title='Assets'
hideCard={true}
columns={columns}
data={tableData}
initialSorting={[{ id: isBorrow ? 'asset.borrowRate' : 'value', desc: !isBorrow }]}
setRowSelection={setSelected}
selectedRows={selected}
/>
)
}

View File

@ -1,94 +0,0 @@
import { ColumnDef } from '@tanstack/react-table'
import React from 'react'
import AssetImage from 'components/common/assets/AssetImage'
import AssetRate from 'components/common/assets/AssetRate'
import Checkbox from 'components/common/Checkbox'
import DisplayCurrency from 'components/common/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber'
import Text from 'components/common/Text'
import { BNCoin } from 'types/classes/BNCoin'
import { demagnify, formatPercent } from 'utils/formatters'
function showBorrowRate(data: AssetTableRow[]) {
const assetData = data.length && (data[0].asset as BorrowAsset)
return !!(assetData && assetData?.borrowRate)
}
export default function useAssetTableColumns(isBorrow: boolean) {
return React.useMemo<ColumnDef<AssetTableRow>[]>(
() => [
{
header: 'Asset',
accessorKey: 'symbol',
id: 'symbol',
cell: ({ row }) => {
const market = row.original.market
const borrowAsset = row.original.asset as BorrowAsset
const showRate = !borrowAsset?.borrowRate
const apy = isBorrow ? market?.apy.borrow : market?.apy.deposit
return (
<div className='flex items-center'>
<Checkbox
name={`asset-${borrowAsset.id.toLowerCase()}`}
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
noMouseEvents
/>
<AssetImage asset={borrowAsset} size={24} className='ml-4' />
<div className='ml-2 text-left'>
<Text size='sm' className='mb-0.5 text-white'>
{borrowAsset.symbol}
</Text>
{showRate && market ? (
<AssetRate
rate={apy ?? 0}
isEnabled={market.borrowEnabled}
className='text-xs'
type='apy'
orientation='rtl'
suffix
/>
) : (
<Text size='xs'>{borrowAsset.name}</Text>
)}
</div>
</div>
)
},
},
{
id: 'details',
header: (data) => {
const tableData = data.table.options.data as AssetTableRow[]
if (showBorrowRate(tableData)) return 'Borrow Rate'
return 'Balance'
},
cell: ({ row }) => {
const asset = row.original.asset as BorrowAsset
const balance = row.original.balance
if (asset?.borrowRate)
return (
<Text size='sm' className='mb-0.5 text-white'>
{formatPercent(asset.borrowRate ?? 0)}
</Text>
)
if (!balance) return null
const coin = new BNCoin({ denom: row.original.asset.denom, amount: balance })
return (
<div className='flex flex-wrap items-center'>
<DisplayCurrency coin={coin} className='mb-0.5 w-full text-white' />
<FormattedNumber
className='w-full text-xs'
options={{ minDecimals: 2, maxDecimals: asset.decimals }}
amount={demagnify(balance, asset)}
/>
</div>
)
},
},
],
[isBorrow],
)
}

View File

@ -1,6 +1,6 @@
import { useCallback, useMemo, useState } from 'react'
import AssetSelectTable from 'components/Modals/AssetsSelect/AssetSelectTable'
import AssetsSelect from 'components/Modals/AssetsSelect'
import SearchBar from 'components/common/SearchBar'
import useAllAssets from 'hooks/assets/useAllAssets'
import useStore from 'store'
@ -59,8 +59,7 @@ export default function WalletAssetsModalContent(props: Props) {
/>
</div>
<div className='max-h-[446px] overflow-y-scroll scrollbar-hide'>
<AssetSelectTable
isBorrow={isBorrow}
<AssetsSelect
assets={filteredAssets}
onChangeSelected={onChangeSelect}
selectedDenoms={selectedDenoms}

View File

@ -1,9 +1,9 @@
import Modal from 'components/Modals/Modal'
import Button from 'components/common/Button'
import { CircularProgress } from 'components/common/CircularProgress'
import DisplayCurrency from 'components/common/DisplayCurrency'
import DoubleLogo from 'components/common/DoubleLogo'
import { FormattedNumber } from 'components/common/FormattedNumber'
import Modal from 'components/Modals/Modal'
import Text from 'components/common/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
@ -73,17 +73,19 @@ export default function WithdrawFromVaultsModal() {
primaryDenom={vault.denoms.primary}
secondaryDenom={vault.denoms.secondary}
/>
<div className='flex flex-wrap flex-1'>
<Text className='w-full'>{vault.name}</Text>
<Text size='sm' className='w-full text-white/50'>
<div className='flex flex-wrap flex-grow'>
<Text size='sm' className='w-full'>
{vault.name}
</Text>
<Text size='xs' className='w-full text-white/50'>
Unlocked
</Text>
</div>
<div className='flex flex-wrap'>
<DisplayCurrency coin={coin} className='w-full text-right' />
<div className='flex flex-wrap flex-shrink max-w-1/2'>
<DisplayCurrency coin={coin} className='w-full text-sm text-right' />
<FormattedNumber
amount={Number(primaryAssetAmount.toPrecision(4))}
className='w-full text-sm text-right text-white/50'
className='w-full text-xs text-right text-white/50'
options={{
suffix: ` ${vault.symbols.primary}`,
maxDecimals: primaryAsset.decimals,
@ -92,7 +94,7 @@ export default function WithdrawFromVaultsModal() {
/>
<FormattedNumber
amount={Number(secondaryAssetAmount.toPrecision(4))}
className='w-full text-sm text-right text-white/50'
className='w-full text-xs text-right text-white/50'
options={{
suffix: ` ${vault.symbols.secondary}`,
maxDecimals: secondaryAsset.decimals,

View File

@ -41,7 +41,6 @@ export default function useAccountBalancesColumns(
type={row.original.type}
/>
),
sortingFn: valueSortingFn,
},
{

View File

@ -165,7 +165,7 @@ export default function AccountFundContent(props: Props) {
<Button
className='w-full mt-4'
text='Select assets'
text='Select Assets'
color='tertiary'
rightIcon={<Plus />}
iconClassName='w-3'

View File

@ -38,7 +38,6 @@ interface AmountMessageProps {
function AmountMessage(props: AmountMessageProps) {
const asset = useAsset(props.coin.denom)
if (!asset) return null
console.log(props.coin.amount.toNumber())
return (
<div key={props.coin.denom} className='flex gap-1'>

View File

@ -1,3 +1,5 @@
import classNames from 'classnames'
import AssetImage from 'components/common/assets/AssetImage'
import useAsset from 'hooks/assets/useAsset'
@ -18,7 +20,7 @@ export default function DoubleLogo(props: Props) {
<AssetImage asset={primaryAsset} size={24} />
</div>
<div className='absolute'>
<AssetImage asset={secondaryAsset} size={16} className='ml-5 mt-5' />
<AssetImage asset={secondaryAsset} size={16} className='mt-5 ml-5' />
</div>
</div>
)

View File

@ -6,10 +6,10 @@ interface Props<T> {
table: TanstackTable<T>
renderExpanded?: (row: TanstackRow<T>, table: TanstackTable<T>) => JSX.Element
rowClassName?: string
rowClickHandler?: () => void
spacingClassName?: string
isBalancesTable?: boolean
className?: string
isSelectable?: boolean
}
function getBorderColor(row: AccountBalanceRow) {
@ -24,14 +24,19 @@ export default function Row<T>(props: Props<T>) {
key={`${props.row.id}-row`}
className={classNames(
'group/row transition-bg',
props.renderExpanded && 'hover:cursor-pointer',
(props.renderExpanded || props.isSelectable) && 'hover:cursor-pointer',
canExpand && props.row.getIsExpanded() ? 'is-expanded bg-black/20' : 'hover:bg-white/5',
)}
onClick={(e) => {
e.preventDefault()
const isExpanded = props.row.getIsExpanded()
props.table.resetExpanded()
!isExpanded && props.row.toggleExpanded()
if (props.isSelectable) {
props.row.toggleSelected()
}
if (canExpand) {
const isExpanded = props.row.getIsExpanded()
props.table.resetExpanded()
!isExpanded && props.row.toggleExpanded()
}
}}
>
{props.row.getVisibleCells().map((cell) => {

View File

@ -3,6 +3,8 @@ import {
flexRender,
getCoreRowModel,
getSortedRowModel,
OnChangeFn,
RowSelectionState,
SortingState,
Row as TanstackRow,
Table as TanstackTable,
@ -27,6 +29,8 @@ interface Props<T> {
spacingClassName?: string
isBalancesTable?: boolean
hideCard?: boolean
setRowSelection?: OnChangeFn<RowSelectionState>
selectedRows?: RowSelectionState
}
export default function Table<T>(props: Props<T>) {
@ -37,7 +41,10 @@ export default function Table<T>(props: Props<T>) {
columns: props.columns,
state: {
sorting,
rowSelection: props.selectedRows,
},
enableRowSelection: true,
onRowSelectionChange: props.setRowSelection,
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
@ -80,7 +87,7 @@ export default function Table<T>(props: Props<T>) {
'align-center',
)}
>
<span className='w-5 h-5 text-white my-auto'>
<span className='w-5 h-5 my-auto text-white'>
{header.column.getCanSort()
? {
asc: <SortAsc size={16} />,
@ -112,6 +119,7 @@ export default function Table<T>(props: Props<T>) {
renderExpanded={props.renderExpanded}
spacingClassName={props.spacingClassName}
isBalancesTable={props.isBalancesTable}
isSelectable={!!props.setRowSelection}
/>
))}
</tbody>

View File

@ -2,4 +2,5 @@ interface AssetTableRow {
balance?: string
asset: BorrowAsset | Asset
market?: Market
value?: BigNumber
}