merge main
This commit is contained in:
commit
8cf597c63f
|
@ -4,18 +4,18 @@ import { formatYAxis } from 'utils/formatting'
|
|||
import { HourlyFundingChartData, PerformanceDataItem } from 'types'
|
||||
import { ContentType } from 'recharts/types/component/Tooltip'
|
||||
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
||||
import { ChartToShow } from './AccountPage'
|
||||
import { ViewToShow } from './AccountPage'
|
||||
import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
|
||||
const CHART_TABS: ChartToShow[] = [
|
||||
const CHART_TABS: ViewToShow[] = [
|
||||
'account-value',
|
||||
'pnl',
|
||||
'cumulative-interest-value',
|
||||
]
|
||||
|
||||
const AccountChart = ({
|
||||
chartToShow,
|
||||
setChartToShow,
|
||||
chartName,
|
||||
handleViewChange,
|
||||
customTooltip,
|
||||
data,
|
||||
hideChart,
|
||||
|
@ -23,8 +23,8 @@ const AccountChart = ({
|
|||
yDecimals,
|
||||
yKey,
|
||||
}: {
|
||||
chartToShow: ChartToShow
|
||||
setChartToShow: (chart: ChartToShow) => void
|
||||
chartName: ViewToShow
|
||||
handleViewChange: (view: ViewToShow) => void
|
||||
customTooltip?: ContentType<number, string>
|
||||
data: PerformanceDataItem[] | HourlyFundingChartData[] | undefined
|
||||
hideChart: () => void
|
||||
|
@ -37,7 +37,7 @@ const AccountChart = ({
|
|||
|
||||
const chartData = useMemo(() => {
|
||||
if (!data || !data.length) return []
|
||||
if (chartToShow === 'cumulative-interest-value') {
|
||||
if (chartName === 'cumulative-interest-value') {
|
||||
return data.map((d) => ({
|
||||
interest_value:
|
||||
d.borrow_interest_cumulative_usd * -1 +
|
||||
|
@ -46,7 +46,7 @@ const AccountChart = ({
|
|||
}))
|
||||
}
|
||||
return data
|
||||
}, [data, chartToShow])
|
||||
}, [data, chartName])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -61,11 +61,11 @@ const AccountChart = ({
|
|||
{CHART_TABS.map((tab) => (
|
||||
<button
|
||||
className={`whitespace-nowrap rounded-md py-1.5 px-2.5 text-sm font-medium focus-visible:bg-th-bkg-3 focus-visible:text-th-fgd-1 ${
|
||||
chartToShow === tab
|
||||
chartName === tab
|
||||
? 'bg-th-bkg-3 text-th-active md:hover:text-th-active'
|
||||
: 'text-th-fgd-3 md:hover:text-th-fgd-2'
|
||||
}`}
|
||||
onClick={() => setChartToShow(tab)}
|
||||
onClick={() => handleViewChange(tab)}
|
||||
key={tab}
|
||||
>
|
||||
{t(tab)}
|
||||
|
|
|
@ -3,7 +3,7 @@ import useMangoAccount from 'hooks/useMangoAccount'
|
|||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { ChartToShow } from './AccountPage'
|
||||
import { ViewToShow } from './AccountPage'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchFundingTotals, fetchVolumeTotals } from 'utils/account'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
|
@ -25,13 +25,13 @@ const AccountHeroStats = ({
|
|||
accountValue,
|
||||
rollingDailyData,
|
||||
setShowPnlHistory,
|
||||
setChartToShow,
|
||||
handleViewChange,
|
||||
}: {
|
||||
accountPnl: number
|
||||
accountValue: number
|
||||
rollingDailyData: PerformanceDataItem[]
|
||||
setShowPnlHistory: (show: boolean) => void
|
||||
setChartToShow: (view: ChartToShow) => void
|
||||
handleViewChange: (view: ViewToShow) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'account'])
|
||||
const { group } = useMangoGroup()
|
||||
|
@ -162,71 +162,78 @@ const AccountHeroStats = ({
|
|||
return volume
|
||||
}, [hourlyVolumeData])
|
||||
|
||||
const handleChartToShow = (
|
||||
viewName:
|
||||
| 'pnl'
|
||||
| 'cumulative-interest-value'
|
||||
| 'hourly-funding'
|
||||
| 'hourly-volume'
|
||||
) => {
|
||||
setChartToShow(viewName)
|
||||
}
|
||||
|
||||
const loadingTotalVolume = fetchingVolumeTotalData || loadingVolumeTotalData
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-6 border-b border-th-bkg-3">
|
||||
<div className="col-span-6 border-t border-th-bkg-3 py-3 px-6 md:col-span-3 lg:col-span-2 lg:border-t-0 xl:col-span-1">
|
||||
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 lg:col-span-2 lg:border-t-0 xl:col-span-1">
|
||||
<div id="account-step-four">
|
||||
<Tooltip
|
||||
maxWidth="20rem"
|
||||
placement="top-start"
|
||||
delay={100}
|
||||
content={
|
||||
<div className="flex-col space-y-2 text-sm">
|
||||
<p className="text-xs">
|
||||
Health describes how close your account is to liquidation.
|
||||
The lower your account health is the more likely you are to
|
||||
get liquidated when prices fluctuate.
|
||||
</p>
|
||||
{maintHealth < 100 && mangoAccountAddress ? (
|
||||
<>
|
||||
<p className="text-xs font-bold text-th-fgd-1">
|
||||
Your account health is {maintHealth}%
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
Scenario:
|
||||
</span>{' '}
|
||||
If the prices of all your liabilities increase by{' '}
|
||||
{maintHealth}%, even for just a moment, some of your
|
||||
liabilities will be liquidated.
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
Scenario:
|
||||
</span>{' '}
|
||||
If the value of your total collateral decreases by{' '}
|
||||
{(
|
||||
(1 - 1 / ((maintHealth || 0) / 100 + 1)) *
|
||||
100
|
||||
).toFixed(2)}
|
||||
% , some of your liabilities will be liquidated.
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
These are examples. A combination of events can also
|
||||
lead to liquidation.
|
||||
</p>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p className="tooltip-underline text-sm font-normal text-th-fgd-3 xl:text-base">
|
||||
{t('health')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<div className="flex justify-between">
|
||||
<Tooltip
|
||||
maxWidth="20rem"
|
||||
placement="top-start"
|
||||
delay={100}
|
||||
content={
|
||||
<div className="flex-col space-y-2 text-sm">
|
||||
<p className="text-xs">
|
||||
Health describes how close your account is to liquidation.
|
||||
The lower your account health is the more likely you are
|
||||
to get liquidated when prices fluctuate.
|
||||
</p>
|
||||
{maintHealth < 100 && mangoAccountAddress ? (
|
||||
<>
|
||||
<p className="text-xs font-bold text-th-fgd-1">
|
||||
Your account health is {maintHealth}%
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
Scenario:
|
||||
</span>{' '}
|
||||
If the prices of all your liabilities increase by{' '}
|
||||
{maintHealth}%, even for just a moment, some of your
|
||||
liabilities will be liquidated.
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
Scenario:
|
||||
</span>{' '}
|
||||
If the value of your total collateral decreases by{' '}
|
||||
{(
|
||||
(1 - 1 / ((maintHealth || 0) / 100 + 1)) *
|
||||
100
|
||||
).toFixed(2)}
|
||||
% , some of your liabilities will be liquidated.
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
These are examples. A combination of events can also
|
||||
lead to liquidation.
|
||||
</p>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p className="tooltip-underline text-sm font-normal text-th-fgd-3 xl:text-base">
|
||||
{t('health')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
{mangoAccountAddress ? (
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
content={t('account:health-contributions')}
|
||||
delay={100}
|
||||
>
|
||||
<IconButton
|
||||
className="text-th-fgd-3"
|
||||
hideBg
|
||||
onClick={() => handleViewChange('health-contributions')}
|
||||
>
|
||||
<ChartBarIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-1 mb-0.5 flex items-center space-x-3">
|
||||
<p className="text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
||||
{maintHealth}%
|
||||
|
@ -298,7 +305,7 @@ const AccountHeroStats = ({
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-6 flex border-t border-th-bkg-3 py-3 px-6 md:col-span-3 lg:col-span-2 lg:border-l lg:border-t-0 xl:col-span-1">
|
||||
<div className="col-span-6 flex border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 lg:col-span-2 lg:border-l lg:border-t-0 xl:col-span-1">
|
||||
<div
|
||||
id="account-step-seven"
|
||||
className="flex w-full flex-col items-start"
|
||||
|
@ -323,7 +330,7 @@ const AccountHeroStats = ({
|
|||
<IconButton
|
||||
className="text-th-fgd-3"
|
||||
hideBg
|
||||
onClick={() => handleChartToShow('pnl')}
|
||||
onClick={() => handleViewChange('pnl')}
|
||||
>
|
||||
<ChartBarIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
|
@ -372,7 +379,7 @@ const AccountHeroStats = ({
|
|||
<IconButton
|
||||
className="text-th-fgd-3"
|
||||
hideBg
|
||||
onClick={() => handleChartToShow('hourly-volume')}
|
||||
onClick={() => handleViewChange('hourly-volume')}
|
||||
>
|
||||
<ChartBarIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
|
@ -429,7 +436,7 @@ const AccountHeroStats = ({
|
|||
className="text-th-fgd-3"
|
||||
hideBg
|
||||
onClick={() =>
|
||||
handleChartToShow('cumulative-interest-value')
|
||||
handleViewChange('cumulative-interest-value')
|
||||
}
|
||||
>
|
||||
<ChartBarIcon className="h-5 w-5" />
|
||||
|
@ -471,7 +478,7 @@ const AccountHeroStats = ({
|
|||
<IconButton
|
||||
className="text-th-fgd-3"
|
||||
hideBg
|
||||
onClick={() => handleChartToShow('hourly-funding')}
|
||||
onClick={() => handleViewChange('hourly-funding')}
|
||||
>
|
||||
<ChartBarIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import AccountActions from './AccountActions'
|
||||
import AccountTabs from './AccountTabs'
|
||||
import AccountChart from './AccountChart'
|
||||
import useMangoAccount from '../../hooks/useMangoAccount'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
// import AccountOnboardingTour from '@components/tours/AccountOnboardingTour'
|
||||
import dayjs from 'dayjs'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
|
@ -18,39 +17,44 @@ import VolumeChart from './VolumeChart'
|
|||
import AccountHeroStats from './AccountHeroStats'
|
||||
import AccountValue from './AccountValue'
|
||||
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
||||
import useAccountHourlyVolumeStats from 'hooks/useAccountHourlyVolumeStats'
|
||||
import HealthContributions from './HealthContributions'
|
||||
import { PerformanceDataItem } from 'types'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const TABS = ['account-value', 'account:assets-liabilities']
|
||||
|
||||
export type ChartToShow =
|
||||
export type ViewToShow =
|
||||
| ''
|
||||
| 'account-value'
|
||||
| 'cumulative-interest-value'
|
||||
| 'pnl'
|
||||
| 'hourly-funding'
|
||||
| 'hourly-volume'
|
||||
| 'health-contributions'
|
||||
|
||||
const AccountPage = () => {
|
||||
const { t } = useTranslation(['common', 'account'])
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const [chartToShow, setChartToShow] = useState<ChartToShow>('')
|
||||
const [showPnlHistory, setShowPnlHistory] = useState<boolean>(false)
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
// const tourSettings = mangoStore((s) => s.settings.tours)
|
||||
// const [isOnBoarded] = useLocalStorageState(IS_ONBOARDED_KEY)
|
||||
const [activeTab, setActiveTab] = useLocalStorageState(
|
||||
'accountHeroKey-0.1',
|
||||
'account-value'
|
||||
)
|
||||
const { performanceData, rollingDailyData } = useAccountPerformanceData()
|
||||
const { hourlyVolumeData, loadingHourlyVolume } =
|
||||
useAccountHourlyVolumeStats()
|
||||
const router = useRouter()
|
||||
const { view } = router.query
|
||||
|
||||
const handleHideChart = () => {
|
||||
setChartToShow('')
|
||||
}
|
||||
const handleViewChange = useCallback(
|
||||
(view: ViewToShow) => {
|
||||
const query = { ...router.query, ['view']: view }
|
||||
router.push({ pathname: router.pathname, query })
|
||||
},
|
||||
[router]
|
||||
)
|
||||
|
||||
const handleCloseDailyPnlModal = () => {
|
||||
setShowPnlHistory(false)
|
||||
|
@ -94,7 +98,7 @@ const AccountPage = () => {
|
|||
]
|
||||
}, [accountPnl, accountValue, performanceData])
|
||||
|
||||
return !chartToShow ? (
|
||||
return !view ? (
|
||||
<>
|
||||
<div className="flex flex-col border-b-0 border-th-bkg-3 px-6 py-4 lg:flex-row lg:items-center lg:justify-between lg:border-b">
|
||||
<div>
|
||||
|
@ -119,7 +123,7 @@ const AccountPage = () => {
|
|||
accountValue={accountValue}
|
||||
latestAccountData={latestAccountData}
|
||||
rollingDailyData={rollingDailyData}
|
||||
setChartToShow={setChartToShow}
|
||||
handleViewChange={handleViewChange}
|
||||
/>
|
||||
) : null}
|
||||
{activeTab === 'account:assets-liabilities' ? (
|
||||
|
@ -135,13 +139,10 @@ const AccountPage = () => {
|
|||
accountPnl={accountPnl}
|
||||
accountValue={accountValue}
|
||||
rollingDailyData={rollingDailyData}
|
||||
setChartToShow={setChartToShow}
|
||||
setShowPnlHistory={setShowPnlHistory}
|
||||
handleViewChange={handleViewChange}
|
||||
/>
|
||||
<AccountTabs />
|
||||
{/* {!tourSettings?.account_tour_seen && isOnBoarded && connected ? (
|
||||
<AccountOnboardingTour />
|
||||
) : null} */}
|
||||
{showPnlHistory ? (
|
||||
<PnlHistoryModal
|
||||
pnlChangeToday={pnlChangeToday}
|
||||
|
@ -151,42 +152,76 @@ const AccountPage = () => {
|
|||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{chartToShow === 'account-value' ? (
|
||||
<AccountChart
|
||||
chartToShow="account-value"
|
||||
setChartToShow={setChartToShow}
|
||||
data={performanceData?.concat(latestAccountData)}
|
||||
hideChart={handleHideChart}
|
||||
yKey="account_equity"
|
||||
/>
|
||||
) : chartToShow === 'pnl' ? (
|
||||
<AccountChart
|
||||
chartToShow="pnl"
|
||||
setChartToShow={setChartToShow}
|
||||
data={performanceData?.concat(latestAccountData)}
|
||||
hideChart={handleHideChart}
|
||||
yKey="pnl"
|
||||
/>
|
||||
) : chartToShow === 'hourly-funding' ? (
|
||||
<FundingChart hideChart={handleHideChart} />
|
||||
) : chartToShow === 'hourly-volume' ? (
|
||||
<VolumeChart
|
||||
chartData={hourlyVolumeData}
|
||||
hideChart={handleHideChart}
|
||||
loading={loadingHourlyVolume}
|
||||
/>
|
||||
) : (
|
||||
<AccountChart
|
||||
chartToShow="cumulative-interest-value"
|
||||
setChartToShow={setChartToShow}
|
||||
data={performanceData}
|
||||
hideChart={handleHideChart}
|
||||
yKey="interest_value"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<AccountView
|
||||
view={view as ViewToShow}
|
||||
latestAccountData={latestAccountData}
|
||||
handleViewChange={handleViewChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
|
||||
const AccountView = ({
|
||||
view,
|
||||
handleViewChange,
|
||||
latestAccountData,
|
||||
}: {
|
||||
view: ViewToShow
|
||||
latestAccountData: PerformanceDataItem[]
|
||||
handleViewChange: (view: ViewToShow) => void
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { connected } = useWallet()
|
||||
const { address } = router.query
|
||||
const { performanceData } = useAccountPerformanceData()
|
||||
|
||||
const handleHideChart = useCallback(() => {
|
||||
if (address && !connected) {
|
||||
router.push(`/?address=${address}`, undefined, { shallow: true })
|
||||
} else {
|
||||
router.push('/', undefined, { shallow: true })
|
||||
}
|
||||
}, [address, router, connected])
|
||||
|
||||
switch (view) {
|
||||
case 'account-value':
|
||||
return (
|
||||
<AccountChart
|
||||
chartName="account-value"
|
||||
handleViewChange={handleViewChange}
|
||||
data={performanceData?.concat(latestAccountData)}
|
||||
hideChart={handleHideChart}
|
||||
yKey="account_equity"
|
||||
/>
|
||||
)
|
||||
case 'pnl':
|
||||
return (
|
||||
<AccountChart
|
||||
chartName="pnl"
|
||||
handleViewChange={handleViewChange}
|
||||
data={performanceData?.concat(latestAccountData)}
|
||||
hideChart={handleHideChart}
|
||||
yKey="pnl"
|
||||
/>
|
||||
)
|
||||
case 'cumulative-interest-value':
|
||||
return (
|
||||
<AccountChart
|
||||
chartName="cumulative-interest-value"
|
||||
handleViewChange={handleViewChange}
|
||||
data={performanceData?.concat(latestAccountData)}
|
||||
hideChart={handleHideChart}
|
||||
yKey="interest_value"
|
||||
/>
|
||||
)
|
||||
case 'hourly-funding':
|
||||
return <FundingChart hideChart={handleHideChart} />
|
||||
case 'hourly-volume':
|
||||
return <VolumeChart hideChart={handleHideChart} />
|
||||
case 'health-contributions':
|
||||
return <HealthContributions hideView={handleHideChart} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,19 +19,19 @@ import { useMemo, useState } from 'react'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { ChartToShow } from './AccountPage'
|
||||
import { ViewToShow } from './AccountPage'
|
||||
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
||||
|
||||
const AccountValue = ({
|
||||
accountValue,
|
||||
latestAccountData,
|
||||
rollingDailyData,
|
||||
setChartToShow,
|
||||
handleViewChange,
|
||||
}: {
|
||||
accountValue: number
|
||||
latestAccountData: PerformanceDataItem[]
|
||||
rollingDailyData: PerformanceDataItem[]
|
||||
setChartToShow: (chart: ChartToShow) => void
|
||||
handleViewChange: (view: ViewToShow) => void
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { theme } = useTheme()
|
||||
|
@ -62,7 +62,7 @@ const AccountValue = ({
|
|||
}
|
||||
|
||||
const handleShowAccountValueChart = () => {
|
||||
setChartToShow('account-value')
|
||||
handleViewChange('account-value')
|
||||
setShowExpandChart(false)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,338 @@
|
|||
import HealthContributionsChart from './HealthContributionsChart'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
NoSymbolIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import MarketLogos from '@components/trade/MarketLogos'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import TokensHealthTable from './TokensHealthTable'
|
||||
import MarketsHealthTable from './MarketsHealthTable'
|
||||
import { HealthContribution, PerpMarketContribution } from 'types'
|
||||
|
||||
const HealthContributions = ({ hideView }: { hideView: () => void }) => {
|
||||
const { t } = useTranslation(['common', 'account', 'trade'])
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const [initActiveIndex, setInitActiveIndex] = useState<number | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [maintActiveIndex, setMaintActiveIndex] = useState<number | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const [initHealthContributions, maintHealthContributions] = useMemo(() => {
|
||||
if (!group || !mangoAccount) return [[], []]
|
||||
const initAssets = mangoAccount.getHealthContributionPerAssetUi(
|
||||
group,
|
||||
HealthType.init
|
||||
)
|
||||
const initContributions = []
|
||||
for (const item of initAssets) {
|
||||
const contribution = item.contribution
|
||||
if (item.asset === 'USDC') {
|
||||
const hasPerp =
|
||||
!!item.contributionDetails?.perpMarketContributions.find(
|
||||
(perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0
|
||||
)
|
||||
initContributions.push({
|
||||
...item,
|
||||
contribution: Math.abs(contribution),
|
||||
hasPerp: hasPerp,
|
||||
isAsset: contribution > 0 ? true : false,
|
||||
})
|
||||
if (item.contributionDetails) {
|
||||
for (const perpMarket of item.contributionDetails
|
||||
.perpMarketContributions) {
|
||||
const contribution = Math.abs(perpMarket.contributionUi)
|
||||
if (contribution > 0) {
|
||||
initContributions.push({
|
||||
asset: perpMarket.market,
|
||||
contribution: contribution,
|
||||
isAsset: perpMarket.contributionUi > 0 ? true : false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
initContributions.push({
|
||||
...item,
|
||||
isAsset: contribution > 0 ? true : false,
|
||||
contribution: Math.abs(contribution),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const maintAssets = mangoAccount.getHealthContributionPerAssetUi(
|
||||
group,
|
||||
HealthType.maint
|
||||
)
|
||||
const maintContributions = []
|
||||
for (const item of maintAssets) {
|
||||
const contribution = item.contribution
|
||||
if (item.asset === 'USDC') {
|
||||
const hasPerp =
|
||||
!!item.contributionDetails?.perpMarketContributions.find(
|
||||
(perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0
|
||||
)
|
||||
maintContributions.push({
|
||||
...item,
|
||||
hasPerp: hasPerp,
|
||||
isAsset: contribution > 0 ? true : false,
|
||||
contribution: Math.abs(contribution),
|
||||
})
|
||||
if (item.contributionDetails) {
|
||||
for (const perpMarket of item.contributionDetails
|
||||
.perpMarketContributions) {
|
||||
const contribution = Math.abs(perpMarket.contributionUi)
|
||||
if (contribution > 0) {
|
||||
maintContributions.push({
|
||||
asset: perpMarket.market,
|
||||
contribution: contribution,
|
||||
isAsset: perpMarket.contributionUi > 0 ? true : false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
maintContributions.push({
|
||||
...item,
|
||||
isAsset: contribution > 0 ? true : false,
|
||||
contribution: Math.abs(contribution),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return [initContributions, maintContributions]
|
||||
}, [group, mangoAccount])
|
||||
|
||||
const [initHealthMarkets, initHealthTokens] = useMemo(() => {
|
||||
if (!initHealthContributions.length) return [[], []]
|
||||
const splitData = initHealthContributions.reduce(
|
||||
(
|
||||
acc: { market: HealthContribution[]; token: HealthContribution[] },
|
||||
obj: HealthContribution
|
||||
) => {
|
||||
const isPerp = obj.asset.includes('PERP')
|
||||
const isSpotMarket = obj.asset.includes('/')
|
||||
if (isSpotMarket) {
|
||||
acc.market.push(obj)
|
||||
}
|
||||
if (!isPerp && !isSpotMarket) {
|
||||
acc.token.push(obj)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ market: [], token: [] }
|
||||
)
|
||||
return [splitData.market, splitData.token]
|
||||
}, [initHealthContributions])
|
||||
|
||||
const [maintHealthMarkets, maintHealthTokens] = useMemo(() => {
|
||||
if (!maintHealthContributions.length) return [[], []]
|
||||
const splitData = maintHealthContributions.reduce(
|
||||
(
|
||||
acc: { market: HealthContribution[]; token: HealthContribution[] },
|
||||
obj: HealthContribution
|
||||
) => {
|
||||
const isPerp = obj.asset.includes('PERP')
|
||||
const isSpotMarket = obj.asset.includes('/')
|
||||
if (isSpotMarket) {
|
||||
acc.market.push(obj)
|
||||
}
|
||||
if (!isPerp && !isSpotMarket) {
|
||||
acc.token.push(obj)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ market: [], token: [] }
|
||||
)
|
||||
const markets = splitData.market.filter((d) => d.contribution > 0)
|
||||
const tokens = splitData.token
|
||||
return [markets, tokens]
|
||||
}, [maintHealthContributions])
|
||||
|
||||
const handleLegendClick = (item: HealthContribution) => {
|
||||
const maintIndex = maintChartData.findIndex((d) => d.asset === item.asset)
|
||||
const initIndex = initChartData.findIndex((d) => d.asset === item.asset)
|
||||
setMaintActiveIndex(maintIndex)
|
||||
setInitActiveIndex(initIndex)
|
||||
}
|
||||
|
||||
const handleLegendMouseEnter = (item: HealthContribution) => {
|
||||
const maintIndex = maintChartData.findIndex((d) => d.asset === item.asset)
|
||||
const initIndex = initChartData.findIndex((d) => d.asset === item.asset)
|
||||
setMaintActiveIndex(maintIndex)
|
||||
setInitActiveIndex(initIndex)
|
||||
}
|
||||
|
||||
const handleLegendMouseLeave = () => {
|
||||
setInitActiveIndex(undefined)
|
||||
setMaintActiveIndex(undefined)
|
||||
}
|
||||
|
||||
const renderLegendLogo = (asset: string) => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group)
|
||||
return <QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
const isSpotMarket = asset.includes('/')
|
||||
const isPerpMarket = asset.includes('PERP')
|
||||
const isMarket = isSpotMarket || isPerpMarket
|
||||
if (isMarket) {
|
||||
let market
|
||||
if (isSpotMarket) {
|
||||
market = group.getSerum3MarketByName(asset)
|
||||
} else {
|
||||
market = group.getPerpMarketByName(asset)
|
||||
}
|
||||
return market ? (
|
||||
<MarketLogos market={market} size="small" />
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
)
|
||||
} else {
|
||||
const bank = group.banksMapByName.get(asset)?.[0]
|
||||
return bank ? (
|
||||
<div className="mr-1.5">
|
||||
<TokenLogo bank={bank} size={16} />
|
||||
</div>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const initChartData = useMemo(() => {
|
||||
if (!initHealthContributions.length) return []
|
||||
return initHealthContributions
|
||||
.filter((cont) => {
|
||||
if (cont.asset.includes('PERP')) {
|
||||
return
|
||||
} else if (cont.asset.includes('/')) {
|
||||
return cont.contribution > 0.01
|
||||
} else return cont
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aMultiplier = a.isAsset ? 1 : -1
|
||||
const bMultiplier = b.isAsset ? 1 : -1
|
||||
return b.contribution * bMultiplier - a.contribution * aMultiplier
|
||||
})
|
||||
}, [initHealthContributions])
|
||||
|
||||
const maintChartData = useMemo(() => {
|
||||
if (!maintHealthContributions.length) return []
|
||||
return maintHealthContributions
|
||||
.filter((cont) => {
|
||||
if (cont.asset.includes('PERP')) {
|
||||
return
|
||||
} else if (cont.asset.includes('/')) {
|
||||
return cont.contribution > 0.01
|
||||
} else return cont
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aMultiplier = a.isAsset ? 1 : -1
|
||||
const bMultiplier = b.isAsset ? 1 : -1
|
||||
return b.contribution * bMultiplier - a.contribution * aMultiplier
|
||||
})
|
||||
}, [maintHealthContributions])
|
||||
|
||||
return group ? (
|
||||
<>
|
||||
<div className="hide-scroll flex h-14 items-center space-x-4 overflow-x-auto border-b border-th-bkg-3">
|
||||
<button
|
||||
className="flex h-14 w-14 flex-shrink-0 items-center justify-center border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
|
||||
onClick={hideView}
|
||||
>
|
||||
<ArrowLeftIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<h2 className="text-lg">{t('account:health-contributions')}</h2>
|
||||
</div>
|
||||
{mangoAccountAddress ? (
|
||||
<>
|
||||
<div className="mx-auto grid max-w-[1140px] grid-cols-2 gap-6 p-6 sm:gap-8">
|
||||
<div className="col-span-1 flex h-full flex-col items-center">
|
||||
<Tooltip content={t('account:tooltip-init-health')}>
|
||||
<h3 className="tooltip-underline text-xs sm:text-base">
|
||||
{t('account:init-health-contributions')}
|
||||
</h3>
|
||||
</Tooltip>
|
||||
<HealthContributionsChart
|
||||
data={initChartData}
|
||||
activeIndex={initActiveIndex}
|
||||
setActiveIndex={setInitActiveIndex}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 flex flex-col items-center">
|
||||
<Tooltip content={t('account:tooltip-maint-health')}>
|
||||
<h3 className="tooltip-underline text-xs sm:text-base">
|
||||
{t('account:maint-health-contributions')}
|
||||
</h3>
|
||||
</Tooltip>
|
||||
<HealthContributionsChart
|
||||
data={maintChartData}
|
||||
activeIndex={maintActiveIndex}
|
||||
setActiveIndex={setMaintActiveIndex}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 mx-auto flex max-w-[600px] flex-wrap justify-center space-x-4">
|
||||
{[...maintChartData]
|
||||
.sort((a, b) => b.contribution - a.contribution)
|
||||
.map((d, i) => {
|
||||
return (
|
||||
<div
|
||||
key={d.asset + i}
|
||||
className={`default-transition flex h-7 cursor-pointer items-center md:hover:text-th-active`}
|
||||
onClick={() => handleLegendClick(d)}
|
||||
onMouseEnter={() => handleLegendMouseEnter(d)}
|
||||
onMouseLeave={handleLegendMouseLeave}
|
||||
>
|
||||
{renderLegendLogo(d.asset)}
|
||||
<span className={`default-transition`}>{d.asset}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{maintHealthTokens.length ? (
|
||||
<div className="border-t border-th-bkg-3 pt-6">
|
||||
<h2 className="mb-1 px-6 text-lg">{t('tokens')}</h2>
|
||||
<TokensHealthTable
|
||||
initTokens={initHealthTokens}
|
||||
maintTokens={maintHealthTokens}
|
||||
handleLegendClick={handleLegendClick}
|
||||
handleLegendMouseEnter={handleLegendMouseEnter}
|
||||
handleLegendMouseLeave={handleLegendMouseLeave}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{maintHealthMarkets.length ? (
|
||||
<div className="pt-6">
|
||||
<h2 className="mb-1 px-6 text-lg">{t('markets')}</h2>
|
||||
<MarketsHealthTable
|
||||
initMarkets={initHealthMarkets}
|
||||
maintMarkets={maintHealthMarkets}
|
||||
handleLegendClick={handleLegendClick}
|
||||
handleLegendMouseEnter={handleLegendMouseEnter}
|
||||
handleLegendMouseLeave={handleLegendMouseLeave}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div className="mx-6 mt-6 flex flex-col items-center rounded-lg border border-th-bkg-3 p-8">
|
||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('account:no-data')}</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default HealthContributions
|
|
@ -0,0 +1,148 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useTheme } from 'next-themes'
|
||||
import {
|
||||
Cell,
|
||||
Pie,
|
||||
PieChart,
|
||||
ResponsiveContainer,
|
||||
Sector,
|
||||
SectorProps,
|
||||
} from 'recharts'
|
||||
import { COLORS } from 'styles/colors'
|
||||
import { useMemo } from 'react'
|
||||
import { formatCurrencyValue } from 'utils/numbers'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { HealthContribution } from 'types'
|
||||
|
||||
const HealthContributionsChart = ({
|
||||
data,
|
||||
activeIndex,
|
||||
setActiveIndex,
|
||||
}: {
|
||||
data: HealthContribution[]
|
||||
activeIndex: number | undefined
|
||||
setActiveIndex: (i: number | undefined) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'account'])
|
||||
const { theme } = useTheme()
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
const handleClick = (index: number) => {
|
||||
setActiveIndex(index)
|
||||
}
|
||||
|
||||
const handleMouseEnter = (data: HealthContribution, index: number) => {
|
||||
setActiveIndex(index)
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setActiveIndex(undefined)
|
||||
}
|
||||
|
||||
const pieSizes = isMobile
|
||||
? { size: 160, outerRadius: 80, innerRadius: 64 }
|
||||
: { size: 240, outerRadius: 120, innerRadius: 96 }
|
||||
const { size, outerRadius, innerRadius } = pieSizes
|
||||
|
||||
const [chartHeroAsset, chartHeroValue] = useMemo(() => {
|
||||
if (!data.length) return [undefined, undefined]
|
||||
if (activeIndex === undefined) {
|
||||
const value = data.reduce((a, c) => {
|
||||
const assetOrLiabMultiplier = c.isAsset ? 1 : -1
|
||||
return a + c.contribution * assetOrLiabMultiplier
|
||||
}, 0)
|
||||
return [t('total'), value]
|
||||
} else {
|
||||
const asset = data[activeIndex]
|
||||
const assetOrLiabMultiplier = asset.isAsset ? 1 : -1
|
||||
const value = asset.contribution * assetOrLiabMultiplier
|
||||
return [asset.asset, value]
|
||||
}
|
||||
}, [activeIndex, data])
|
||||
|
||||
const renderActiveShape = ({
|
||||
cx,
|
||||
cy,
|
||||
innerRadius,
|
||||
outerRadius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
fill,
|
||||
}: SectorProps) => {
|
||||
return (
|
||||
<g>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius! + 4}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
return data.length ? (
|
||||
<div className="mt-4 flex h-full w-full flex-col items-center">
|
||||
<div className="relative h-[168px] w-[168px] sm:h-[248px] sm:w-[248px]">
|
||||
<ResponsiveContainer height="100%" width="100%">
|
||||
<PieChart width={size} height={size}>
|
||||
<Pie
|
||||
cursor="pointer"
|
||||
data={data}
|
||||
dataKey="contribution"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={outerRadius}
|
||||
innerRadius={innerRadius}
|
||||
minAngle={2}
|
||||
startAngle={90}
|
||||
endAngle={450}
|
||||
activeIndex={activeIndex}
|
||||
activeShape={renderActiveShape}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{data.map((entry, index) => {
|
||||
const fillColor = entry.isAsset
|
||||
? COLORS.UP[theme]
|
||||
: COLORS.DOWN[theme]
|
||||
|
||||
let opacity
|
||||
|
||||
if (entry.isAsset) {
|
||||
opacity = 1 - index * 0.1
|
||||
} else {
|
||||
opacity = 1 - Math.abs((index - (data.length - 1)) * 0.1)
|
||||
}
|
||||
return (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={fillColor}
|
||||
opacity={opacity}
|
||||
stroke="none"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
{chartHeroValue !== undefined ? (
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-center">
|
||||
<p className="text-xs sm:text-sm">{chartHeroAsset}</p>
|
||||
<span className="text-base font-bold sm:text-xl">
|
||||
{formatCurrencyValue(chartHeroValue, 2)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default HealthContributionsChart
|
|
@ -0,0 +1,276 @@
|
|||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { MouseEventHandler } from 'react'
|
||||
import MarketLogos from '@components/trade/MarketLogos'
|
||||
import { HealthContribution } from 'types'
|
||||
|
||||
const MarketsHealthTable = ({
|
||||
initMarkets,
|
||||
maintMarkets,
|
||||
handleLegendClick,
|
||||
handleLegendMouseEnter,
|
||||
handleLegendMouseLeave,
|
||||
}: {
|
||||
initMarkets: HealthContribution[]
|
||||
maintMarkets: HealthContribution[]
|
||||
handleLegendClick: (cont: HealthContribution) => void
|
||||
handleLegendMouseEnter: (cont: HealthContribution) => void
|
||||
handleLegendMouseLeave: MouseEventHandler
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'account', 'trade'])
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
return group && mangoAccount ? (
|
||||
!isMobile ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('market')}</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('account:tooltip-init-health')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('account:init-health-contribution')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('account:tooltip-maint-health')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('account:maint-health-contribution')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{maintMarkets
|
||||
.sort((a, b) => b.contribution - a.contribution)
|
||||
.map((cont) => {
|
||||
const { asset, contribution, isAsset } = cont
|
||||
const market = group.getSerum3MarketByName(asset)
|
||||
const bank = group.banksMapByTokenIndex.get(
|
||||
market.baseTokenIndex
|
||||
)?.[0]
|
||||
|
||||
let initAssetWeight = 0
|
||||
let initLiabWeight = 0
|
||||
let maintAssetWeight = 0
|
||||
let maintLiabWeight = 0
|
||||
|
||||
if (bank) {
|
||||
initAssetWeight = bank
|
||||
.scaledInitAssetWeight(bank.price)
|
||||
.toNumber()
|
||||
initLiabWeight = bank
|
||||
.scaledInitLiabWeight(bank.price)
|
||||
.toNumber()
|
||||
maintAssetWeight = bank.maintAssetWeight.toNumber()
|
||||
maintLiabWeight = bank.maintLiabWeight.toNumber()
|
||||
}
|
||||
|
||||
const assetOrLiabMultiplier = isAsset ? 1 : -1
|
||||
|
||||
const initContribution =
|
||||
(initMarkets.find((cont) => cont.asset === asset)
|
||||
?.contribution || 0) * assetOrLiabMultiplier
|
||||
|
||||
const maintContribution = contribution * assetOrLiabMultiplier
|
||||
|
||||
return (
|
||||
<TrBody
|
||||
key={asset}
|
||||
className="cursor-pointer md:hover:bg-th-bkg-2"
|
||||
onClick={() => handleLegendClick(cont)}
|
||||
onMouseEnter={() => handleLegendMouseEnter(cont)}
|
||||
onMouseLeave={handleLegendMouseLeave}
|
||||
>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market} />
|
||||
<p className="font-body">{asset}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="text-right">
|
||||
<p>
|
||||
<FormatNumericValue
|
||||
value={initContribution}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="text-th-fgd-3">
|
||||
{initContribution > 0
|
||||
? initAssetWeight.toFixed(2)
|
||||
: initContribution < 0
|
||||
? initLiabWeight.toFixed(2)
|
||||
: 0}
|
||||
x
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="text-right">
|
||||
<p>
|
||||
<FormatNumericValue
|
||||
value={maintContribution}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="text-th-fgd-3">
|
||||
{maintContribution > 0
|
||||
? maintAssetWeight.toFixed(2)
|
||||
: maintContribution < 0
|
||||
? maintLiabWeight.toFixed(2)
|
||||
: 0}
|
||||
x
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="mt-3 border-y border-th-bkg-3">
|
||||
{maintMarkets
|
||||
.sort((a, b) => b.contribution - a.contribution)
|
||||
.map((cont) => {
|
||||
const { asset, contribution, isAsset } = cont
|
||||
const market = group.getSerum3MarketByName(asset)
|
||||
const bank = group.banksMapByTokenIndex.get(
|
||||
market.baseTokenIndex
|
||||
)?.[0]
|
||||
|
||||
let initAssetWeight = 0
|
||||
let initLiabWeight = 0
|
||||
let maintAssetWeight = 0
|
||||
let maintLiabWeight = 0
|
||||
|
||||
if (bank) {
|
||||
initAssetWeight = bank
|
||||
.scaledInitAssetWeight(bank.price)
|
||||
.toNumber()
|
||||
initLiabWeight = bank.scaledInitLiabWeight(bank.price).toNumber()
|
||||
maintAssetWeight = bank.maintAssetWeight.toNumber()
|
||||
maintLiabWeight = bank.maintLiabWeight.toNumber()
|
||||
}
|
||||
|
||||
const assetOrLiabMultiplier = isAsset ? 1 : -1
|
||||
|
||||
const initContribution =
|
||||
(initMarkets.find((cont) => cont.asset === asset)?.contribution ||
|
||||
0) * assetOrLiabMultiplier
|
||||
|
||||
const maintContribution = contribution * assetOrLiabMultiplier
|
||||
|
||||
return (
|
||||
<Disclosure key={asset}>
|
||||
{({ 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">
|
||||
<MarketLogos market={market} />
|
||||
<div>
|
||||
<p className="text-th-fgd-1">{asset}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} h-6 w-6 flex-shrink-0 text-th-fgd-3`}
|
||||
/>
|
||||
</div>
|
||||
</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 pt-4 pb-4">
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<Tooltip
|
||||
content={t('account:tooltip-init-health')}
|
||||
>
|
||||
<span className="tooltip-underline">
|
||||
{t('account:init-health-contribution')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
<FormatNumericValue
|
||||
value={initContribution}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-3">
|
||||
{initContribution > 0
|
||||
? initAssetWeight.toFixed(2)
|
||||
: initContribution < 0
|
||||
? initLiabWeight.toFixed(2)
|
||||
: 0}
|
||||
x
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<Tooltip
|
||||
content={t('account:tooltip-maint-health')}
|
||||
>
|
||||
<span className="tooltip-underline">
|
||||
{t('account:maint-health-contribution')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-2">
|
||||
<FormatNumericValue
|
||||
value={maintContribution}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-3">
|
||||
{maintContribution > 0
|
||||
? maintAssetWeight.toFixed(2)
|
||||
: maintContribution < 0
|
||||
? maintLiabWeight.toFixed(2)
|
||||
: 0}
|
||||
x
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
||||
export default MarketsHealthTable
|
|
@ -0,0 +1,406 @@
|
|||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { MouseEventHandler } from 'react'
|
||||
import { ContributionDetails, HealthContribution } from 'types'
|
||||
|
||||
const TokensHealthTable = ({
|
||||
initTokens,
|
||||
maintTokens,
|
||||
handleLegendClick,
|
||||
handleLegendMouseEnter,
|
||||
handleLegendMouseLeave,
|
||||
}: {
|
||||
initTokens: HealthContribution[]
|
||||
maintTokens: HealthContribution[]
|
||||
handleLegendClick: (cont: HealthContribution) => void
|
||||
handleLegendMouseEnter: (cont: HealthContribution) => void
|
||||
handleLegendMouseLeave: MouseEventHandler
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'account', 'trade'])
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
return group && mangoAccount ? (
|
||||
!isMobile ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('token')}</Th>
|
||||
<Th className="text-right">{t('trade:notional')}</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('account:tooltip-init-health')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('account:init-health-contribution')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
<Th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('account:tooltip-maint-health')}>
|
||||
<span className="tooltip-underline">
|
||||
{t('account:maint-health-contribution')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{maintTokens
|
||||
.sort((a, b) => b.contribution - a.contribution)
|
||||
.map((cont) => {
|
||||
const {
|
||||
asset,
|
||||
contribution,
|
||||
contributionDetails,
|
||||
isAsset,
|
||||
hasPerp,
|
||||
} = cont
|
||||
const bank = group.banksMapByName.get(asset)?.[0]
|
||||
|
||||
let initAssetWeight = 0
|
||||
let initLiabWeight = 0
|
||||
let maintAssetWeight = 0
|
||||
let maintLiabWeight = 0
|
||||
|
||||
let balance = 0
|
||||
|
||||
if (bank) {
|
||||
initAssetWeight = bank
|
||||
.scaledInitAssetWeight(bank.price)
|
||||
.toNumber()
|
||||
initLiabWeight = bank
|
||||
.scaledInitLiabWeight(bank.price)
|
||||
.toNumber()
|
||||
maintAssetWeight = bank.maintAssetWeight.toNumber()
|
||||
maintLiabWeight = bank.maintLiabWeight.toNumber()
|
||||
|
||||
balance = mangoAccount.getTokenBalanceUi(bank)
|
||||
}
|
||||
|
||||
const assetOrLiabMultiplier = isAsset ? 1 : -1
|
||||
const initToken = initTokens.find((cont) => cont.asset === asset)
|
||||
const initContribution =
|
||||
(initToken?.contribution || 0) * assetOrLiabMultiplier
|
||||
|
||||
const maintContribution = contribution * assetOrLiabMultiplier
|
||||
|
||||
return (
|
||||
<TrBody
|
||||
key={asset}
|
||||
className="cursor-pointer md:hover:bg-th-bkg-2"
|
||||
onClick={() => handleLegendClick(cont)}
|
||||
onMouseEnter={() => handleLegendMouseEnter(cont)}
|
||||
onMouseLeave={handleLegendMouseLeave}
|
||||
>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
<TokenLogo bank={bank} />
|
||||
</div>
|
||||
<p className="font-body">{asset}</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
{bank ? (
|
||||
<p>
|
||||
<FormatNumericValue
|
||||
value={balance * bank.uiPrice}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>{' '}
|
||||
<span className={`block text-th-fgd-4`}>
|
||||
<FormatNumericValue
|
||||
value={balance}
|
||||
decimals={bank.mintDecimals}
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
) : (
|
||||
'–'
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col items-end text-right">
|
||||
<Tooltip
|
||||
className={!hasPerp ? 'hidden' : ''}
|
||||
content={
|
||||
<UsdcTooltipContent
|
||||
contributions={initToken?.contributionDetails}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<p className={hasPerp ? 'tooltip-underline' : ''}>
|
||||
<FormatNumericValue
|
||||
value={initContribution}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="text-th-fgd-3">
|
||||
{initContribution > 0
|
||||
? initAssetWeight.toFixed(2)
|
||||
: initContribution < 0
|
||||
? initLiabWeight.toFixed(2)
|
||||
: 0}
|
||||
x
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col items-end text-right">
|
||||
<Tooltip
|
||||
className={!hasPerp ? 'hidden' : ''}
|
||||
content={
|
||||
<UsdcTooltipContent
|
||||
contributions={contributionDetails}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<p className={hasPerp ? 'tooltip-underline' : ''}>
|
||||
<FormatNumericValue
|
||||
value={maintContribution}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="text-th-fgd-3">
|
||||
{maintContribution > 0
|
||||
? maintAssetWeight.toFixed(2)
|
||||
: maintContribution < 0
|
||||
? maintLiabWeight.toFixed(2)
|
||||
: 0}
|
||||
x
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="mt-3 border-y border-th-bkg-3">
|
||||
{maintTokens
|
||||
.sort((a, b) => b.contribution - a.contribution)
|
||||
.map((cont) => {
|
||||
const {
|
||||
asset,
|
||||
contribution,
|
||||
contributionDetails,
|
||||
isAsset,
|
||||
hasPerp,
|
||||
} = cont
|
||||
const bank = group.banksMapByName.get(asset)?.[0]
|
||||
|
||||
let initAssetWeight = 0
|
||||
let initLiabWeight = 0
|
||||
let maintAssetWeight = 0
|
||||
let maintLiabWeight = 0
|
||||
|
||||
let balance = 0
|
||||
|
||||
if (bank) {
|
||||
initAssetWeight = bank
|
||||
.scaledInitAssetWeight(bank.price)
|
||||
.toNumber()
|
||||
initLiabWeight = bank.scaledInitLiabWeight(bank.price).toNumber()
|
||||
maintAssetWeight = bank.maintAssetWeight.toNumber()
|
||||
maintLiabWeight = bank.maintLiabWeight.toNumber()
|
||||
|
||||
balance = mangoAccount.getTokenBalanceUi(bank)
|
||||
}
|
||||
|
||||
const assetOrLiabMultiplier = isAsset ? 1 : -1
|
||||
|
||||
const initToken = initTokens.find((cont) => cont.asset === asset)
|
||||
const initContribution =
|
||||
(initToken?.contribution || 0) * assetOrLiabMultiplier
|
||||
|
||||
const maintContribution = contribution * assetOrLiabMultiplier
|
||||
|
||||
return (
|
||||
<Disclosure key={asset}>
|
||||
{({ 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="mr-2.5">
|
||||
<TokenLogo bank={bank} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-th-fgd-1">{asset}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} h-6 w-6 flex-shrink-0 text-th-fgd-3`}
|
||||
/>
|
||||
</div>
|
||||
</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 pt-4 pb-4">
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('trade:notional')}
|
||||
</p>
|
||||
<p>
|
||||
{bank ? (
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
<FormatNumericValue
|
||||
value={balance * bank.uiPrice}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>{' '}
|
||||
<span className={`block text-th-fgd-4`}>
|
||||
<FormatNumericValue
|
||||
value={balance}
|
||||
decimals={bank.mintDecimals}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
'–'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('account:init-health-contribution')}
|
||||
</p>
|
||||
<Tooltip
|
||||
className={!hasPerp ? 'hidden' : ''}
|
||||
content={
|
||||
<UsdcTooltipContent
|
||||
contributions={initToken?.contributionDetails}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<p
|
||||
className={`font-mono text-th-fgd-2 ${
|
||||
hasPerp ? 'tooltip-underline' : ''
|
||||
}`}
|
||||
>
|
||||
<FormatNumericValue
|
||||
value={initContribution}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-3">
|
||||
{initContribution > 0
|
||||
? initAssetWeight.toFixed(2)
|
||||
: initContribution < 0
|
||||
? initLiabWeight.toFixed(2)
|
||||
: 0}
|
||||
x
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('account:maint-health-contribution')}
|
||||
</p>
|
||||
<Tooltip
|
||||
className={!hasPerp ? 'hidden' : ''}
|
||||
content={
|
||||
<UsdcTooltipContent
|
||||
contributions={contributionDetails}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<p
|
||||
className={`font-mono text-th-fgd-2 ${
|
||||
hasPerp ? 'tooltip-underline' : ''
|
||||
}`}
|
||||
>
|
||||
<FormatNumericValue
|
||||
value={maintContribution}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-3">
|
||||
{maintContribution > 0
|
||||
? maintAssetWeight.toFixed(2)
|
||||
: maintContribution < 0
|
||||
? maintLiabWeight.toFixed(2)
|
||||
: 0}
|
||||
x
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
||||
export default TokensHealthTable
|
||||
|
||||
const UsdcTooltipContent = ({
|
||||
contributions,
|
||||
}: {
|
||||
contributions: ContributionDetails | undefined
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
if (!contributions) return null
|
||||
const { perpMarketContributions, spotUi } = contributions
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<p className="mr-3">{t('spot')}</p>
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
<FormatNumericValue value={spotUi} decimals={2} isUsd />
|
||||
</span>
|
||||
</div>
|
||||
{perpMarketContributions
|
||||
.filter((cont) => Math.abs(cont.contributionUi) > 0.01)
|
||||
.map((perp) => (
|
||||
<div className="flex justify-between" key={perp.market}>
|
||||
<p className="mr-3">{perp.market}</p>
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
<FormatNumericValue
|
||||
value={perp.contributionUi}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -24,18 +24,15 @@ import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
|||
import { FadeInFadeOut } from '@components/shared/Transitions'
|
||||
import ContentBox from '@components/shared/ContentBox'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import useAccountHourlyVolumeStats from 'hooks/useAccountHourlyVolumeStats'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { DAILY_MILLISECONDS } from 'utils/constants'
|
||||
|
||||
const VolumeChart = ({
|
||||
chartData,
|
||||
hideChart,
|
||||
loading,
|
||||
}: {
|
||||
chartData: FormattedHourlyAccountVolumeData[] | undefined
|
||||
hideChart: () => void
|
||||
loading: boolean
|
||||
}) => {
|
||||
const VolumeChart = ({ hideChart }: { hideChart: () => void }) => {
|
||||
const { t } = useTranslation(['account', 'common', 'stats'])
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const { hourlyVolumeData: chartData, loadingHourlyVolume: loading } =
|
||||
useAccountHourlyVolumeStats()
|
||||
const [daysToShow, setDaysToShow] = useState('30')
|
||||
const { theme } = useTheme()
|
||||
|
||||
|
@ -160,8 +157,8 @@ const VolumeChart = ({
|
|||
onChange={(v) => setDaysToShow(v)}
|
||||
/>
|
||||
</div>
|
||||
{loading ? (
|
||||
<SheenLoader className="flex flex-1">
|
||||
{loading && mangoAccountAddress ? (
|
||||
<SheenLoader className="mt-6 flex flex-1">
|
||||
<div
|
||||
className={`h-[calc(100vh-166px)] w-full rounded-lg bg-th-bkg-2`}
|
||||
/>
|
||||
|
|
|
@ -6,11 +6,7 @@ import { useViewport } from 'hooks/useViewport'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatNumericValue,
|
||||
getDecimalCount,
|
||||
} from 'utils/numbers'
|
||||
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
|
||||
import { LinkButton } from './Button'
|
||||
|
@ -28,6 +24,9 @@ import { Disclosure, Transition } from '@headlessui/react'
|
|||
import TokenLogo from './TokenLogo'
|
||||
import useHealthContributions from 'hooks/useHealthContributions'
|
||||
import Tooltip from './Tooltip'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { USDC_MINT } from 'utils/constants'
|
||||
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
|
||||
|
||||
const BalancesTable = () => {
|
||||
const { t } = useTranslation(['common', 'account', 'trade'])
|
||||
|
@ -329,12 +328,20 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
|
|||
const handleSwapFormBalanceClick = useCallback(
|
||||
(balance: number) => {
|
||||
const set = mangoStore.getState().set
|
||||
const group = mangoStore.getState().group
|
||||
const swap = mangoStore.getState().swap
|
||||
const usdcBank = group?.getFirstBankByMint(new PublicKey(USDC_MINT))
|
||||
const solBank = group?.getFirstBankByMint(WRAPPED_SOL_MINT)
|
||||
if (balance >= 0) {
|
||||
set((s) => {
|
||||
s.swap.inputBank = tokenBank
|
||||
s.swap.amountIn = balance.toString()
|
||||
s.swap.amountOut = ''
|
||||
s.swap.swapMode = 'ExactIn'
|
||||
if (tokenBank.name === swap.outputBank?.name) {
|
||||
s.swap.outputBank =
|
||||
swap.outputBank.name === 'USDC' ? solBank : usdcBank
|
||||
}
|
||||
})
|
||||
} else {
|
||||
set((s) => {
|
||||
|
@ -342,6 +349,10 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
|
|||
s.swap.amountIn = ''
|
||||
s.swap.amountOut = Math.abs(balance).toString()
|
||||
s.swap.swapMode = 'ExactOut'
|
||||
if (tokenBank.name === swap.inputBank?.name) {
|
||||
s.swap.inputBank =
|
||||
swap.inputBank.name === 'USDC' ? solBank : usdcBank
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -382,7 +393,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
|
|||
className="font-normal underline underline-offset-2 md:underline-offset-4 md:hover:no-underline"
|
||||
onClick={() =>
|
||||
handleSwapFormBalanceClick(
|
||||
Number(formatNumericValue(balance, tokenBank.mintDecimals))
|
||||
Number(floorToDecimal(balance, tokenBank.mintDecimals))
|
||||
)
|
||||
}
|
||||
>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import dayjs from 'dayjs'
|
||||
import { ReactNode, forwardRef } from 'react'
|
||||
import { MouseEventHandler, ReactNode, forwardRef } from 'react'
|
||||
|
||||
export const Table = ({
|
||||
children,
|
||||
|
@ -43,15 +44,19 @@ interface TrBodyProps {
|
|||
children: ReactNode
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
onMouseEnter?: (x: any) => void
|
||||
onMouseLeave?: MouseEventHandler
|
||||
}
|
||||
|
||||
export const TrBody = forwardRef<HTMLTableRowElement, TrBodyProps>(
|
||||
(props, ref) => {
|
||||
const { children, className, onClick } = props
|
||||
const { children, className, onClick, onMouseEnter, onMouseLeave } = props
|
||||
return (
|
||||
<tr
|
||||
className={`border-y border-th-bkg-3 ${className}`}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -26,8 +26,6 @@ export default function useAccountHourlyVolumeStats() {
|
|||
|
||||
return {
|
||||
hourlyVolumeData,
|
||||
loadingHourlyVolumeData,
|
||||
fetchingHourlyVolumeData,
|
||||
loadingHourlyVolume,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,22 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
"liabilities": "Liabilities",
|
||||
"lifetime-volume": "Lifetime Trade Volume",
|
||||
"maint-health-contribution": "Maint Health Contribution",
|
||||
"maint-health-contributions": "Maint Health Contributions",
|
||||
"no-data": "No data to display",
|
||||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
"tooltip-leverage": "Total assets value divided by account equity value",
|
||||
"tooltip-maint-health": "The contribution an asset gives to your maintenance account health. If your maintenance health reaches 0 your account will be liquidated.",
|
||||
"tooltip-pnl": "The amount your account has profited or lost",
|
||||
"tooltip-total-collateral": "Total value of collateral for trading and borrowing (including unsettled PnL)",
|
||||
"tooltip-total-funding": "The sum of perp position funding earned and paid",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"mango": "Mango",
|
||||
"mango-stats": "Mango Stats",
|
||||
"market": "Market",
|
||||
"markets": "Markets",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow",
|
||||
"more": "More",
|
||||
|
|
|
@ -6,15 +6,22 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
"liabilities": "Liabilities",
|
||||
"lifetime-volume": "Lifetime Trade Volume",
|
||||
"maint-health-contribution": "Maint Health Contribution",
|
||||
"maint-health-contributions": "Maint Health Contributions",
|
||||
"no-data": "No data to display",
|
||||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
"tooltip-leverage": "Total assets value divided by account equity value",
|
||||
"tooltip-maint-health": "The contribution an asset gives to your maintenance account health. If your maintenance health reaches 0 your account will be liquidated.",
|
||||
"tooltip-pnl": "The amount your account has profited or lost",
|
||||
"tooltip-total-collateral": "Total value of collateral for trading and borrowing (including unsettled PnL)",
|
||||
"tooltip-total-funding": "The sum of perp position funding earned and paid",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"mango": "Mango",
|
||||
"mango-stats": "Mango Stats",
|
||||
"market": "Market",
|
||||
"markets": "Markets",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow",
|
||||
"more": "More",
|
||||
|
|
|
@ -6,15 +6,22 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
"liabilities": "Liabilities",
|
||||
"lifetime-volume": "Lifetime Trade Volume",
|
||||
"maint-health-contribution": "Maint Health Contribution",
|
||||
"maint-health-contributions": "Maint Health Contributions",
|
||||
"no-data": "No data to display",
|
||||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
"tooltip-leverage": "Total assets value divided by account equity value",
|
||||
"tooltip-maint-health": "The contribution an asset gives to your maintenance account health. If your maintenance health reaches 0 your account will be liquidated.",
|
||||
"tooltip-pnl": "The amount your account has profited or lost",
|
||||
"tooltip-total-collateral": "Total value of collateral for trading and borrowing (including unsettled PnL)",
|
||||
"tooltip-total-funding": "The sum of perp position funding earned and paid",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"mango": "Mango",
|
||||
"mango-stats": "Mango Stats",
|
||||
"market": "Market",
|
||||
"markets": "Markets",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow",
|
||||
"more": "More",
|
||||
|
|
|
@ -6,15 +6,22 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
"liabilities": "Liabilities",
|
||||
"lifetime-volume": "Lifetime Trade Volume",
|
||||
"maint-health-contribution": "Maint Health Contribution",
|
||||
"maint-health-contributions": "Maint Health Contributions",
|
||||
"no-data": "No data to display",
|
||||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
"tooltip-leverage": "Total assets value divided by account equity value",
|
||||
"tooltip-maint-health": "The contribution an asset gives to your maintenance account health. If your maintenance health reaches 0 your account will be liquidated.",
|
||||
"tooltip-pnl": "The amount your account has profited or lost",
|
||||
"tooltip-total-collateral": "Total value of collateral for trading and borrowing (including unsettled PnL)",
|
||||
"tooltip-total-funding": "The sum of perp position funding earned and paid",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"mango": "Mango",
|
||||
"mango-stats": "Mango统计",
|
||||
"market": "市场",
|
||||
"markets": "Markets",
|
||||
"max": "最多",
|
||||
"max-borrow": "最多借贷",
|
||||
"more": "更多",
|
||||
|
|
|
@ -6,15 +6,22 @@
|
|||
"daily-volume": "24h Volume",
|
||||
"export": "Export {{dataType}}",
|
||||
"funding-chart": "Funding Chart",
|
||||
"health-contributions": "Health Contributions",
|
||||
"init-health-contribution": "Init Health Contribution",
|
||||
"init-health-contributions": "Init Health Contributions",
|
||||
"liabilities": "Liabilities",
|
||||
"lifetime-volume": "Lifetime Trade Volume",
|
||||
"maint-health-contribution": "Maint Health Contribution",
|
||||
"maint-health-contributions": "Maint Health Contributions",
|
||||
"no-data": "No data to display",
|
||||
"no-pnl-history": "No PnL History",
|
||||
"pnl-chart": "PnL Chart",
|
||||
"pnl-history": "PnL History",
|
||||
"tooltip-collateral-value": "The amount of capital this token gives you to use for trades and loans.",
|
||||
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
|
||||
"tooltip-init-health": "The contribution an asset gives to your initial account health. Initial health affects your ability to open new positions and withdraw collateral from your account. The sum of these values is equal to your account's free collateral.",
|
||||
"tooltip-leverage": "Total assets value divided by account equity value",
|
||||
"tooltip-maint-health": "The contribution an asset gives to your maintenance account health. If your maintenance health reaches 0 your account will be liquidated.",
|
||||
"tooltip-pnl": "The amount your account has profited or lost",
|
||||
"tooltip-total-collateral": "Total value of collateral for trading and borrowing (including unsettled PnL)",
|
||||
"tooltip-total-funding": "The sum of perp position funding earned and paid",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"mango": "Mango",
|
||||
"mango-stats": "Mango統計",
|
||||
"market": "市場",
|
||||
"markets": "Markets",
|
||||
"max": "最多",
|
||||
"max-borrow": "最多借貸",
|
||||
"more": "更多",
|
||||
|
|
|
@ -413,3 +413,21 @@ export type TickerData = {
|
|||
target_volume: string
|
||||
ticker_id: string
|
||||
}
|
||||
|
||||
export interface HealthContribution {
|
||||
asset: string
|
||||
contribution: number
|
||||
contributionDetails?: ContributionDetails
|
||||
hasPerp?: boolean
|
||||
isAsset: boolean
|
||||
}
|
||||
|
||||
export interface PerpMarketContribution {
|
||||
market: string
|
||||
contributionUi: number
|
||||
}
|
||||
|
||||
export interface ContributionDetails {
|
||||
perpMarketContributions: PerpMarketContribution[]
|
||||
spotUi: number
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue