mango-ui-v3/components/PerpPositionsTable.tsx

361 lines
14 KiB
TypeScript
Raw Normal View History

2021-08-24 20:15:57 -07:00
import { useCallback, useState } from 'react'
import { useRouter } from 'next/router'
import Link from 'next/link'
2022-01-27 19:57:18 -08:00
import { useTranslation } from 'next-i18next'
2021-12-06 09:50:15 -08:00
import { ExclamationIcon } from '@heroicons/react/outline'
2022-01-27 19:57:18 -08:00
import useMangoStore from '../stores/useMangoStore'
2022-02-21 21:22:21 -08:00
import Button, { LinkButton } from '../components/Button'
2021-09-07 18:52:45 -07:00
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid'
import { ExpandableRow, Table, Td, Th, TrBody, TrHead } from './TableElements'
import {
formatUsdValue,
getPrecisionDigits,
perpContractPrecision,
} from '../utils'
2021-12-06 09:50:15 -08:00
import Loading from './Loading'
2021-08-24 20:15:57 -07:00
import MarketCloseModal from './MarketCloseModal'
import PerpSideBadge from './PerpSideBadge'
import PnlText from './PnlText'
2021-12-06 09:50:15 -08:00
import { settlePnl } from './MarketPosition'
import MobileTableHeader from './mobile/MobileTableHeader'
2021-06-27 21:44:48 -07:00
const PositionsTable = () => {
const { t } = useTranslation('common')
2021-12-06 09:50:15 -08:00
const { reloadMangoAccount } = useMangoStore((s) => s.actions)
const [settling, setSettling] = useState(false)
2022-02-21 21:22:21 -08:00
const [settleSinglePos, setSettleSinglePos] = useState(null)
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
const price = useMangoStore((s) => s.tradeForm.price)
2021-08-24 20:15:57 -07:00
const [showMarketCloseModal, setShowMarketCloseModal] = useState(false)
2021-08-20 04:51:29 -07:00
const setMangoStore = useMangoStore((s) => s.set)
2022-01-27 19:57:18 -08:00
const openPositions = useMangoStore(
(s) => s.selectedMangoAccount.openPerpPositions
)
const unsettledPositions =
useMangoStore.getState().selectedMangoAccount.unsettledPerpPositions
2021-09-07 18:52:45 -07:00
const { width } = useViewport()
const isMobile = width ? width < breakpoints.md : false
const { asPath } = useRouter()
2021-06-27 21:44:48 -07:00
2021-08-24 20:15:57 -07:00
const handleCloseWarning = useCallback(() => {
setShowMarketCloseModal(false)
}, [])
const handleSizeClick = (size, indexPrice) => {
const sizePrecisionDigits = getPrecisionDigits(selectedMarket.minOrderSize)
const priceOrDefault = price ? price : indexPrice
const roundedSize = parseFloat(Math.abs(size).toFixed(sizePrecisionDigits))
const quoteSize = parseFloat((roundedSize * priceOrDefault).toFixed(2))
2021-08-20 04:51:29 -07:00
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
2022-02-10 09:49:59 -08:00
state.tradeForm.quoteSize = quoteSize
state.tradeForm.side = size > 0 ? 'sell' : 'buy'
2021-08-20 04:51:29 -07:00
})
}
2021-12-06 09:50:15 -08:00
const handleSettleAll = async () => {
setSettling(true)
for (const p of unsettledPositions) {
await settlePnl(p.perpMarket, p.perpAccount, t, undefined)
}
reloadMangoAccount()
2021-12-06 09:50:15 -08:00
setSettling(false)
}
2022-02-21 21:22:21 -08:00
const handleSettlePnl = async (perpMarket, perpAccount, index) => {
setSettleSinglePos(index)
await settlePnl(perpMarket, perpAccount, t, undefined)
setSettleSinglePos(null)
}
2021-06-27 21:44:48 -07:00
return (
2021-12-07 20:00:02 -08:00
<div className="flex flex-col pb-2">
2021-12-06 09:50:15 -08:00
{unsettledPositions.length > 0 ? (
2022-02-22 02:42:25 -08:00
<div className="border border-th-bkg-4 mb-6 p-4 sm:p-6 rounded-lg">
2022-02-21 21:22:21 -08:00
<div className="flex items-center justify-between pb-4">
2022-02-20 19:55:13 -08:00
<div className="flex items-center">
<ExclamationIcon className="flex-shrink-0 h-5 mr-1.5 mt-0.5 text-th-primary w-5" />
2022-02-20 19:55:13 -08:00
<h3>{t('unsettled-positions')}</h3>
</div>
2022-02-21 21:22:21 -08:00
<Button
className="text-xs pt-0 pb-0 h-8 pl-3 pr-3 whitespace-nowrap"
onClick={handleSettleAll}
>
2022-02-21 21:22:21 -08:00
{settling ? <Loading /> : 'Redeem All'}
</Button>
</div>
2022-02-22 18:13:57 -08:00
<div className="gap-4 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 grid-flow-row">
2022-02-21 21:22:21 -08:00
{unsettledPositions.map((p, index) => {
return (
<div
2022-02-22 02:42:25 -08:00
className="bg-th-bkg-3 col-span-1 flex items-center justify-between px-5 py-3 rounded-full"
2022-02-21 21:22:21 -08:00
key={p.marketConfig.baseSymbol}
>
<div className="flex space-x-2">
<div className="flex items-center">
<img
alt=""
width="24"
height="24"
src={`/assets/icons/${p.marketConfig.baseSymbol.toLowerCase()}.svg`}
className={`mr-3`}
/>
<div>
<p className="mb-0 text-th-fgd-1 text-xs">
{p.marketConfig.name}
</p>
<PnlText className="font-bold" pnl={p.unsettledPnl} />
</div>
</div>
</div>
{settleSinglePos === index ? (
<Loading />
) : (
<LinkButton
className="text-xs"
onClick={() =>
handleSettlePnl(p.perpMarket, p.perpAccount, index)
}
>
{t('redeem-pnl')}
</LinkButton>
)}
</div>
2022-02-21 21:22:21 -08:00
)
})}
</div>
</div>
2021-12-06 09:50:15 -08:00
) : null}
2021-12-07 20:00:02 -08:00
<div className={`md:overflow-x-auto`}>
2021-11-09 01:24:43 -08:00
<div className={`align-middle inline-block min-w-full`}>
{openPositions.length ? (
2021-09-07 18:52:45 -07:00
!isMobile ? (
<Table>
<thead>
<TrHead>
<Th>{t('market')}</Th>
<Th>{t('side')}</Th>
<Th>{t('position-size')}</Th>
<Th>{t('notional-size')}</Th>
<Th>{t('average-entry')}</Th>
<Th>{t('break-even')}</Th>
<Th>{t('unrealized-pnl')}</Th>
2021-09-07 18:52:45 -07:00
</TrHead>
</thead>
<tbody>
{openPositions.map(
2022-02-10 15:48:14 -08:00
({
marketConfig,
perpMarket,
perpAccount,
basePosition,
notionalSize,
indexPrice,
avgEntryPrice,
breakEvenPrice,
unrealizedPnl,
}) => {
const basePositionUi = Math.abs(
basePosition
).toLocaleString(undefined, {
maximumFractionDigits:
perpContractPrecision[marketConfig.baseSymbol],
})
return (
2022-02-10 15:48:14 -08:00
<TrBody key={`${marketConfig.marketIndex}`}>
<Td>
<div className="flex items-center">
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${marketConfig.baseSymbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
{decodeURIComponent(asPath).includes(
marketConfig.name
) ? (
<span>{marketConfig.name}</span>
) : (
2021-11-14 06:48:09 -08:00
<Link
href={{
2022-01-31 09:48:48 -08:00
pathname: '/',
query: { name: marketConfig.name },
}}
2021-11-14 06:48:09 -08:00
shallow={true}
>
<a className="text-th-fgd-1 underline hover:no-underline hover:text-th-fgd-1">
{marketConfig.name}
</a>
</Link>
)}
</div>
</Td>
<Td>
<PerpSideBadge perpAccount={perpAccount} />
</Td>
<Td>
{basePosition &&
selectedMarketConfig.kind === 'perp' &&
asPath.includes(marketConfig.baseSymbol) ? (
<span
className="cursor-pointer underline hover:no-underline"
onClick={() =>
handleSizeClick(basePosition, indexPrice)
}
>
{`${basePositionUi} ${marketConfig.baseSymbol}`}
2021-09-07 18:52:45 -07:00
</span>
) : (
<span>
{`${basePositionUi} ${marketConfig.baseSymbol}`}
2021-09-07 18:52:45 -07:00
</span>
)}
</Td>
<Td>{formatUsdValue(Math.abs(notionalSize))}</Td>
<Td>
{avgEntryPrice
? formatUsdValue(avgEntryPrice)
: '--'}
</Td>
<Td>
{breakEvenPrice
? formatUsdValue(breakEvenPrice)
: '--'}
</Td>
<Td>
{breakEvenPrice ? (
<PnlText pnl={unrealizedPnl} />
) : (
'--'
)}
</Td>
{showMarketCloseModal ? (
<MarketCloseModal
isOpen={showMarketCloseModal}
onClose={handleCloseWarning}
market={perpMarket}
2022-01-27 19:57:18 -08:00
marketIndex={marketConfig.marketIndex}
/>
) : null}
</TrBody>
)
}
)}
2021-09-07 18:52:45 -07:00
</tbody>
2021-06-27 21:44:48 -07:00
</Table>
2021-09-07 18:52:45 -07:00
) : (
<>
<MobileTableHeader
colOneHeader={t('market')}
colTwoHeader={t('unrealized-pnl')}
/>
{openPositions.map(
(
{
marketConfig,
basePosition,
notionalSize,
avgEntryPrice,
breakEvenPrice,
unrealizedPnl,
},
index
) => {
2022-03-05 19:03:42 -08:00
const basePositionUi = Math.abs(
basePosition
).toLocaleString(undefined, {
maximumFractionDigits:
perpContractPrecision[marketConfig.baseSymbol],
})
return (
<ExpandableRow
buttonTemplate={
<>
<div className="flex items-center justify-between text-fgd-1 w-full">
<div className="flex items-center">
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${marketConfig.baseSymbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
<div>
<div className="mb-0.5 text-left">
{marketConfig.name}
</div>
<div className="text-th-fgd-3 text-xs">
<span
className={`mr-1 ${
basePosition > 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{basePosition > 0
? t('long').toUpperCase()
: t('short').toUpperCase()}
</span>
2022-03-05 19:03:42 -08:00
{basePositionUi}
</div>
</div>
2021-09-07 18:52:45 -07:00
</div>
<PnlText pnl={unrealizedPnl} />
2021-09-07 18:52:45 -07:00
</div>
</>
}
key={`${index}`}
panelTemplate={
<div className="grid grid-cols-2 grid-flow-row gap-4">
<div className="col-span-1 text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
{t('average-entry')}
</div>
{avgEntryPrice
? formatUsdValue(avgEntryPrice)
: '--'}
</div>
<div className="col-span-1 text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
{t('notional-size')}
</div>
{formatUsdValue(notionalSize)}
</div>
<div className="col-span-1 text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
{t('break-even')}
</div>
{breakEvenPrice
? formatUsdValue(breakEvenPrice)
: '--'}
</div>
2021-09-07 18:52:45 -07:00
</div>
}
/>
)
}
)}
</>
2021-09-07 18:52:45 -07:00
)
2021-06-27 21:44:48 -07:00
) : (
<div
className={`w-full text-center py-6 bg-th-bkg-1 text-th-fgd-3 rounded-md`}
>
{t('no-perp')}
2021-06-27 21:44:48 -07:00
</div>
)}
</div>
</div>
</div>
)
}
export default PositionsTable