mango-v4-ui/components/explore/SpotTable.tsx

488 lines
17 KiB
TypeScript
Raw Normal View History

2022-10-08 04:37:08 -07:00
import { useTranslation } from 'next-i18next'
2023-07-27 19:44:43 -07:00
import { useCallback } from 'react'
2022-11-18 11:11:06 -08:00
import useMangoGroup from 'hooks/useMangoGroup'
2023-07-27 19:44:43 -07:00
import {
SortableColumnHeader,
Table,
Td,
Th,
TrBody,
TrHead,
} from '@components/shared/TableElements'
2023-01-24 16:54:24 -08:00
import FormatNumericValue from '@components/shared/FormatNumericValue'
2023-10-04 04:52:39 -07:00
import { numberCompacter } from 'utils/numbers'
2023-05-01 19:01:14 -07:00
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
2023-05-31 05:15:50 -07:00
import { Disclosure, Transition } from '@headlessui/react'
2023-10-03 22:05:16 -07:00
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
2023-07-21 11:50:06 -07:00
import useThemeWrapper from 'hooks/useThemeWrapper'
2023-07-27 19:44:43 -07:00
import { useSortableData } from 'hooks/useSortableData'
import Change from '@components/shared/Change'
import { Bank } from '@blockworks-foundation/mango-v4'
2023-10-03 22:05:16 -07:00
import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme'
import ContentBox from '@components/shared/ContentBox'
import { COLORS } from 'styles/colors'
import TokenLogo from '@components/shared/TokenLogo'
import { goToTokenPage } from '@components/stats/tokens/TokenOverviewTable'
import { useRouter } from 'next/router'
import Decimal from 'decimal.js'
import BankAmountWithValue from '@components/shared/BankAmountWithValue'
2023-10-04 04:52:39 -07:00
import { BankWithMarketData } from './Spot'
import { SerumMarketWithMarketData } from 'hooks/useListedMarketsWithMarketData'
2023-10-04 16:04:51 -07:00
import Tooltip from '@components/shared/Tooltip'
2023-10-06 03:33:15 -07:00
import dayjs from 'dayjs'
import TableTokenName from '@components/shared/TableTokenName'
2023-07-27 19:44:43 -07:00
type TableData = {
2023-10-04 04:52:39 -07:00
assetWeight: string
available: Decimal
baseBank: Bank
borrowRate: number
2023-07-27 19:44:43 -07:00
change: number
2023-10-04 04:52:39 -07:00
depositRate: number
tokenName: string
market: SerumMarketWithMarketData | undefined
2023-07-27 19:44:43 -07:00
price: number
2023-10-06 03:33:15 -07:00
priceHistory: {
price: number
time: string
}[]
2023-07-27 19:44:43 -07:00
volume: number
isUp: boolean
}
2022-10-08 04:37:08 -07:00
2023-10-04 04:52:39 -07:00
const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
2022-10-08 04:37:08 -07:00
const { t } = useTranslation('common')
2022-11-20 12:32:38 -08:00
const { group } = useMangoGroup()
2023-07-21 11:50:06 -07:00
const { theme } = useThemeWrapper()
2022-10-08 04:37:08 -07:00
const { width } = useViewport()
2023-10-03 22:05:16 -07:00
const router = useRouter()
2022-10-08 04:37:08 -07:00
const showTableView = width ? width > breakpoints.md : false
2023-07-21 05:03:44 -07:00
2023-07-27 22:04:38 -07:00
const formattedTableData = useCallback(
2023-10-04 04:52:39 -07:00
(tokens: BankWithMarketData[]) => {
2023-07-27 19:44:43 -07:00
const formatted = []
2023-10-04 04:52:39 -07:00
for (const token of tokens) {
const baseBank = token.bank
const price = baseBank.uiPrice
2023-07-27 19:44:43 -07:00
2023-10-06 03:33:15 -07:00
const pastPrice = token.market?.marketData?.price_24h
2023-07-27 19:44:43 -07:00
2023-10-06 03:33:15 -07:00
const priceHistory =
token.market?.marketData?.price_history
.sort((a, b) => a.time.localeCompare(b.time))
.concat([{ price: price, time: dayjs().toISOString() }]) || []
2023-07-27 19:44:43 -07:00
2023-10-04 04:52:39 -07:00
const volume = token.market?.marketData?.quote_volume_24h || 0
2023-07-27 19:44:43 -07:00
2023-10-06 03:33:15 -07:00
const change =
volume > 0 && pastPrice ? ((price - pastPrice) / pastPrice) * 100 : 0
2023-07-27 19:44:43 -07:00
2023-10-04 04:52:39 -07:00
const tokenName = baseBank.name
2023-10-03 22:05:16 -07:00
let availableVaultBalance = 0
let available = new Decimal(0)
let depositRate = 0
let borrowRate = 0
let assetWeight = '0'
if (baseBank) {
availableVaultBalance = group
? group.getTokenVaultBalanceByMintUi(baseBank.mint) -
baseBank.uiDeposits() * baseBank.minVaultToDepositsRatio
: 0
available = Decimal.max(
0,
availableVaultBalance.toFixed(baseBank.mintDecimals),
)
depositRate = baseBank.getDepositRateUi()
borrowRate = baseBank.getBorrowRateUi()
assetWeight = baseBank
.scaledInitAssetWeight(baseBank.price)
.toFixed(2)
}
2023-07-27 19:44:43 -07:00
const isUp =
2023-10-06 03:33:15 -07:00
price && priceHistory.length ? price >= priceHistory[0].price : false
2023-07-27 19:44:43 -07:00
const data = {
2023-10-03 22:05:16 -07:00
available,
assetWeight,
borrowRate,
2023-07-27 19:44:43 -07:00
baseBank,
change,
2023-10-03 22:05:16 -07:00
depositRate,
2023-10-04 04:52:39 -07:00
market: token.market,
2023-07-27 19:44:43 -07:00
price,
priceHistory,
2023-10-04 04:52:39 -07:00
tokenName,
2023-07-27 19:44:43 -07:00
volume,
isUp,
}
formatted.push(data)
}
return formatted
},
[group],
)
const {
items: tableData,
requestSort,
sortConfig,
2023-10-04 04:52:39 -07:00
} = useSortableData(formattedTableData(tokens))
2023-05-31 05:15:50 -07:00
2022-10-08 04:37:08 -07:00
return (
<ContentBox hideBorder hidePadding>
{showTableView ? (
2023-11-01 17:58:52 -07:00
<div className="thin-scroll overflow-x-auto">
<Table>
<thead>
<TrHead>
<Th className="text-left">
2023-07-27 19:44:43 -07:00
<SortableColumnHeader
2023-11-01 17:58:52 -07:00
sortKey="tokenName"
sort={() => requestSort('tokenName')}
2023-07-27 19:44:43 -07:00
sortConfig={sortConfig}
2023-11-01 17:58:52 -07:00
title={t('token')}
2023-07-27 19:44:43 -07:00
/>
2023-11-01 17:58:52 -07:00
</Th>
<Th>
<div className="flex justify-end">
2023-10-04 16:04:51 -07:00
<SortableColumnHeader
2023-11-01 17:58:52 -07:00
sortKey="price"
sort={() => requestSort('price')}
2023-10-04 16:04:51 -07:00
sortConfig={sortConfig}
2023-11-01 17:58:52 -07:00
title={t('price')}
2023-10-04 16:04:51 -07:00
/>
2023-11-01 17:58:52 -07:00
</div>
</Th>
<Th>
<div className="flex justify-end">
2023-10-04 16:04:51 -07:00
<SortableColumnHeader
2023-11-01 17:58:52 -07:00
sortKey="change"
sort={() => requestSort('change')}
2023-10-04 16:04:51 -07:00
sortConfig={sortConfig}
2023-11-01 17:58:52 -07:00
title={t('rolling-change')}
2023-10-04 16:04:51 -07:00
/>
2023-11-01 17:58:52 -07:00
</div>
</Th>
<Th className="hidden text-right md:block"></Th>
<Th>
<div className="flex justify-end">
2023-10-04 16:04:51 -07:00
<SortableColumnHeader
2023-11-01 17:58:52 -07:00
sortKey="volume"
sort={() => requestSort('volume')}
2023-10-04 16:04:51 -07:00
sortConfig={sortConfig}
2023-11-01 17:58:52 -07:00
title={t('trade:24h-volume')}
2023-10-04 16:04:51 -07:00
/>
2023-11-01 17:58:52 -07:00
</div>
</Th>
<Th>
<div className="flex justify-end">
<Tooltip content={t('tooltip-available', { token: '' })}>
<SortableColumnHeader
sortKey="available"
sort={() => requestSort('available')}
sortConfig={sortConfig}
title={t('available')}
titleClass="tooltip-underline"
/>
</Tooltip>
</div>
</Th>
<Th>
<div className="flex justify-end">
<Tooltip
content={t('tooltip-collateral-weight', { token: '' })}
>
<SortableColumnHeader
sortKey="assetWeight"
sort={() => requestSort('assetWeight')}
sortConfig={sortConfig}
title={t('explore:collateral-weight')}
titleClass="tooltip-underline"
/>
</Tooltip>
</div>
</Th>
<Th>
<div className="flex justify-end">
<Tooltip content={t('tooltip-interest-rates')}>
<SortableColumnHeader
sortKey="depositRate"
sort={() => requestSort('depositRate')}
sortConfig={sortConfig}
title={t('rates')}
titleClass="tooltip-underline"
/>
</Tooltip>
</div>
</Th>
<Th />
</TrHead>
</thead>
<tbody>
{tableData.map((data) => {
const {
available,
assetWeight,
baseBank,
borrowRate,
change,
depositRate,
market,
price,
priceHistory,
tokenName,
volume,
isUp,
} = data
2022-10-08 04:37:08 -07:00
2023-11-01 17:58:52 -07:00
if (!baseBank) return null
2023-10-03 22:05:16 -07:00
2023-11-01 17:58:52 -07:00
return (
<TrBody
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
key={tokenName}
onClick={() =>
goToTokenPage(tokenName.split(' ')[0], router)
}
>
<Td>
<TableTokenName bank={baseBank} symbol={tokenName} />
</Td>
<Td>
<div className="flex flex-col text-right">
<p>
{price ? (
<>
<FormatNumericValue value={price} isUsd />
</>
) : (
''
)}
</p>
</div>
</Td>
<Td>
<div className="flex flex-col items-end">
{market ? (
<Change change={change} suffix="%" />
2023-07-27 19:44:43 -07:00
) : (
2023-11-01 17:58:52 -07:00
<span></span>
2023-07-27 19:44:43 -07:00
)}
2023-10-03 22:05:16 -07:00
</div>
2023-11-01 17:58:52 -07:00
</Td>
<Td>
{priceHistory.length ? (
<div className="h-10 w-24">
<SimpleAreaChart
color={isUp ? COLORS.UP[theme] : COLORS.DOWN[theme]}
data={priceHistory}
name={tokenName}
xKey="time"
yKey="price"
/>
</div>
) : !market ? null : (
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
)}
</Td>
<Td>
<div className="flex flex-col text-right">
<p>
{!market ? (
''
) : volume ? (
<span>
{numberCompacter.format(volume)}{' '}
<span className="font-body text-th-fgd-4">
USDC
</span>
2023-05-31 05:15:50 -07:00
</span>
2023-11-01 17:58:52 -07:00
) : (
<span>
0{' '}
<span className="font-body text-th-fgd-4">
USDC
</span>
2023-07-21 05:20:12 -07:00
</span>
2023-11-01 17:58:52 -07:00
)}
</p>
</div>
</Td>
<Td>
<div className="flex flex-col text-right">
<BankAmountWithValue
amount={available}
bank={baseBank}
fixDecimals={false}
stacked
/>
</div>
</Td>
<Td className="text-right font-mono">{assetWeight}x</Td>
<Td>
<div className="flex justify-end space-x-1.5">
<p className="text-th-up">
<FormatNumericValue
value={depositRate}
decimals={2}
/>
%
</p>
<span className="text-th-fgd-4">|</span>
<p className="text-th-down">
<FormatNumericValue value={borrowRate} decimals={2} />
%
</p>
</div>
</Td>
<Td>
<div className="flex justify-end">
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
</div>
</Td>
</TrBody>
)
})}
</tbody>
</Table>
</div>
2022-10-08 04:37:08 -07:00
) : (
2023-05-31 05:15:50 -07:00
<div className="border-b border-th-bkg-3">
2023-07-27 19:44:43 -07:00
{tableData.map((data) => {
2023-10-04 04:52:39 -07:00
return <MobileSpotItem key={data.tokenName} data={data} />
2023-07-27 19:44:43 -07:00
})}
2022-10-08 04:37:08 -07:00
</div>
)}
</ContentBox>
)
}
2023-10-04 04:52:39 -07:00
export default SpotTable
2022-10-08 04:37:08 -07:00
2023-10-04 04:52:39 -07:00
const MobileSpotItem = ({ data }: { data: TableData }) => {
2022-10-08 04:37:08 -07:00
const { t } = useTranslation('common')
2023-07-21 11:50:06 -07:00
const { theme } = useThemeWrapper()
2022-10-08 04:37:08 -07:00
2023-07-27 19:44:43 -07:00
const {
2023-10-04 04:52:39 -07:00
available,
assetWeight,
2023-07-27 19:44:43 -07:00
baseBank,
2023-10-04 04:52:39 -07:00
borrowRate,
2023-07-27 19:44:43 -07:00
change,
2023-10-04 04:52:39 -07:00
depositRate,
2023-07-27 19:44:43 -07:00
market,
price,
priceHistory,
2023-10-04 04:52:39 -07:00
tokenName,
2023-07-27 19:44:43 -07:00
volume,
isUp,
} = data
2023-05-31 05:15:50 -07:00
2022-10-08 04:37:08 -07:00
return (
2023-05-31 05:15:50 -07:00
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button
className={`w-full border-t border-th-bkg-3 p-4 text-left first:border-t-0 focus:outline-none`}
>
<div className="flex items-center justify-between">
<div className="flex items-center">
<div className="flex flex-shrink-0 items-center">
2023-10-04 04:52:39 -07:00
<TokenLogo bank={baseBank} />
2023-05-31 05:15:50 -07:00
</div>
2023-10-04 04:52:39 -07:00
<p className="ml-3 leading-none text-th-fgd-1">{tokenName}</p>
2023-05-31 05:15:50 -07:00
</div>
<div className="flex items-center space-x-3">
2023-10-06 03:33:15 -07:00
{priceHistory.length ? (
2023-10-03 22:05:16 -07:00
<div className="h-10 w-20">
<SimpleAreaChart
color={isUp ? COLORS.UP[theme] : COLORS.DOWN[theme]}
data={priceHistory}
2023-10-04 04:52:39 -07:00
name={tokenName}
2023-10-03 22:05:16 -07:00
xKey="time"
yKey="price"
/>
</div>
2023-10-04 04:52:39 -07:00
) : !market ? null : (
2023-10-03 22:05:16 -07:00
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
2023-05-31 05:15:50 -07:00
)}
2023-10-04 04:52:39 -07:00
{market ? <Change change={change} suffix="%" /> : null}
2023-05-31 05:15:50 -07:00
<ChevronDownIcon
className={`${
open ? 'rotate-180' : 'rotate-360'
} h-6 w-6 flex-shrink-0 text-th-fgd-3`}
/>
</div>
2022-12-21 03:07:17 -08:00
</div>
2023-05-31 05:15:50 -07:00
</Disclosure.Button>
<Transition
enter="transition ease-in duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
>
<Disclosure.Panel>
<div className="mx-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pb-4 pt-4">
2023-05-31 05:15:50 -07:00
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">{t('price')}</p>
<p className="font-mono text-th-fgd-2">
2023-10-04 04:52:39 -07:00
{price ? <FormatNumericValue value={price} isUsd /> : '-'}
2023-05-31 05:15:50 -07:00
</p>
</div>
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">
{t('trade:24h-volume')}
</p>
<p className="font-mono text-th-fgd-2">
2023-10-04 04:52:39 -07:00
{!market ? (
''
) : volume ? (
2023-05-31 05:15:50 -07:00
<span>
2023-07-21 05:03:44 -07:00
{numberCompacter.format(volume)}{' '}
2023-10-04 04:52:39 -07:00
<span className="font-body text-th-fgd-4">USDC</span>
2023-05-31 05:15:50 -07:00
</span>
) : (
2023-07-21 05:20:12 -07:00
<span>
2023-10-04 04:52:39 -07:00
0 <span className="font-body text-th-fgd-4">USDC</span>
2023-07-21 05:20:12 -07:00
</span>
2023-05-31 05:15:50 -07:00
)}
</p>
</div>
2023-10-04 04:52:39 -07:00
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">{t('available')}</p>
<BankAmountWithValue
amount={available}
bank={baseBank}
fixDecimals={false}
/>
</div>
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">
{t('explore:collateral-weight')}
</p>
<p className="font-mono text-th-fgd-2">{assetWeight}x</p>
</div>
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">{t('rates')}</p>
<div className="flex space-x-1.5">
<p className="text-th-up">
<FormatNumericValue value={depositRate} decimals={2} />%
</p>
<span className="text-th-fgd-4">|</span>
<p className="text-th-down">
<FormatNumericValue value={borrowRate} decimals={2} />%
</p>
</div>
</div>
2023-05-31 05:15:50 -07:00
</div>
</Disclosure.Panel>
</Transition>
</>
)}
</Disclosure>
2022-10-08 04:37:08 -07:00
)
}