♻️ refactor table (Farm) (#555)
* ♻️ refactor table (Farm) * 🧽 clean up PR * 🧽 clean up PR
This commit is contained in:
parent
96ab3f64c0
commit
9e5f88ac83
19
src/components/Earn/Farm/Table/AvailableVaultsTable.tsx
Normal file
19
src/components/Earn/Farm/Table/AvailableVaultsTable.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
import Card from 'components/Card'
|
||||
import useAvailableColumns from 'components/Earn/Farm/Table/Columns/useAvailableColumns'
|
||||
import Table from 'components/Table'
|
||||
|
||||
type Props = {
|
||||
data: Vault[]
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
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 }]} />
|
||||
</Card>
|
||||
)
|
||||
}
|
23
src/components/Earn/Farm/Table/Columns/Apy.tsx
Normal file
23
src/components/Earn/Farm/Table/Columns/Apy.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Loading from 'components/Loading'
|
||||
|
||||
interface Props {
|
||||
vault: Vault | DepositedVault
|
||||
}
|
||||
|
||||
export default function Apy(props: Props) {
|
||||
const { vault } = props
|
||||
|
||||
if (vault.apy === null) return <Loading />
|
||||
|
||||
return (
|
||||
<FormattedNumber
|
||||
amount={vault.apy ?? 0}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
)
|
||||
}
|
32
src/components/Earn/Farm/Table/Columns/Deposit.tsx
Normal file
32
src/components/Earn/Farm/Table/Columns/Deposit.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from 'react'
|
||||
|
||||
import ActionButton from 'components/Button/ActionButton'
|
||||
import Loading from 'components/Loading'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
vault: Vault | DepositedVault
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export const Deposit = (props: Props) => {
|
||||
const { vault } = props
|
||||
|
||||
function enterVaultHandler() {
|
||||
useStore.setState({
|
||||
vaultModal: {
|
||||
vault,
|
||||
selectedBorrowDenoms: [vault.denoms.secondary],
|
||||
isCreate: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (props.isLoading) return <Loading />
|
||||
|
||||
return (
|
||||
<div className='flex items-center justify-end'>
|
||||
<ActionButton onClick={enterVaultHandler} color='tertiary' text='Deposit' />
|
||||
</div>
|
||||
)
|
||||
}
|
46
src/components/Earn/Farm/Table/Columns/DepositCap.tsx
Normal file
46
src/components/Earn/Farm/Table/Columns/DepositCap.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React from 'react'
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Loading from 'components/Loading'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
|
||||
interface Props {
|
||||
vault: Vault | DepositedVault
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export default function DepositCap(props: Props) {
|
||||
const { vault } = props
|
||||
|
||||
if (props.isLoading) return <Loading />
|
||||
|
||||
const percent = vault.cap.used
|
||||
.dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER))
|
||||
.multipliedBy(100)
|
||||
.integerValue()
|
||||
|
||||
const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6
|
||||
|
||||
return (
|
||||
<TitleAndSubCell
|
||||
title={
|
||||
<FormattedNumber
|
||||
amount={vault.cap.max.toNumber()}
|
||||
options={{ minDecimals: 2, abbreviated: true, decimals }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
}
|
||||
sub={
|
||||
<FormattedNumber
|
||||
amount={percent.toNumber()}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% Filled' }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
22
src/components/Earn/Farm/Table/Columns/Details.tsx
Normal file
22
src/components/Earn/Farm/Table/Columns/Details.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import Loading from 'components/Loading'
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean
|
||||
isExpanded: boolean
|
||||
}
|
||||
|
||||
export default function Details(props: Props) {
|
||||
if (props.isLoading) return <Loading />
|
||||
|
||||
return (
|
||||
<div className='flex items-center justify-end'>
|
||||
<div className={classNames('w-4', props.isExpanded && 'rotate-180')}>
|
||||
<ChevronDown />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
21
src/components/Earn/Farm/Table/Columns/MaxLTV.tsx
Normal file
21
src/components/Earn/Farm/Table/Columns/MaxLTV.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Loading from 'components/Loading'
|
||||
|
||||
interface Props {
|
||||
vault: Vault | DepositedVault
|
||||
isLoading: boolean
|
||||
}
|
||||
export default function MaxLtv(props: Props) {
|
||||
const { vault } = props
|
||||
if (props.isLoading) return <Loading />
|
||||
return (
|
||||
<FormattedNumber
|
||||
amount={vault.ltv.max * 100}
|
||||
options={{ minDecimals: 0, maxDecimals: 0, suffix: '%' }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
)
|
||||
}
|
71
src/components/Earn/Farm/Table/Columns/Name.tsx
Normal file
71
src/components/Earn/Farm/Table/Columns/Name.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import VaultLogo from 'components/Earn/Farm/VaultLogo'
|
||||
import Text from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { VaultStatus } from 'types/enums/vault'
|
||||
import { produceCountdown } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
vault: Vault | DepositedVault
|
||||
}
|
||||
|
||||
export default function Name(props: Props) {
|
||||
const { vault } = props
|
||||
const timeframe = vault.lockup.timeframe[0]
|
||||
const unlockDuration = !!timeframe ? ` - (${vault.lockup.duration}${timeframe})` : ''
|
||||
|
||||
let remainingTime = 0
|
||||
let status: VaultStatus = VaultStatus.ACTIVE
|
||||
if ('status' in vault) {
|
||||
status = vault.status as VaultStatus
|
||||
if (vault.status === VaultStatus.UNLOCKING && vault.unlocksAt) {
|
||||
remainingTime = vault.unlocksAt - Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex'>
|
||||
<VaultLogo vault={vault} />
|
||||
<TitleAndSubCell
|
||||
className='ml-2 mr-2 text-left'
|
||||
title={`${vault.name}${unlockDuration}`}
|
||||
sub={vault.provider}
|
||||
/>
|
||||
{status === VaultStatus.UNLOCKING && (
|
||||
<Text
|
||||
className='group/label relative h-5 w-[84px] rounded-sm bg-green text-center leading-5 text-white'
|
||||
size='xs'
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-0 text-center',
|
||||
'opacity-100 transition-opacity duration-500',
|
||||
'group-hover/label:opacity-0 group-[.is-expanded]/row:opacity-0',
|
||||
)}
|
||||
>
|
||||
Unlocking
|
||||
</span>
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-0 text-center',
|
||||
'opacity-0 transition-opacity duration-500',
|
||||
'group-hover/label:opacity-100 group-[.is-expanded]/row:opacity-100',
|
||||
)}
|
||||
>
|
||||
{produceCountdown(remainingTime)}
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
{status === VaultStatus.UNLOCKED && (
|
||||
<Text
|
||||
className='h-5 w-[84px] rounded-sm bg-green text-center leading-5 text-white'
|
||||
size='xs'
|
||||
>
|
||||
Unlocked
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
19
src/components/Earn/Farm/Table/Columns/PositionValue.tsx
Normal file
19
src/components/Earn/Farm/Table/Columns/PositionValue.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
interface Props {
|
||||
vault: DepositedVault
|
||||
isLoading: boolean
|
||||
}
|
||||
export default function PositionValue(props: Props) {
|
||||
const { vault } = props
|
||||
const positionValue = vault.values.primary
|
||||
.plus(vault.values.secondary)
|
||||
.plus(vault.values.unlocking)
|
||||
.plus(vault.values.unlocked)
|
||||
const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue)
|
||||
return <DisplayCurrency coin={coin} className='text-xs' />
|
||||
}
|
18
src/components/Earn/Farm/Table/Columns/TVL.tsx
Normal file
18
src/components/Earn/Farm/Table/Columns/TVL.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import Loading from 'components/Loading'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
interface Props {
|
||||
vault: Vault | DepositedVault
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export default function TVL(props: Props) {
|
||||
const { vault } = props
|
||||
if (props.isLoading) return <Loading />
|
||||
const coin = BNCoin.fromDenomAndBigNumber(vault.cap.denom, vault.cap.used)
|
||||
|
||||
return <DisplayCurrency coin={coin} className='text-xs' />
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { ColumnDef } from '@tanstack/react-table'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import Apy 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'
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export default function useAvailableColumns(props: Props) {
|
||||
return useMemo<ColumnDef<Vault | DepositedVault>[]>(() => {
|
||||
return [
|
||||
{
|
||||
header: 'Vault',
|
||||
accessorKey: 'name',
|
||||
cell: ({ row }) => <Name vault={row.original as Vault} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'apy',
|
||||
header: 'APY',
|
||||
cell: ({ row }) => <Apy vault={row.original as Vault} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'tvl',
|
||||
header: 'TVL',
|
||||
cell: ({ row }) => <TVL vault={row.original as Vault} isLoading={props.isLoading} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'cap',
|
||||
header: 'Deposit Cap',
|
||||
cell: ({ row }) => <DepositCap vault={row.original as Vault} isLoading={props.isLoading} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'ltv.max',
|
||||
header: 'Max LTV',
|
||||
cell: ({ row }) => <MaxLTV vault={row.original as Vault} isLoading={props.isLoading} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'details',
|
||||
enableSorting: false,
|
||||
header: 'Deposit',
|
||||
cell: ({ row }) => <Deposit vault={row.original as Vault} isLoading={props.isLoading} />,
|
||||
},
|
||||
]
|
||||
}, [props.isLoading])
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
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'
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export default function useDepositedColumns(props: Props) {
|
||||
return useMemo<ColumnDef<DepositedVault>[]>(() => {
|
||||
return [
|
||||
{
|
||||
header: 'Vault',
|
||||
accessorKey: 'name',
|
||||
cell: ({ row }) => <Name vault={row.original as DepositedVault} />,
|
||||
},
|
||||
{
|
||||
header: 'Pos. Value',
|
||||
cell: ({ row }: { row: Row<DepositedVault> }) => (
|
||||
<PositionValue vault={row.original as DepositedVault} isLoading={props.isLoading} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'apy',
|
||||
header: 'APY',
|
||||
cell: ({ row }) => <Apy vault={row.original as DepositedVault} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'tvl',
|
||||
header: 'TVL',
|
||||
cell: ({ row }) => (
|
||||
<TVL vault={row.original as DepositedVault} isLoading={props.isLoading} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'cap',
|
||||
header: 'Deposit Cap',
|
||||
cell: ({ row }) => (
|
||||
<DepositCap vault={row.original as DepositedVault} isLoading={props.isLoading} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'ltv.max',
|
||||
header: 'Max LTV',
|
||||
cell: ({ row }) => (
|
||||
<MaxLTV vault={row.original as DepositedVault} isLoading={props.isLoading} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'details',
|
||||
enableSorting: false,
|
||||
header: 'Details',
|
||||
cell: ({ row }) => <Details isLoading={props.isLoading} isExpanded={row.getIsExpanded()} />,
|
||||
},
|
||||
]
|
||||
}, [props.isLoading])
|
||||
}
|
19
src/components/Earn/Farm/Table/DepositedVaultsTable.tsx
Normal file
19
src/components/Earn/Farm/Table/DepositedVaultsTable.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
import Card from 'components/Card'
|
||||
import useDepositedColumns from 'components/Earn/Farm/Table/Columns/useDepositedColumns'
|
||||
import Table from 'components/Table'
|
||||
|
||||
type Props = {
|
||||
data: DepositedVault[]
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export default function DepositedVaultsTable(props: Props) {
|
||||
const columns = useDepositedColumns({ isLoading: props.isLoading })
|
||||
return (
|
||||
<Card className='w-full h-fit bg-white/5' title={'Deposited vaults'}>
|
||||
<Table columns={columns} data={props.data} initialSorting={[{ id: 'name', desc: true }]} />
|
||||
</Card>
|
||||
)
|
||||
}
|
@ -1,310 +0,0 @@
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
Row,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import ActionButton from 'components/Button/ActionButton'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import VaultExpanded from 'components/Earn/Farm/VaultExpanded'
|
||||
import VaultLogo from 'components/Earn/Farm/VaultLogo'
|
||||
import { VaultRow } from 'components/Earn/Farm/VaultRow'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||
import Loading from 'components/Loading'
|
||||
import Text from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { BN_ONE } from 'constants/math'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { VaultStatus } from 'types/enums/vault'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { produceCountdown } from 'utils/formatters'
|
||||
|
||||
type Props = {
|
||||
data: Vault[] | DepositedVault[]
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
export const VaultTable = (props: Props) => {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([{ id: 'name', desc: true }])
|
||||
|
||||
const columns = React.useMemo<ColumnDef<Vault | DepositedVault>[]>(() => {
|
||||
return [
|
||||
{
|
||||
header: 'Vault',
|
||||
accessorKey: 'name',
|
||||
cell: ({ row }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
const timeframe = vault.lockup.timeframe[0]
|
||||
const unlockDuration = !!timeframe ? ` - (${vault.lockup.duration}${timeframe})` : ''
|
||||
|
||||
const status = vault.status
|
||||
let remainingTime = 0
|
||||
|
||||
if (status === VaultStatus.UNLOCKING && vault.unlocksAt) {
|
||||
remainingTime = vault.unlocksAt - Date.now()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex'>
|
||||
<VaultLogo vault={vault} />
|
||||
<TitleAndSubCell
|
||||
className='ml-2 mr-2 text-left'
|
||||
title={`${vault.name}${unlockDuration}`}
|
||||
sub={vault.provider}
|
||||
/>
|
||||
{status === VaultStatus.UNLOCKING && (
|
||||
<Text
|
||||
className='group/label relative h-5 w-[84px] rounded-sm bg-green text-center leading-5 text-white'
|
||||
size='xs'
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-0 text-center',
|
||||
'opacity-100 transition-opacity duration-500',
|
||||
'group-hover/label:opacity-0 group-[.is-expanded]/row:opacity-0',
|
||||
)}
|
||||
>
|
||||
Unlocking
|
||||
</span>
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-0 text-center',
|
||||
'opacity-0 transition-opacity duration-500',
|
||||
'group-hover/label:opacity-100 group-[.is-expanded]/row:opacity-100',
|
||||
)}
|
||||
>
|
||||
{produceCountdown(remainingTime)}
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
{status === VaultStatus.UNLOCKED && (
|
||||
<Text
|
||||
className='h-5 w-[84px] rounded-sm bg-green text-center leading-5 text-white'
|
||||
size='xs'
|
||||
>
|
||||
Unlocked
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
...((props.data[0] as DepositedVault)?.values
|
||||
? [
|
||||
{
|
||||
header: 'Pos. Value',
|
||||
cell: ({ row }: { row: Row<DepositedVault | Vault> }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
const positionValue = vault.values.primary
|
||||
.plus(vault.values.secondary)
|
||||
.plus(vault.values.unlocking)
|
||||
.plus(vault.values.unlocked)
|
||||
const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue)
|
||||
return <DisplayCurrency coin={coin} className='text-xs' />
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
accessorKey: 'apy',
|
||||
header: 'APY',
|
||||
cell: ({ row }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
if (vault.apy === null) return <Loading />
|
||||
return (
|
||||
<FormattedNumber
|
||||
amount={vault.apy ?? 0}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'tvl',
|
||||
header: 'TVL',
|
||||
cell: ({ row }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
if (props.isLoading) return <Loading />
|
||||
const coin = new BNCoin({
|
||||
denom: vault.cap.denom,
|
||||
amount: vault.cap.used.toString(),
|
||||
})
|
||||
|
||||
return <DisplayCurrency coin={coin} className='text-xs' />
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'cap',
|
||||
header: 'Depo. Cap',
|
||||
cell: ({ row }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
if (props.isLoading) return <Loading />
|
||||
const percent = vault.cap.used
|
||||
.dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER))
|
||||
.multipliedBy(100)
|
||||
|
||||
const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6
|
||||
|
||||
return (
|
||||
<TitleAndSubCell
|
||||
title={
|
||||
<FormattedNumber
|
||||
amount={vault.cap.max.toNumber()}
|
||||
options={{ minDecimals: 2, abbreviated: true, decimals }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
}
|
||||
sub={
|
||||
<FormattedNumber
|
||||
amount={percent.toNumber()}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% used' }}
|
||||
className='text-xs'
|
||||
animate
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'leverage.max',
|
||||
header: 'Max. Leverage',
|
||||
cell: ({ row }) => {
|
||||
if (props.isLoading) return <Loading />
|
||||
const maxLeverage = BN_ONE.dividedBy(1 - row.original.ltv.max).toNumber()
|
||||
return (
|
||||
<FormattedNumber
|
||||
amount={maxLeverage}
|
||||
options={{ minDecimals: 2, suffix: 'x' }}
|
||||
className='text-xs'
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'details',
|
||||
enableSorting: false,
|
||||
header: (data) => {
|
||||
const tableData = data.table.options.data as DepositedVault[]
|
||||
if (tableData.length && tableData[0].status) return 'Details'
|
||||
return 'Deposit'
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const vault = row.original as DepositedVault
|
||||
|
||||
function enterVaultHandler() {
|
||||
useStore.setState({
|
||||
vaultModal: {
|
||||
vault,
|
||||
selectedBorrowDenoms: [vault.denoms.secondary],
|
||||
isCreate: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (props.isLoading) return <Loading />
|
||||
return (
|
||||
<div className='flex items-center justify-end'>
|
||||
{vault.status ? (
|
||||
<div className={classNames('w-4', row.getIsExpanded() && 'rotate-180')}>
|
||||
<ChevronDown />
|
||||
</div>
|
||||
) : (
|
||||
<ActionButton onClick={enterVaultHandler} color='tertiary' text='Deposit' />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
}, [props.data, props.isLoading])
|
||||
|
||||
const table = useReactTable({
|
||||
data: props.data,
|
||||
columns: columns,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
})
|
||||
|
||||
return (
|
||||
<table className='w-full'>
|
||||
<thead className='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',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex',
|
||||
header.id === 'name' ? '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='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((row) => {
|
||||
if (row.getIsExpanded()) {
|
||||
return (
|
||||
<React.Fragment key={`${row.id}_subrow`}>
|
||||
<VaultRow row={row} resetExpanded={table.resetExpanded} />
|
||||
<VaultExpanded row={row} resetExpanded={table.resetExpanded} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<VaultRow key={row.original.address} row={row} resetExpanded={table.resetExpanded} />
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { Suspense, useMemo } from 'react'
|
||||
|
||||
import Card from 'components/Card'
|
||||
import { VaultTable } from 'components/Earn/Farm/VaultTable'
|
||||
import AvailableVaultsTable from 'components/Earn/Farm/Table/AvailableVaultsTable'
|
||||
import DepositedVaultsTable from 'components/Earn/Farm/Table/DepositedVaultsTable'
|
||||
import VaultUnlockBanner from 'components/Earn/Farm/VaultUnlockBanner'
|
||||
import { ENV } from 'constants/env'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
@ -12,15 +12,10 @@ import useVaults from 'hooks/useVaults'
|
||||
import { NETWORK } from 'types/enums/network'
|
||||
import { VaultStatus } from 'types/enums/vault'
|
||||
|
||||
interface Props {
|
||||
type: 'available' | 'deposited'
|
||||
}
|
||||
|
||||
function Content(props: Props) {
|
||||
function Content() {
|
||||
const accountId = useAccountId()
|
||||
const { data: vaults } = useVaults()
|
||||
const { data: depositedVaults } = useDepositedVaults(accountId || '')
|
||||
const isAvailable = props.type === 'available'
|
||||
|
||||
const vaultsMetaData =
|
||||
ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
|
||||
@ -44,13 +39,9 @@ function Content(props: Props) {
|
||||
)
|
||||
}, [vaults, depositedVaults, vaultsMetaData])
|
||||
|
||||
const vaultsToDisplay = isAvailable ? available : deposited
|
||||
|
||||
if (!vaultsToDisplay.length) return null
|
||||
|
||||
const unlockedVaults: DepositedVault[] = []
|
||||
|
||||
if (!isAvailable && depositedVaults?.length > 0) {
|
||||
if (depositedVaults?.length > 0) {
|
||||
depositedVaults.forEach((vault) => {
|
||||
if (vault.status === VaultStatus.UNLOCKED) {
|
||||
unlockedVaults.push(vault)
|
||||
@ -60,13 +51,11 @@ function Content(props: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isAvailable && <VaultUnlockBanner vaults={unlockedVaults} />}
|
||||
<Card
|
||||
className='w-full h-fit bg-white/5'
|
||||
title={isAvailable ? 'Available vaults' : 'Deposited'}
|
||||
>
|
||||
<VaultTable data={vaultsToDisplay} />
|
||||
</Card>
|
||||
<VaultUnlockBanner vaults={unlockedVaults} />
|
||||
{deposited.length && (
|
||||
<DepositedVaultsTable data={deposited as DepositedVault[]} isLoading={false} />
|
||||
)}
|
||||
{available.length && <AvailableVaultsTable data={available as Vault[]} isLoading={false} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -88,25 +77,13 @@ function Fallback() {
|
||||
},
|
||||
}))
|
||||
|
||||
return (
|
||||
<Card className='w-full h-fit bg-white/5' title='Available vaults'>
|
||||
<VaultTable data={mockVaults} isLoading />
|
||||
</Card>
|
||||
)
|
||||
return <AvailableVaultsTable data={mockVaults} isLoading />
|
||||
}
|
||||
|
||||
export function AvailableVaults() {
|
||||
export default function Vaults() {
|
||||
return (
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<Content type='available' />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
export function DepositedVaults() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<Content type='deposited' />
|
||||
<Content />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
31
src/components/HLS/Farm/HLSFarmIntro.tsx
Normal file
31
src/components/HLS/Farm/HLSFarmIntro.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { PlusSquared } from 'components/Icons'
|
||||
import Intro from 'components/Intro'
|
||||
import { DocURL } from 'types/enums/docURL'
|
||||
|
||||
export default function HlsFarmIntro() {
|
||||
return (
|
||||
<Intro
|
||||
bg='farm'
|
||||
text={
|
||||
<>
|
||||
<span className='text-white'>Leverage farming</span> is a strategy where users borrow
|
||||
funds to increase their yield farming position, aiming to earn more in rewards than the
|
||||
cost of the borrowed assets.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
text='Learn how to Farm'
|
||||
leftIcon={<PlusSquared />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.open(DocURL.FARM_INTRO_URL, '_blank')
|
||||
}}
|
||||
color='secondary'
|
||||
/>
|
||||
</Intro>
|
||||
)
|
||||
}
|
19
src/components/HLS/Farm/Table/Columns/Name.tsx
Normal file
19
src/components/HLS/Farm/Table/Columns/Name.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
|
||||
interface Props {
|
||||
strategy: HLSStrategy
|
||||
}
|
||||
|
||||
export default function Name(props: Props) {
|
||||
return (
|
||||
<div className='flex'>
|
||||
<TitleAndSubCell
|
||||
className='ml-2 mr-2 text-left'
|
||||
title={`${props.strategy.denoms.deposit}-${props.strategy.denoms.borrow}`}
|
||||
sub={'Via Mars'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { ColumnDef } from '@tanstack/react-table'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import Name from 'components/HLS/Farm/Table/Columns/Name'
|
||||
|
||||
export default function useAvailableColumns() {
|
||||
return useMemo<ColumnDef<HLSStrategy>[]>(
|
||||
() => [
|
||||
{
|
||||
header: 'Vault',
|
||||
accessorKey: 'name',
|
||||
cell: ({ row }) => <Name strategy={row.original} />,
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
}
|
7
src/components/HLS/Farm/Table/index.tsx
Normal file
7
src/components/HLS/Farm/Table/index.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
interface Props {
|
||||
data: HLSStrategy[] | DepositedHLSStrategy[]
|
||||
}
|
||||
|
||||
export default function Index(props: Props) {
|
||||
return null
|
||||
}
|
31
src/components/HLS/Staking/HLSStakingIntro.tsx
Normal file
31
src/components/HLS/Staking/HLSStakingIntro.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { PlusSquared } from 'components/Icons'
|
||||
import Intro from 'components/Intro'
|
||||
import { DocURL } from 'types/enums/docURL'
|
||||
|
||||
export default function HLSStakingIntro() {
|
||||
return (
|
||||
<Intro
|
||||
bg='borrow'
|
||||
text={
|
||||
<>
|
||||
<span className='text-white'>Leverage staking</span> is a strategy where users borrow
|
||||
funds to increase their staking position, aiming to earn more in rewards than the cost of
|
||||
the borrowed assets.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
text='Learn how to Stake'
|
||||
leftIcon={<PlusSquared />}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.open(DocURL.FARM_INTRO_URL, '_blank')
|
||||
}}
|
||||
color='secondary'
|
||||
/>
|
||||
</Intro>
|
||||
)
|
||||
}
|
130
src/components/Table/index.tsx
Normal file
130
src/components/Table/index.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
Row,
|
||||
SortingState,
|
||||
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 Text from '../Text'
|
||||
|
||||
interface Props {
|
||||
columns: ColumnDef<any>[]
|
||||
data: unknown[]
|
||||
initialSorting: SortingState
|
||||
}
|
||||
|
||||
export default function Table(props: Props) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>(props.initialSorting)
|
||||
|
||||
const table = useReactTable({
|
||||
data: props.data,
|
||||
columns: props.columns,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
})
|
||||
|
||||
return (
|
||||
<table className='w-full'>
|
||||
<thead className='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',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex',
|
||||
header.id === 'name' ? '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='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((row) => {
|
||||
if (row.getIsExpanded()) {
|
||||
return (
|
||||
<React.Fragment key={`${row.id}_subrow`}>
|
||||
{getExpandedRowModel('farm', row, table.resetExpanded)}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
return getRowModel('farm', row, table.resetExpanded)
|
||||
})}
|
||||
</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}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import FarmIntro from 'components/Earn/Farm/FarmIntro'
|
||||
import { AvailableVaults, DepositedVaults } from 'components/Earn/Farm/Vaults'
|
||||
import Vaults from 'components/Earn/Farm/Vaults'
|
||||
import Tab from 'components/Earn/Tab'
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import { EARN_TABS } from 'constants/pages'
|
||||
@ -10,8 +10,7 @@ export default function FarmPage() {
|
||||
<MigrationBanner />
|
||||
<Tab tabs={EARN_TABS} activeTabIdx={1} />
|
||||
<FarmIntro />
|
||||
<DepositedVaults />
|
||||
<AvailableVaults />
|
||||
<Vaults />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Tab from 'components/Earn/Tab'
|
||||
import AvailableHLSVaults from 'components/HLS/AvailableHLSVaults'
|
||||
import AvailableHLSVaults from 'components/HLS/Farm/AvailableHLSVaults'
|
||||
import HlsFarmIntro from 'components/HLS/Farm/HLSFarmIntro'
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import { HLS_TABS } from 'constants/pages'
|
||||
|
||||
@ -8,6 +9,7 @@ export default function HLSFarmPage() {
|
||||
<div className='flex flex-wrap w-full gap-6'>
|
||||
<MigrationBanner />
|
||||
<Tab tabs={HLS_TABS} activeTabIdx={0} />
|
||||
<HlsFarmIntro />
|
||||
<AvailableHLSVaults />
|
||||
</div>
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Tab from 'components/Earn/Tab'
|
||||
import AvailableHlsStakingAssets from 'components/HLS/AvailableHLSStakingAssets'
|
||||
import AvailableHlsStakingAssets from 'components/HLS/Staking/AvailableHLSStakingAssets'
|
||||
import HLSStakingIntro from 'components/HLS/Staking/HLSStakingIntro'
|
||||
import MigrationBanner from 'components/MigrationBanner'
|
||||
import { HLS_TABS } from 'constants/pages'
|
||||
|
||||
@ -8,6 +9,7 @@ export default function HLSStakingPage() {
|
||||
<div className='flex flex-wrap w-full gap-6'>
|
||||
<MigrationBanner />
|
||||
<Tab tabs={HLS_TABS} activeTabIdx={1} />
|
||||
<HLSStakingIntro />
|
||||
<AvailableHlsStakingAssets />
|
||||
</div>
|
||||
)
|
||||
|
4
src/types/interfaces/asset.d.ts
vendored
4
src/types/interfaces/asset.d.ts
vendored
@ -111,3 +111,7 @@ interface HLSStrategyNoCap {
|
||||
borrow: string
|
||||
}
|
||||
}
|
||||
|
||||
interface DepositedHLSStrategy extends HLSStrategy {
|
||||
depositedAmount: BigNumber
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user