import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/router' import Link from 'next/link' import { useTranslation } from 'next-i18next' import { ExclamationIcon, InformationCircleIcon, PencilIcon, } from '@heroicons/react/solid' import { ZERO_I80F48 } from '@blockworks-foundation/mango-client' import useMangoStore from '../stores/useMangoStore' import Button, { LinkButton } from '../components/Button' import { useViewport } from '../hooks/useViewport' import { breakpoints } from './TradePageGrid' import { ExpandableRow, Table, Td, Th, TrBody, TrHead } from './TableElements' import { formatUsdValue, getPrecisionDigits, roundPerpSize, usdFormatter, } from '../utils' import Loading from './Loading' import MarketCloseModal from './MarketCloseModal' import PerpSideBadge from './PerpSideBadge' import PnlText from './PnlText' import { settlePnl } from './MarketPosition' import MobileTableHeader from './mobile/MobileTableHeader' import ShareModal from './ShareModal' import { TwitterIcon } from './icons' import { marketSelector } from '../stores/selectors' import { useWallet } from '@solana/wallet-adapter-react' import RedeemButtons from './RedeemButtons' import Tooltip from './Tooltip' import useMangoAccount from 'hooks/useMangoAccount' import useLocalStorageState from 'hooks/useLocalStorageState' import EditTableColumnsModal from './EditTableColumnsModal' const TABLE_COLUMNS = { market: true, side: true, 'position-size': true, 'notional-size': true, 'average-entry': true, 'break-even': true, 'estimated-liq-price': true, 'unrealized-pnl': true, 'unsettled-balance': true, } const PERP_TABLE_COLUMNS_KEY = 'perpPositionTableColumns' const PositionsTable: React.FC = () => { const { t } = useTranslation('common') const [showShareModal, setShowShareModal] = useState(false) const [showMarketCloseModal, setShowMarketCloseModal] = useState(false) const [positionToClose, setPositionToClose] = useState(null) const [positionToShare, setPositionToShare] = useState(null) const [settleSinglePos, setSettleSinglePos] = useState(null) const market = useMangoStore(marketSelector) const { wallet } = useWallet() const price = useMangoStore((s) => s.tradeForm.price) const setMangoStore = useMangoStore((s) => s.set) const openPositions = useMangoStore( (s) => s.selectedMangoAccount.openPerpPositions ) const unsettledPositions = useMangoStore.getState().selectedMangoAccount.unsettledPerpPositions const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache) const { mangoAccount } = useMangoAccount() const { width } = useViewport() const isMobile = width ? width < breakpoints.md : false const { asPath } = useRouter() const [tableColumnsToShow] = useLocalStorageState( PERP_TABLE_COLUMNS_KEY, TABLE_COLUMNS ) const [showEditTableColumns, setShowEditTableColumns] = useState(false) useEffect(() => { if (positionToShare) { const updatedPosition = openPositions.find( (p) => p.marketConfig === positionToShare.marketConfig ) setPositionToShare(updatedPosition) } }, [openPositions]) useEffect(() => { if (positionToClose) { const updatedPosition = openPositions.find( (p) => p.marketConfig === positionToClose.marketConfig ) if (updatedPosition) { setPositionToClose(updatedPosition) } } }, [openPositions]) const handleCloseWarning = useCallback(() => { setShowMarketCloseModal(false) setPositionToClose(null) }, []) const handleSizeClick = (size, indexPrice) => { const sizePrecisionDigits = getPrecisionDigits(market!.minOrderSize) const priceOrDefault = price ? price : indexPrice const roundedSize = parseFloat(Math.abs(size).toFixed(sizePrecisionDigits)) const quoteSize = parseFloat((roundedSize * priceOrDefault).toFixed(2)) setMangoStore((state) => { state.tradeForm.baseSize = roundedSize state.tradeForm.quoteSize = quoteSize state.tradeForm.side = size > 0 ? 'sell' : 'buy' }) } const handleCloseShare = useCallback(() => { setShowShareModal(false) setPositionToShare(null) }, []) const handleShowShare = (position) => { setPositionToShare(position) setShowShareModal(true) } const handleShowMarketCloseModal = (position) => { setPositionToClose(position) setShowMarketCloseModal(true) } const handleSettlePnl = async (perpMarket, perpAccount, index) => { if (wallet) { setSettleSinglePos(index) await settlePnl(perpMarket, perpAccount, t, undefined, wallet) setSettleSinglePos(null) } } const unsettledSum = useMemo(() => { if (unsettledPositions.length > 1) { return unsettledPositions.reduce((a, c) => a + c.unsettledPnl, 0) } return }, [unsettledPositions]) return (
{unsettledPositions.length > 0 ? (

{t('unsettled-positions')}{' '} {unsettledSum ? (
= 0 ? 'text-th-green' : 'text-th-red' } > {formatUsdValue(unsettledSum)}
) : null}

{unsettledPositions.length > 1 ? : null}
{unsettledPositions.map((p, index) => { return (

{p.marketConfig.name}

{settleSinglePos === index ? ( ) : ( handleSettlePnl(p.perpMarket, p.perpAccount, index) } > {t('redeem-pnl')} )}
) })}
) : null}
{openPositions.length ? ( !isMobile ? ( {Object.entries(tableColumnsToShow).map((entry) => entry[1] ? ( ) : null )} setShowEditTableColumns(true)} > {t('edit-columns')} {openPositions.map( ( { marketConfig, perpMarket, perpAccount, basePosition, notionalSize, indexPrice, avgEntryPrice, breakEvenPrice, unrealizedPnl, unsettledPnl, }, index ) => { const basePositionUi = roundPerpSize( basePosition, marketConfig.baseSymbol ) const liquidationPrice = mangoGroup && mangoAccount && marketConfig && mangoGroup && mangoCache ? mangoAccount.getLiquidationPrice( mangoGroup, mangoCache, marketConfig.marketIndex ) : undefined return ( {tableColumnsToShow['market'] ? ( ) : null} {tableColumnsToShow['side'] ? ( ) : null} {tableColumnsToShow['position-size'] ? ( ) : null} {tableColumnsToShow['notional-size'] ? ( ) : null} {tableColumnsToShow['average-entry'] ? ( ) : null} {tableColumnsToShow['break-even'] ? ( ) : null} {tableColumnsToShow['estimated-liq-price'] ? ( ) : null} {tableColumnsToShow['unrealized-pnl'] ? ( ) : null} {tableColumnsToShow['unsettled-balance'] ? ( ) : null} ) } )}
{entry[0] === 'estimated-liq-price' ? ( {t('estimated-liq-price')} ) : ( t(entry[0]) )}
{decodeURIComponent(asPath).includes( marketConfig.name ) ? ( {marketConfig.name} ) : ( {marketConfig.name} )}
{basePosition && asPath.includes(marketConfig.baseSymbol) ? ( handleSizeClick(basePosition, indexPrice) } > {basePositionUi} ) : ( {basePositionUi} )} {formatUsdValue(Math.abs(notionalSize))} {avgEntryPrice ? formatUsdValue(avgEntryPrice) : '-'} {breakEvenPrice ? formatUsdValue(breakEvenPrice) : '-'} {liquidationPrice && liquidationPrice.gt(ZERO_I80F48) ? usdFormatter(liquidationPrice) : 'N/A'} {unrealizedPnl ? ( ) : ( '-' )} {unsettledPnl ? ( settleSinglePos === index ? ( ) : ( = 0 ? 'text-th-green' : 'text-th-red' } onClick={() => handleSettlePnl( perpMarket, perpAccount, index ) } disabled={unsettledPnl === 0} > {formatUsdValue(unsettledPnl)} ) ) : ( '--' )}
handleShowShare(openPositions[index]) } disabled={!avgEntryPrice ? true : false} >
) : (
{openPositions.map( ( { marketConfig, basePosition, notionalSize, avgEntryPrice, breakEvenPrice, perpAccount, perpMarket, unrealizedPnl, unsettledPnl, }, index ) => { const basePositionUi = roundPerpSize( basePosition, marketConfig.baseSymbol ) const liquidationPrice = mangoGroup && mangoAccount && marketConfig && mangoGroup && mangoCache ? mangoAccount.getLiquidationPrice( mangoGroup, mangoCache, marketConfig.marketIndex ) : undefined return (
{marketConfig.name}
0 ? 'text-th-green' : 'text-th-red' }`} > {basePosition > 0 ? t('long').toUpperCase() : t('short').toUpperCase()} {basePositionUi}
{breakEvenPrice ? ( ) : ( '--' )}
} key={`${index}`} panelTemplate={
{t('average-entry')}
{avgEntryPrice ? formatUsdValue(avgEntryPrice) : '--'}
{t('notional-size')}
{formatUsdValue(notionalSize)}
{t('break-even')}
{breakEvenPrice ? formatUsdValue(breakEvenPrice) : '--'}
{t('unsettled-balance')}
{unsettledPnl ? ( settleSinglePos === index ? ( ) : ( = 0 ? 'text-th-green' : 'text-th-red' }`} onClick={() => handleSettlePnl( perpMarket, perpAccount, index ) } disabled={unsettledPnl === 0} > {formatUsdValue(unsettledPnl)} ) ) : ( '--' )}
{t('estimated-liq-price')}
{liquidationPrice && liquidationPrice.gt(ZERO_I80F48) ? usdFormatter(liquidationPrice) : 'N/A'}
} /> ) } )}
) ) : (
{t('no-perp')}
)}
{showShareModal ? ( ) : null} {showMarketCloseModal ? ( ) : null} {showEditTableColumns ? ( setShowEditTableColumns(false)} columns={tableColumnsToShow} storageKey={PERP_TABLE_COLUMNS_KEY} /> ) : null}
) } export default PositionsTable