Merge branch 'main' into update-translations

This commit is contained in:
tjshipe 2022-06-16 17:52:09 -04:00 committed by GitHub
commit 4e88ef68f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 809 additions and 730 deletions

View File

@ -7,11 +7,7 @@ import {
ZERO_I80F48,
} from '@blockworks-foundation/mango-client'
import { useCallback, useState } from 'react'
import {
ExclamationIcon,
ExternalLinkIcon,
HeartIcon,
} from '@heroicons/react/solid'
import { ExclamationIcon, ExternalLinkIcon } from '@heroicons/react/solid'
import { BellIcon } from '@heroicons/react/outline'
import useMangoStore, { MNGO_INDEX } from '../stores/useMangoStore'
import { abbreviateAddress, formatUsdValue, usdFormatter } from '../utils'
@ -31,6 +27,7 @@ import Loading from './Loading'
import CreateAlertModal from './CreateAlertModal'
import { useWallet } from '@solana/wallet-adapter-react'
import { useRouter } from 'next/router'
import HealthHeart from './HealthHeart'
const I80F48_100 = I80F48.fromString('100')
@ -131,11 +128,6 @@ export default function AccountInfo() {
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
: I80F48_100
const initHealthRatio =
mangoAccount && mangoGroup && mangoCache
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Init')
: I80F48_100
const maintHealth =
mangoAccount && mangoGroup && mangoCache
? mangoAccount.getHealth(mangoGroup, mangoCache, 'Maint')
@ -193,7 +185,7 @@ export default function AccountInfo() {
) : null}
<div>
{mangoAccount ? (
<div className="-mt-2 flex justify-center text-xs">
<div className="-mt-2 mb-2 flex justify-center text-xs">
<a
className="flex items-center text-th-fgd-4 hover:text-th-primary"
href={`https://explorer.solana.com/address/${mangoAccount?.publicKey}`}
@ -208,12 +200,41 @@ export default function AccountInfo() {
<div>
<div className="flex justify-between pb-2">
<div className="font-normal leading-4 text-th-fgd-3">
{t('equity')}
{t('value')}
</div>
<div className="text-th-fgd-1">
{initialLoad ? <DataLoader /> : formatUsdValue(+equity)}
</div>
</div>
<div className="flex justify-between pb-2">
<Tooltip
content={
<div>
{t('tooltip-account-liquidated')}{' '}
<a
href="https://docs.mango.markets/mango/health-overview"
target="_blank"
rel="noopener noreferrer"
>
{t('learn-more')}
</a>
</div>
}
>
<div className="default-transition cursor-help border-b border-dashed border-th-fgd-3 border-opacity-20 font-normal leading-4 text-th-fgd-3 hover:border-th-bkg-2">
{t('health')}
</div>
</Tooltip>
<div className="flex items-center space-x-2">
<HealthHeart size={24} health={Number(maintHealthRatio)} />
<div className="text-th-fgd-1">
{maintHealthRatio.gt(I80F48_100)
? '>100'
: maintHealthRatio.toFixed(2)}
%
</div>
</div>
</div>
<div className="flex justify-between pb-2">
<div className="font-normal leading-4 text-th-fgd-3">
{t('leverage')}
@ -326,54 +347,6 @@ export default function AccountInfo() {
</div>
</div>
</div>
<div className="my-2 flex items-center rounded border border-th-bkg-4 p-2.5 sm:my-1">
<div className="flex items-center pr-2">
<HeartIcon
className="mr-1.5 h-5 w-5 text-th-primary"
aria-hidden="true"
/>
<span>
<Tooltip
content={
<div>
{t('tooltip-account-liquidated')}{' '}
<a
href="https://docs.mango.markets/mango/health-overview"
target="_blank"
rel="noopener noreferrer"
>
{t('learn-more')}
</a>
</div>
}
>
<div className="default-transition cursor-help border-b border-dashed border-th-fgd-3 border-opacity-20 font-normal leading-4 text-th-fgd-3 hover:border-th-bkg-2">
{t('health')}
</div>
</Tooltip>
</span>
</div>
<div className="flex h-1.5 flex-grow rounded bg-th-bkg-4">
<div
style={{
width: `${maintHealthRatio}%`,
}}
className={`flex rounded ${
maintHealthRatio.toNumber() > 30
? 'bg-th-green'
: initHealthRatio.toNumber() > 0
? 'bg-th-orange'
: 'bg-th-red'
}`}
></div>
</div>
<div className="pl-2 text-right">
{maintHealthRatio.gt(I80F48_100)
? '>100'
: maintHealthRatio.toFixed(2)}
%
</div>
</div>
{mangoAccount && mangoAccount.beingLiquidated ? (
<div className="flex items-center justify-center pt-0.5 text-xs">
<ExclamationIcon className="mr-1.5 h-5 w-5 flex-shrink-0 text-th-red" />

View File

@ -54,6 +54,11 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
const openOrders = useMangoStore((s) => s.selectedMangoAccount.openOrders)
const setMangoStore = useMangoStore((s) => s.set)
const activeAlerts = useMangoStore((s) => s.alerts.activeAlerts)
const spotBalances = useMangoStore((s) => s.selectedMangoAccount.spotBalances)
const unsettledBalances = spotBalances.filter(
(bal) => bal.unsettled && bal.unsettled > 0
)
const fetchTotalAccountSOL = useCallback(async () => {
if (!mangoAccount) {
@ -162,7 +167,10 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
}
const isDisabled =
(openOrders && openOrders.length > 0) || hasBorrows || hasOpenPositions
(openOrders && openOrders.length > 0) ||
hasBorrows ||
hasOpenPositions ||
!!unsettledBalances.length
return (
<Modal onClose={onClose} isOpen={isOpen && mangoAccount !== undefined}>
@ -240,6 +248,12 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
{t('close-account:close-open-orders')}
</div>
) : null}
{unsettledBalances.length ? (
<div className="flex items-center text-th-fgd-2">
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-red" />
{t('close-account:settle-balances')}
</div>
) : null}
</div>
</>
) : null}

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import sumBy from 'lodash/sumBy'
import useInterval from '../hooks/useInterval'
import { SECONDS } from '../stores/useMangoStore'
import { CLUSTER, SECONDS } from '../stores/useMangoStore'
import { useTranslation } from 'next-i18next'
import { ExclamationIcon } from '@heroicons/react/outline'
import { Connection } from '@solana/web3.js'
@ -43,7 +43,7 @@ const GlobalNotification = () => {
getRecentPerformance(setShow, setTps)
}, 45 * SECONDS)
if (show) {
if (show && CLUSTER == 'mainnet') {
return (
<div className="flex items-center bg-th-bkg-4 text-th-fgd-2">
<div className="flex w-full items-center justify-center p-1">

View File

@ -0,0 +1,46 @@
const HealthHeart = ({ health, size }: { health: number; size: number }) => {
const styles = {
height: `${size}px`,
width: `${size}px`,
}
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={
health > 15 && health < 50
? 'text-th-orange'
: health > 50
? 'text-th-green'
: 'text-th-red'
}
style={styles}
viewBox="0 0 20 20"
fill="currentColor"
>
<g transform-origin="center">
<path
fillRule="evenodd"
d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"
clipRule="evenodd"
/>
<animateTransform
attributeName="transform"
type="scale"
keyTimes="0;0.5;1"
values="1;1.1;1"
dur={health > 15 && health < 50 ? '1s' : health > 50 ? '2s' : '0.33s'}
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0.8;1;0.8"
dur={health > 15 && health < 50 ? '1s' : health > 50 ? '2s' : '0.33s'}
repeatCount="indefinite"
/>
</g>
</svg>
)
}
export default HealthHeart

View File

@ -1,20 +1,14 @@
import { useEffect, useMemo, useState } from 'react'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { ExclamationIcon } from '@heroicons/react/solid'
import { useTranslation } from 'next-i18next'
import useMangoStore, { PerpPosition } from '../../stores/useMangoStore'
import { formatUsdValue } from '../../utils'
import useMangoStore from '../../stores/useMangoStore'
import BalancesTable from '../BalancesTable'
import Switch from '../Switch'
import useLocalStorageState from '../../hooks/useLocalStorageState'
import PerformanceChart from './PerformanceChart'
import PositionsTable from '../PerpPositionsTable'
import LongShortChart from './LongShortChart'
import Tooltip from 'components/Tooltip'
import { CalendarIcon, InformationCircleIcon } from '@heroicons/react/outline'
import { ZERO_BN } from '@blockworks-foundation/mango-client'
import AccountOverviewStats from './AccountOverviewStats'
dayjs.extend(utc)
@ -46,10 +40,6 @@ export const fetchHourlyPerformanceStats = async (
export default function AccountOverview() {
const { t } = useTranslation('common')
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const spotBalances = useMangoStore((s) => s.selectedMangoAccount.spotBalances)
const perpPositions = useMangoStore(
(s) => s.selectedMangoAccount.perpPositions
)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
const [showZeroBalances, setShowZeroBalances] = useLocalStorageState(
@ -57,7 +47,6 @@ export default function AccountOverview() {
true
)
const [pnl, setPnl] = useState(0)
const [hourlyPerformanceStats, setHourlyPerformanceStats] = useState([])
useEffect(() => {
@ -67,8 +56,6 @@ export default function AccountOverview() {
return
}
const stats = await fetchHourlyPerformanceStats(pubKey, 30)
setPnl(stats?.length ? stats?.[0]?.['pnl'] : 0)
setHourlyPerformanceStats(stats)
}
if (pubKey) {
@ -76,218 +63,21 @@ export default function AccountOverview() {
}
}, [mangoAccount?.publicKey])
const maintHealthRatio = useMemo(() => {
return mangoAccount && mangoGroup && mangoCache
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
: 100
}, [mangoAccount, mangoGroup, mangoCache])
const initHealthRatio = useMemo(() => {
return mangoAccount && mangoGroup && mangoCache
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Init')
: 100
}, [mangoAccount, mangoGroup, mangoCache])
const mangoAccountValue = useMemo(() => {
return mangoAccount && mangoGroup && mangoCache
? +mangoAccount.computeValue(mangoGroup, mangoCache)
: 0
}, [mangoAccount, mangoGroup, mangoCache])
const { longData, shortData, longExposure, shortExposure } = useMemo(() => {
if (!spotBalances || !perpPositions) {
return {}
}
const DUST_THRESHOLD = 0.05
const netUnsettledPositionsValue = perpPositions.reduce(
(a, c) => a + (c?.unsettledPnl ?? 0),
0
)
const longData: any = []
const shortData: any = []
for (const { net, symbol, value } of spotBalances) {
let amount = Number(net)
let totValue = Number(value)
if (symbol === 'USDC') {
amount += netUnsettledPositionsValue
totValue += netUnsettledPositionsValue
}
if (totValue > DUST_THRESHOLD) {
longData.push({
asset: symbol,
amount: amount,
symbol: symbol,
value: totValue,
})
}
if (-totValue > DUST_THRESHOLD) {
shortData.push({
asset: symbol,
amount: Math.abs(amount),
symbol: symbol,
value: Math.abs(totValue),
})
}
}
for (const {
marketConfig,
basePosition,
notionalSize,
perpAccount,
} of perpPositions.filter((p) => !!p) as PerpPosition[]) {
if (notionalSize < DUST_THRESHOLD) continue
if (perpAccount.basePosition.gt(ZERO_BN)) {
longData.push({
asset: marketConfig.name,
amount: basePosition,
symbol: marketConfig.baseSymbol,
value: notionalSize,
})
} else {
shortData.push({
asset: marketConfig.name,
amount: Math.abs(basePosition),
symbol: marketConfig.baseSymbol,
value: notionalSize,
})
}
}
const longExposure = longData.reduce((a, c) => a + c.value, 0)
const shortExposure = shortData.reduce((a, c) => a + c.value, 0)
const dif = longExposure - shortExposure
if (dif > 0) {
shortData.push({ symbol: 'spacer', value: dif })
}
return { longData, shortData, longExposure, shortExposure }
}, [spotBalances, perpPositions])
return mangoAccount ? (
<>
<div className="grid grid-cols-12 md:gap-x-6">
<div className="col-span-12 border-y border-th-bkg-4 p-3 sm:p-4 md:col-span-6 xl:col-span-3">
<div className="pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
{t('account-value')}
</div>
<div className="text-xl font-bold text-th-fgd-1 sm:text-3xl">
{formatUsdValue(mangoAccountValue)}
</div>
</div>
<div className="col-span-12 border-b border-th-bkg-4 p-3 sm:p-4 md:col-span-6 md:border-t xl:col-span-3">
<div className="flex items-center justify-between">
<div className="flex w-full items-center justify-between pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
{t('pnl')}{' '}
{hourlyPerformanceStats?.length ? (
<div className="flex items-center text-xs text-th-fgd-4">
<CalendarIcon className="mr-1 h-4 w-4" />
{dayjs(hourlyPerformanceStats[0]['time']).format(
'MMM D, h:mma'
)}
</div>
) : null}
</div>
</div>
<div className="text-xl font-bold text-th-fgd-1 sm:text-3xl">
{formatUsdValue(pnl)}
</div>
</div>
<div className="col-span-12 border-th-bkg-4 px-3 pt-3 sm:px-4 sm:pt-4 md:col-span-6 xl:col-span-3 xl:border-t">
<div className="pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
{t('health-ratio')}
</div>
<div className={`text-xl font-bold text-th-fgd-1 sm:text-3xl`}>
{maintHealthRatio < 100 ? maintHealthRatio.toFixed(2) : '>100'}%
</div>
{mangoAccount.beingLiquidated ? (
<div className="flex items-center pt-0.5 text-xs sm:pt-2 sm:text-sm">
<ExclamationIcon className="mr-1.5 h-5 w-5 flex-shrink-0 text-th-red sm:h-7 sm:w-7" />
<span className="text-th-red">{t('being-liquidated')}</span>
</div>
) : null}
<div className="-mx-3 mt-3 flex h-1 rounded bg-th-bkg-3 sm:-mx-4 sm:mt-4">
<div
style={{
width: `${maintHealthRatio}%`,
}}
className={`flex rounded ${
maintHealthRatio > 30
? 'bg-th-green'
: initHealthRatio > 0
? 'bg-th-orange'
: 'bg-th-red'
}`}
></div>
</div>
</div>
<div className="col-span-12 border-b border-th-bkg-4 p-3 sm:p-4 md:col-span-6 xl:col-span-3 xl:border-t">
<div className="pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
{t('leverage')}
</div>
{mangoGroup && mangoCache ? (
<div className="text-xl font-bold text-th-fgd-1 sm:text-3xl">
{mangoAccount.getLeverage(mangoGroup, mangoCache).toFixed(2)}x
</div>
) : null}
</div>
<div className="relative col-span-12 mb-4 mt-8 h-[460px] rounded-md border border-th-bkg-4 p-4 sm:h-[400px] md:p-6 lg:col-span-6 lg:mb-0">
<PerformanceChart
<div className="relative col-span-12 h-[690px] lg:h-[538px] xl:h-[410px]">
<AccountOverviewStats
hourlyPerformanceStats={hourlyPerformanceStats}
accountValue={mangoAccountValue}
chartToShow="Value"
/>
</div>
<div className="relative col-span-12 h-[460px] rounded-md border border-th-bkg-4 p-4 sm:h-[400px] md:p-6 lg:col-span-6 lg:mt-8">
<PerformanceChart
hourlyPerformanceStats={hourlyPerformanceStats}
accountValue={mangoAccountValue}
chartToShow="PnL"
/>
</div>
</div>
<div className="my-8">
<h2 className="mb-4">{t('portfolio-balance')}</h2>
<div className="grid grid-flow-col grid-cols-1 grid-rows-2 md:grid-cols-2 md:grid-rows-1 md:gap-6">
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
<div className="flex items-center justify-between">
<div>
<Tooltip content={t('total-long-tooltip')}>
<div className="flex items-center space-x-1.5 pb-0.5">
<div className="text-th-fgd-3">{t('total-long')}</div>
<InformationCircleIcon className="h-5 w-5 text-th-fgd-3" />
</div>
</Tooltip>
{mangoGroup && mangoCache ? (
<div className="text-xl font-bold text-th-fgd-1 md:text-3xl">
{formatUsdValue(+longExposure)}
</div>
) : null}
</div>
<LongShortChart type="long" chartData={longData} />
</div>
</div>
<div className="border-b border-t border-th-bkg-4 p-3 sm:p-4">
<div className="flex items-center justify-between">
<div>
<Tooltip content={t('total-short-tooltip')}>
<div className="flex items-center space-x-1.5 pb-0.5">
<div className="text-th-fgd-3">{t('total-short')}</div>
<InformationCircleIcon className="h-5 w-5 text-th-fgd-3" />
</div>
</Tooltip>
{mangoGroup && mangoCache ? (
<div className="text-xl font-bold text-th-fgd-1 md:text-3xl">
{formatUsdValue(+shortExposure)}
</div>
) : null}
</div>
<LongShortChart type="short" chartData={shortData} />
</div>
</div>
</div>
</div>
<div className="pb-8">
<h2 className="mb-4">{t('perp-positions')}</h2>

View File

@ -0,0 +1,578 @@
import { useState, useEffect, useMemo } from 'react'
import { useTheme } from 'next-themes'
import cloneDeep from 'lodash/cloneDeep'
import dayjs from 'dayjs'
import {
AreaChart,
Area,
XAxis,
YAxis,
Tooltip as ChartTooltip,
} from 'recharts'
import { InformationCircleIcon } from '@heroicons/react/outline'
import useDimensions from 'react-cool-dimensions'
import { useTranslation } from 'next-i18next'
import { ZERO_BN } from '@blockworks-foundation/mango-client'
import ButtonGroup from '../ButtonGroup'
import { formatUsdValue } from '../../utils'
import { numberCompacter } from '../SwapTokenInfo'
import Checkbox from '../Checkbox'
import Tooltip from '../Tooltip'
import useMangoStore, { PerpPosition } from 'stores/useMangoStore'
import LongShortChart from './LongShortChart'
import HealthHeart from 'components/HealthHeart'
type AccountOverviewStats = {
hourlyPerformanceStats: any[]
performanceRange: '24hr' | '7d' | '30d' | '3m'
}
const defaultData = [
{ account_equity: 0, pnl: 0, time: '2022-01-01T00:00:00.000Z' },
{ account_equity: 0, pnl: 0, time: '2023-01-01T00:00:00.000Z' },
]
const performanceRangePresets = [
{ label: '24h', value: 1 },
{ label: '7d', value: 7 },
{ label: '30d', value: 30 },
{ label: '3m', value: 90 },
]
const performanceRangePresetLabels = performanceRangePresets.map((x) => x.label)
const AccountOverviewStats = ({ hourlyPerformanceStats, accountValue }) => {
const { theme } = useTheme()
const { t } = useTranslation('common')
const { observe, width, height } = useDimensions()
const [chartToShow, setChartToShow] = useState<string>('Value')
const [chartData, setChartData] = useState<any[]>([])
const [mouseData, setMouseData] = useState<string | null>(null)
const [performanceRange, setPerformanceRange] = useState('30d')
const [showSpotPnl, setShowSpotPnl] = useState(true)
const [showPerpPnl, setShowPerpPnl] = useState(true)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
const spotBalances = useMangoStore((s) => s.selectedMangoAccount.spotBalances)
const perpPositions = useMangoStore(
(s) => s.selectedMangoAccount.perpPositions
)
const maintHealthRatio = useMemo(() => {
return mangoAccount && mangoGroup && mangoCache
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
: 100
}, [mangoAccount, mangoGroup, mangoCache])
const { longData, shortData, longExposure, shortExposure } = useMemo(() => {
const longData: any = []
const shortData: any = []
if (!spotBalances || !perpPositions) {
longData.push({ symbol: 'spacer', value: 1 })
shortData.push({ symbol: 'spacer', value: 1 })
// return {}
}
const DUST_THRESHOLD = 0.05
const netUnsettledPositionsValue = perpPositions.reduce(
(a, c) => a + (c?.unsettledPnl ?? 0),
0
)
for (const { net, symbol, value } of spotBalances) {
let amount = Number(net)
let totValue = Number(value)
if (symbol === 'USDC') {
amount += netUnsettledPositionsValue
totValue += netUnsettledPositionsValue
}
if (totValue > DUST_THRESHOLD) {
longData.push({
asset: symbol,
amount: amount,
symbol: symbol,
value: totValue,
})
}
if (-totValue > DUST_THRESHOLD) {
shortData.push({
asset: symbol,
amount: Math.abs(amount),
symbol: symbol,
value: Math.abs(totValue),
})
}
}
for (const {
marketConfig,
basePosition,
notionalSize,
perpAccount,
} of perpPositions.filter((p) => !!p) as PerpPosition[]) {
if (notionalSize < DUST_THRESHOLD) continue
if (perpAccount.basePosition.gt(ZERO_BN)) {
longData.push({
asset: marketConfig.name,
amount: basePosition,
symbol: marketConfig.baseSymbol,
value: notionalSize,
})
} else {
shortData.push({
asset: marketConfig.name,
amount: Math.abs(basePosition),
symbol: marketConfig.baseSymbol,
value: notionalSize,
})
}
}
const longExposure = longData.reduce((a, c) => a + c.value, 0)
const shortExposure = shortData.reduce((a, c) => a + c.value, 0)
if (shortExposure === 0) {
shortData.push({ symbol: 'spacer', value: 1 })
}
if (longExposure === 0) {
longData.push({ symbol: 'spacer', value: 1 })
}
const dif = longExposure - shortExposure
if (dif > 0) {
shortData.push({ symbol: 'spacer', value: dif })
}
return { longData, shortData, longExposure, shortExposure }
}, [spotBalances, perpPositions])
useEffect(() => {
if (hourlyPerformanceStats.length > 0) {
if (performanceRange === '3m') {
setChartData(hourlyPerformanceStats.slice().reverse())
}
if (performanceRange === '30d') {
const start = new Date(
// @ts-ignore
dayjs().utc().hour(0).minute(0).subtract(29, 'day')
).getTime()
const chartData = cloneDeep(hourlyPerformanceStats).filter(
(d) => new Date(d.time).getTime() > start
)
const pnlStart = chartData[chartData.length - 1].pnl
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
for (let i = 0; i < chartData.length; i++) {
if (i === chartData.length - 1) {
chartData[i].pnl = 0
chartData[i].perp_pnl = 0
} else {
chartData[i].pnl = chartData[i].pnl - pnlStart
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
}
}
setChartData(chartData.reverse())
}
if (performanceRange === '7d') {
const start = new Date(
// @ts-ignore
dayjs().utc().hour(0).minute(0).subtract(7, 'day')
).getTime()
const chartData = cloneDeep(hourlyPerformanceStats).filter(
(d) => new Date(d.time).getTime() > start
)
const pnlStart = chartData[chartData.length - 1].pnl
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
for (let i = 0; i < chartData.length; i++) {
if (i === chartData.length - 1) {
chartData[i].pnl = 0
chartData[i].perp_pnl = 0
} else {
chartData[i].pnl = chartData[i].pnl - pnlStart
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
}
}
setChartData(chartData.reverse())
}
if (performanceRange === '24h') {
const start = new Date(
// @ts-ignore
dayjs().utc().hour(0).minute(0).subtract(1, 'day')
).getTime()
const chartData = cloneDeep(hourlyPerformanceStats).filter(
(d) => new Date(d.time).getTime() > start
)
const pnlStart = chartData[chartData.length - 1].pnl
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
for (let i = 0; i < chartData.length; i++) {
if (i === chartData.length - 1) {
chartData[i].pnl = 0
chartData[i].perp_pnl = 0
} else {
chartData[i].pnl = chartData[i].pnl - pnlStart
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
}
}
setChartData(chartData.reverse())
}
} else {
setChartData([])
}
}, [hourlyPerformanceStats, performanceRange])
useEffect(() => {
if (chartData.length > 0) {
for (const stat of chartData) {
stat.spot_pnl = stat.pnl - stat.perp_pnl
}
}
}, [chartData])
const handleMouseMove = (coords) => {
if (coords.activePayload) {
setMouseData(coords.activePayload[0].payload)
}
}
const handleMouseLeave = () => {
setMouseData(null)
}
const renderPnlChartTitle = () => {
if (showPerpPnl && showSpotPnl) {
return t('pnl')
}
if (!showSpotPnl) {
return `${t('perp')} ${t('pnl')}`
}
if (!showPerpPnl) {
return `${t('spot')} ${t('pnl')}`
}
}
const formatDateAxis = (date) => {
if (['3m', '30d'].includes(performanceRange)) {
return dayjs(date).format('D MMM')
} else if (performanceRange === '7d') {
return dayjs(date).format('ddd, h:mma')
} else {
return dayjs(date).format('h:mma')
}
}
const pnlChartDataKey = () => {
if (!showPerpPnl && showSpotPnl) {
return 'spot_pnl'
} else if (!showSpotPnl && showPerpPnl) {
return 'perp_pnl'
} else {
return 'pnl'
}
}
const pnlChartColor =
chartToShow === 'PnL' &&
chartData.length > 0 &&
chartData[chartData.length - 1][pnlChartDataKey()] > 0
? theme === 'Mango'
? '#AFD803'
: '#5EBF4D'
: theme === 'Mango'
? '#F84638'
: '#CC2929'
return (
<div className="grid grid-cols-12 lg:gap-6">
<div className="order-2 col-span-12 lg:order-1 lg:col-span-4">
<div className="p-4 sm:p-5 lg:border-t lg:border-th-bkg-4">
<div className="flex items-center pb-1.5">
<div className="text-sm text-th-fgd-3">
{chartToShow === 'Value'
? t('account-value')
: renderPnlChartTitle()}{' '}
</div>
<Tooltip content={t('delayed-info')}>
<InformationCircleIcon className="ml-1.5 h-5 w-5 flex-shrink-0 cursor-help text-th-fgd-3" />
</Tooltip>
</div>
{mouseData ? (
<>
<div className="pb-1 text-2xl font-bold text-th-fgd-1 sm:text-4xl">
{formatUsdValue(
mouseData[
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
]
)}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{dayjs(mouseData['time']).format('ddd MMM D YYYY, h:mma')}
</div>
</>
) : chartData.length === 0 ? (
<>
<div className="pb-1 text-3xl font-bold text-th-fgd-1 sm:text-4xl">
{chartToShow === 'PnL' ? '--' : formatUsdValue(accountValue)}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{dayjs().format('ddd MMM D YYYY, h:mma')}
</div>
</>
) : chartData.length > 0 ? (
<>
<div className="pb-1 text-3xl font-bold text-th-fgd-1 sm:text-4xl">
{chartToShow === 'PnL'
? formatUsdValue(
chartData[chartData.length - 1][pnlChartDataKey()]
)
: formatUsdValue(accountValue)}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{chartToShow === 'PnL'
? dayjs(chartData[chartData.length - 1]['time']).format(
'ddd MMM D YYYY, h:mma'
)
: dayjs().format('ddd MMM D YYYY, h:mma')}
</div>
</>
) : (
<>
<div className="mt-1 h-8 w-48 animate-pulse rounded bg-th-bkg-3" />
<div className="mt-1 h-4 w-24 animate-pulse rounded bg-th-bkg-3" />
</>
)}
</div>
<div className="flex divide-x divide-th-bkg-4 border-y border-th-bkg-4 p-4 sm:p-5 lg:flex-col lg:divide-x-0 lg:divide-y lg:p-0 xl:flex-row xl:divide-y-0 xl:divide-x xl:p-5">
<div className="flex w-1/2 items-center space-x-2 lg:w-full lg:p-5 xl:w-1/2 xl:p-0">
<HealthHeart size={40} health={Number(maintHealthRatio)} />
<div>
<Tooltip
content={
<div>
{t('tooltip-account-liquidated')}{' '}
<a
href="https://docs.mango.markets/mango/health-overview"
target="_blank"
rel="noopener noreferrer"
>
{t('learn-more')}
</a>
</div>
}
>
<div className="flex items-center space-x-1.5 pb-0.5">
<div className="text-th-fgd-3">{t('health')}</div>
<InformationCircleIcon className="h-5 w-5 flex-shrink-0 cursor-help text-th-fgd-3" />
</div>
</Tooltip>
<div className={`text-2xl font-bold text-th-fgd-1`}>
{maintHealthRatio < 100 ? maintHealthRatio.toFixed(2) : '>100'}%
</div>
</div>
</div>
<div className="w-1/2 px-4 pl-4 sm:px-5 lg:w-full lg:p-5 xl:w-1/2 xl:p-0 xl:pl-4">
<div className="pb-0.5 text-th-fgd-3">{t('leverage')}</div>
{mangoGroup && mangoCache ? (
<div className={`text-2xl font-bold text-th-fgd-1`}>
{mangoAccount?.getLeverage(mangoGroup, mangoCache).toFixed(2)}x
</div>
) : null}
</div>
</div>
<div className="flex divide-x divide-th-bkg-4 border-b border-th-bkg-4 p-4 sm:p-5 lg:flex-col lg:divide-x-0 lg:divide-y lg:p-0 xl:flex-row xl:divide-y-0 xl:divide-x xl:p-5">
<div className="flex w-1/2 flex-col md:flex-row md:items-center md:space-x-3 lg:w-full lg:p-5 xl:w-1/2 xl:flex-col xl:items-start xl:space-x-0 xl:p-0">
<LongShortChart chartData={longData} />
<div className="mt-3 md:mt-0 xl:mt-3">
<Tooltip content={t('total-long-tooltip')}>
<div className="flex items-center space-x-1.5 pb-0.5">
<div className="text-th-fgd-3">{t('long-exposure')}</div>
<InformationCircleIcon className="h-5 w-5 flex-shrink-0 cursor-help text-th-fgd-3" />
</div>
</Tooltip>
{mangoGroup && mangoCache ? (
<div className="text-2xl font-bold text-th-fgd-1">
{formatUsdValue(+longExposure)}
</div>
) : null}
</div>
</div>
<div className="flex w-1/2 flex-col pl-4 sm:px-5 md:flex-row md:items-center md:space-x-3 lg:w-full lg:p-5 xl:w-1/2 xl:flex-col xl:items-start xl:space-x-0 xl:p-0 xl:pl-4">
<LongShortChart chartData={shortData} />
<div className="mt-3 md:mt-0 xl:mt-3">
<Tooltip content={t('total-short-tooltip')}>
<div className="flex items-center space-x-1.5 pb-0.5">
<div className="text-th-fgd-3">{t('short-exposure')}</div>
<InformationCircleIcon className="h-5 w-5 flex-shrink-0 cursor-help text-th-fgd-3" />
</div>
</Tooltip>
{mangoGroup && mangoCache ? (
<div className="text-2xl font-bold text-th-fgd-1">
{formatUsdValue(+shortExposure)}
</div>
) : null}
</div>
</div>
</div>
</div>
<div className="order-1 col-span-12 border-y border-th-bkg-4 p-4 lg:order-2 lg:col-span-8 xl:p-6">
<div className="mb-4 flex justify-between space-x-2 sm:mb-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:space-x-3">
<div className="mb-3 w-28 sm:mb-0">
<ButtonGroup
activeValue={chartToShow}
className="h-8"
onChange={(c) => setChartToShow(c)}
values={['Value', 'PnL']}
/>
</div>
{chartToShow === 'PnL' && chartData.length ? (
<div className="flex space-x-3">
<Checkbox
checked={showSpotPnl}
disabled={!showPerpPnl}
onChange={(e) => setShowSpotPnl(e.target.checked)}
>
{t('include-spot')}
</Checkbox>
<Checkbox
checked={showPerpPnl}
disabled={!showSpotPnl}
onChange={(e) => setShowPerpPnl(e.target.checked)}
>
{t('include-perp')}
</Checkbox>
</div>
) : null}
</div>
<div className="w-40">
<ButtonGroup
activeValue={performanceRange}
className="h-8"
onChange={(p) => setPerformanceRange(p)}
values={performanceRangePresetLabels}
/>
</div>
</div>
{chartData.length > 0 ? (
<div className="h-48 md:h-64 lg:h-[410px] xl:h-[270px]" ref={observe}>
<AreaChart
width={width}
height={height}
data={chartData?.length ? chartData : defaultData}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<ChartTooltip
cursor={{
strokeOpacity: 0,
}}
content={<></>}
/>
<defs>
<linearGradient
id="defaultGradientArea"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop offset="0%" stopColor="#ffba24" stopOpacity={0.9} />
<stop offset="80%" stopColor="#ffba24" stopOpacity={0} />
</linearGradient>
<linearGradient
id="greenGradientArea"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
stopOpacity={0.9}
/>
<stop
offset="80%"
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
stopOpacity={0}
/>
</linearGradient>
<linearGradient
id="redGradientArea"
x1="0"
y1="1"
x2="0"
y2="0"
>
<stop
offset="0%"
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
stopOpacity={0.9}
/>
<stop
offset="80%"
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
stopOpacity={0}
/>
</linearGradient>
</defs>
<Area
isAnimationActive={true}
type="monotone"
dataKey={
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
}
stroke={chartToShow === 'PnL' ? pnlChartColor : '#ffba24'}
fill={
chartToShow === 'PnL'
? chartData[chartData.length - 1][pnlChartDataKey()] > 0
? 'url(#greenGradientArea)'
: 'url(#redGradientArea)'
: 'url(#defaultGradientArea)'
}
fillOpacity={0.3}
/>
<YAxis
dataKey={
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
}
type="number"
domain={['dataMin', 'dataMax']}
axisLine={false}
dx={-10}
tick={{
fill:
theme === 'Light'
? 'rgba(0,0,0,0.4)'
: 'rgba(255,255,255,0.35)',
fontSize: 10,
}}
tickLine={false}
tickFormatter={(v) => numberCompacter.format(v)}
/>
<XAxis
dataKey="time"
axisLine={false}
dy={10}
minTickGap={20}
tick={{
fill:
theme === 'Light'
? 'rgba(0,0,0,0.4)'
: 'rgba(255,255,255,0.35)',
fontSize: 10,
}}
tickLine={false}
tickFormatter={(v) => formatDateAxis(v)}
/>
</AreaChart>
</div>
) : (
<div className="flex h-48 w-full items-center justify-center rounded-md bg-th-bkg-3 md:h-64 lg:h-[410px] xl:h-[270px]">
<p className="mb-0">{t('no-chart')}</p>
</div>
)}
</div>
</div>
)
}
export default AccountOverviewStats

View File

@ -2,7 +2,6 @@ import { PieChart, Pie, Cell, Tooltip } from 'recharts'
import { formatUsdValue, tokenPrecision } from 'utils'
import * as MonoIcons from '../icons'
import { QuestionMarkCircleIcon } from '@heroicons/react/outline'
import { useTranslation } from 'next-i18next'
export const CHART_COLORS = {
All: '#ff7c43',
@ -25,15 +24,7 @@ export const CHART_COLORS = {
USDT: '#50AF95',
}
const LongShortChart = ({
type,
chartData,
}: {
type: string
chartData: any[]
}) => {
const { t } = useTranslation('common')
const LongShortChart = ({ chartData }: { chartData: any[] }) => {
const CustomToolTip = () => {
const renderIcon = (symbol) => {
const iconName = `${symbol.slice(0, 1)}${symbol
@ -47,7 +38,10 @@ const LongShortChart = ({
)
}
return chartData.length ? (
const hideTooltip =
chartData.length === 1 && chartData[0].symbol === 'spacer'
return chartData.length && !hideTooltip ? (
<div className="space-y-1.5 rounded-md bg-th-bkg-2 p-3 pb-2 md:bg-th-bkg-1">
{chartData
.filter((d) => d.symbol !== 'spacer')
@ -89,16 +83,15 @@ const LongShortChart = ({
}
return chartData.length ? (
<div className="relative h-20 w-20">
<PieChart width={80} height={80}>
<PieChart width={48} height={48}>
<Pie
cursor="pointer"
data={chartData}
dataKey="value"
cx="50%"
cy="50%"
outerRadius={40}
innerRadius={28}
outerRadius={24}
innerRadius={16}
minAngle={2}
startAngle={90}
endAngle={450}
@ -113,12 +106,12 @@ const LongShortChart = ({
/>
))}
</Pie>
<Tooltip content={<CustomToolTip />} position={{ x: -220, y: 0 }} />
<Tooltip
content={<CustomToolTip />}
position={{ x: 64, y: 0 }}
wrapperStyle={{ zIndex: 10 }}
/>
</PieChart>
<div className="absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 transform text-xs font-bold uppercase text-th-fgd-3">
{type === 'long' ? t('long') : t('short')}
</div>
</div>
) : null
}

View File

@ -1,389 +0,0 @@
import { useState, useEffect } from 'react'
import { useTheme } from 'next-themes'
import cloneDeep from 'lodash/cloneDeep'
import dayjs from 'dayjs'
import {
AreaChart,
Area,
XAxis,
YAxis,
Tooltip as ChartTooltip,
} from 'recharts'
import { InformationCircleIcon } from '@heroicons/react/outline'
import useDimensions from 'react-cool-dimensions'
import { useTranslation } from 'next-i18next'
import ButtonGroup from '../ButtonGroup'
import { formatUsdValue } from '../../utils'
import { numberCompacter } from '../SwapTokenInfo'
import Checkbox from '../Checkbox'
import Tooltip from '../Tooltip'
type PerformanceChart = {
hourlyPerformanceStats: any[]
performanceRange: '24hr' | '7d' | '30d' | '3m'
}
const defaultData = [
{ account_equity: 0, pnl: 0, time: '2022-01-01T00:00:00.000Z' },
{ account_equity: 0, pnl: 0, time: '2023-01-01T00:00:00.000Z' },
]
const performanceRangePresets = [
{ label: '24h', value: 1 },
{ label: '7d', value: 7 },
{ label: '30d', value: 30 },
{ label: '3m', value: 90 },
]
const performanceRangePresetLabels = performanceRangePresets.map((x) => x.label)
const PerformanceChart = ({
hourlyPerformanceStats,
accountValue,
chartToShow,
}) => {
const { theme } = useTheme()
const { t } = useTranslation('common')
const { observe, width, height } = useDimensions()
const [chartData, setChartData] = useState<any[]>([])
const [mouseData, setMouseData] = useState<string | null>(null)
const [performanceRange, setPerformanceRange] = useState('30d')
const [showSpotPnl, setShowSpotPnl] = useState(true)
const [showPerpPnl, setShowPerpPnl] = useState(true)
useEffect(() => {
if (hourlyPerformanceStats.length > 0) {
if (performanceRange === '3m') {
setChartData(hourlyPerformanceStats.slice().reverse())
}
if (performanceRange === '30d') {
const start = new Date(
// @ts-ignore
dayjs().utc().hour(0).minute(0).subtract(29, 'day')
).getTime()
const chartData = cloneDeep(hourlyPerformanceStats).filter(
(d) => new Date(d.time).getTime() > start
)
const pnlStart = chartData[chartData.length - 1].pnl
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
for (let i = 0; i < chartData.length; i++) {
if (i === chartData.length - 1) {
chartData[i].pnl = 0
chartData[i].perp_pnl = 0
} else {
chartData[i].pnl = chartData[i].pnl - pnlStart
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
}
}
setChartData(chartData.reverse())
}
if (performanceRange === '7d') {
const start = new Date(
// @ts-ignore
dayjs().utc().hour(0).minute(0).subtract(7, 'day')
).getTime()
const chartData = cloneDeep(hourlyPerformanceStats).filter(
(d) => new Date(d.time).getTime() > start
)
const pnlStart = chartData[chartData.length - 1].pnl
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
for (let i = 0; i < chartData.length; i++) {
if (i === chartData.length - 1) {
chartData[i].pnl = 0
chartData[i].perp_pnl = 0
} else {
chartData[i].pnl = chartData[i].pnl - pnlStart
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
}
}
setChartData(chartData.reverse())
}
if (performanceRange === '24h') {
const start = new Date(
// @ts-ignore
dayjs().utc().hour(0).minute(0).subtract(1, 'day')
).getTime()
const chartData = cloneDeep(hourlyPerformanceStats).filter(
(d) => new Date(d.time).getTime() > start
)
const pnlStart = chartData[chartData.length - 1].pnl
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
for (let i = 0; i < chartData.length; i++) {
if (i === chartData.length - 1) {
chartData[i].pnl = 0
chartData[i].perp_pnl = 0
} else {
chartData[i].pnl = chartData[i].pnl - pnlStart
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
}
}
setChartData(chartData.reverse())
}
} else {
setChartData([])
}
}, [hourlyPerformanceStats, performanceRange])
useEffect(() => {
if (chartData.length > 0) {
for (const stat of chartData) {
stat.spot_pnl = stat.pnl - stat.perp_pnl
}
}
}, [chartData])
const handleMouseMove = (coords) => {
if (coords.activePayload) {
setMouseData(coords.activePayload[0].payload)
}
}
const handleMouseLeave = () => {
setMouseData(null)
}
const renderPnlChartTitle = () => {
if (showPerpPnl && showSpotPnl) {
return t('pnl')
}
if (!showSpotPnl) {
return `${t('perp')} ${t('pnl')}`
}
if (!showPerpPnl) {
return `${t('spot')} ${t('pnl')}`
}
}
const formatDateAxis = (date) => {
if (['3m', '30d'].includes(performanceRange)) {
return dayjs(date).format('D MMM')
} else if (performanceRange === '7d') {
return dayjs(date).format('ddd, h:mma')
} else {
return dayjs(date).format('h:mma')
}
}
const pnlChartDataKey = () => {
if (!showPerpPnl && showSpotPnl) {
return 'spot_pnl'
} else if (!showSpotPnl && showPerpPnl) {
return 'perp_pnl'
} else {
return 'pnl'
}
}
const pnlChartColor =
chartToShow === 'PnL' &&
chartData.length > 0 &&
chartData[chartData.length - 1][pnlChartDataKey()] > 0
? theme === 'Mango'
? '#AFD803'
: '#5EBF4D'
: theme === 'Mango'
? '#F84638'
: '#CC2929'
return (
<div className="h-64" ref={observe}>
<div className="flex flex-col pb-4 sm:flex-row sm:justify-between sm:pb-9">
<div>
<div className="flex items-center pb-0.5">
<div className="text-sm text-th-fgd-3">
{chartToShow === 'Value'
? t('account-value')
: renderPnlChartTitle()}{' '}
<span className="text-th-fgd-4">
{`(${t('timeframe-desc', {
timeframe: performanceRange,
})})`}
</span>
</div>
<Tooltip content={t('delayed-info')}>
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-help text-th-fgd-3" />
</Tooltip>
</div>
{mouseData ? (
<>
<div className="pb-1 text-xl font-bold text-th-fgd-1">
{formatUsdValue(
mouseData[
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
]
)}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{dayjs(mouseData['time']).format('ddd MMM D YYYY, h:mma')}
</div>
</>
) : chartData.length === 0 ? (
<>
<div className="pb-1 text-xl font-bold text-th-fgd-1">--</div>
<div className="text-xs font-normal text-th-fgd-4">
{dayjs().format('ddd MMM D YYYY, h:mma')}
</div>
</>
) : chartData.length > 0 ? (
<>
<div className="pb-1 text-xl font-bold text-th-fgd-1">
{chartToShow === 'PnL'
? formatUsdValue(
chartData[chartData.length - 1][pnlChartDataKey()]
)
: formatUsdValue(accountValue)}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{chartToShow === 'PnL'
? dayjs(chartData[chartData.length - 1]['time']).format(
'ddd MMM D YYYY, h:mma'
)
: dayjs().format('ddd MMM D YYYY, h:mma')}
</div>
</>
) : (
<>
<div className="mt-1 h-8 w-48 animate-pulse rounded bg-th-bkg-3" />
<div className="mt-1 h-4 w-24 animate-pulse rounded bg-th-bkg-3" />
</>
)}
</div>
<div className="flex flex-col sm:items-end">
<div className="mt-4 w-full sm:mt-0 sm:w-40">
<ButtonGroup
activeValue={performanceRange}
className="h-8 font-bold"
onChange={(p) => setPerformanceRange(p)}
values={performanceRangePresetLabels}
/>
</div>
{chartToShow === 'PnL' ? (
<div className="flex space-x-3 pt-4">
<Checkbox
checked={showSpotPnl}
disabled={!showPerpPnl}
onChange={(e) => setShowSpotPnl(e.target.checked)}
>
{t('include-spot')}
</Checkbox>
<Checkbox
checked={showPerpPnl}
disabled={!showSpotPnl}
onChange={(e) => setShowPerpPnl(e.target.checked)}
>
{t('include-perp')}
</Checkbox>
</div>
) : null}
</div>
</div>
{chartData.length > 0 ? (
<AreaChart
width={width}
height={height}
data={chartData?.length ? chartData : defaultData}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<ChartTooltip
cursor={{
strokeOpacity: 0,
}}
content={<></>}
/>
<defs>
<linearGradient
id="defaultGradientArea"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop offset="0%" stopColor="#ffba24" stopOpacity={0.9} />
<stop offset="80%" stopColor="#ffba24" stopOpacity={0} />
</linearGradient>
<linearGradient id="greenGradientArea" x1="0" y1="0" x2="0" y2="1">
<stop
offset="0%"
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
stopOpacity={0.9}
/>
<stop
offset="80%"
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
stopOpacity={0}
/>
</linearGradient>
<linearGradient id="redGradientArea" x1="0" y1="1" x2="0" y2="0">
<stop
offset="0%"
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
stopOpacity={0.9}
/>
<stop
offset="80%"
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
stopOpacity={0}
/>
</linearGradient>
</defs>
<Area
isAnimationActive={true}
type="monotone"
dataKey={
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
}
stroke={chartToShow === 'PnL' ? pnlChartColor : '#ffba24'}
fill={
chartToShow === 'PnL'
? chartData[chartData.length - 1][pnlChartDataKey()] > 0
? 'url(#greenGradientArea)'
: 'url(#redGradientArea)'
: 'url(#defaultGradientArea)'
}
fillOpacity={0.3}
/>
<YAxis
dataKey={
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
}
type="number"
domain={['dataMin', 'dataMax']}
axisLine={false}
dx={-10}
tick={{
fill:
theme === 'Light'
? 'rgba(0,0,0,0.4)'
: 'rgba(255,255,255,0.35)',
fontSize: 10,
}}
tickLine={false}
tickFormatter={(v) => numberCompacter.format(v)}
/>
<XAxis
dataKey="time"
axisLine={false}
dy={10}
minTickGap={20}
tick={{
fill:
theme === 'Light'
? 'rgba(0,0,0,0.4)'
: 'rgba(255,255,255,0.35)',
fontSize: 10,
}}
tickLine={false}
tickFormatter={(v) => formatDateAxis(v)}
/>
</AreaChart>
) : null}
</div>
)
}
export default PerformanceChart

View File

@ -3,6 +3,7 @@ import useMangoStore from '../stores/useMangoStore'
import {
getMultipleAccounts,
nativeToUi,
zeroKey,
} from '@blockworks-foundation/mango-client'
import {
MSRM_DECIMALS,
@ -66,8 +67,10 @@ const useSrmAccount = () => {
)
setSrmAccount(srmAccountInfo.accountInfo)
if (!msrmPk.equals(zeroKey)) {
setMsrmAccount(msrmAccountInfo.accountInfo)
}
}
fetchAccounts()
}

View File

@ -5,7 +5,8 @@
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"devnet": "NEXT_PUBLIC_CLUSTER=devnet NEXT_PUBLIC_ENDPOINT=https://mango.devnet.rpcpool.com/ NEXT_PUBLIC_GROUP=devnet.2 next dev",
"devnet": "NEXT_PUBLIC_CLUSTER=devnet NEXT_PUBLIC_ENDPOINT=https://mango.devnet.rpcpool.com/ NEXT_PUBLIC_GROUP=devnet.4 next dev",
"testnet": "NEXT_PUBLIC_CLUSTER=testnet NEXT_PUBLIC_ENDPOINT=https://api.testnet.solana.com NEXT_PUBLIC_GROUP=testnet.0 next dev",
"build": "next build",
"start": "next start",
"prepare": "husky install",
@ -17,7 +18,7 @@
"analyze": "ANALYZE=true yarn build"
},
"dependencies": {
"@blockworks-foundation/mango-client": "^3.5.5",
"@blockworks-foundation/mango-client": "^3.5.7",
"@headlessui/react": "^0.0.0-insiders.2dbc38c",
"@heroicons/react": "^1.0.0",
"@jup-ag/react-hook": "^1.0.0-beta.22",

View File

@ -5,12 +5,13 @@
"close-account": "Close Account",
"close-all-borrows": "Close all borrows",
"close-open-orders": "Close all open orders",
"close-perp-positions": "Close and settle all Perp positons",
"close-perp-positions": "Close and settle all futures positons",
"closing-account-will": "Closing your Mango Account will:",
"delete-your-account": "Delete your Mango account",
"error-deleting-account": "Error deleting account",
"delete-your-account": "Delete your Mango Account",
"error-deleting-account": "Error deleting your Mango Account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}"
}

View File

@ -213,7 +213,8 @@
"liquidations": "Liquidations",
"liquidity": "Liquidity",
"liquidity-mining": "Liquidity Mining",
"long": "long",
"long": "Long",
"long-exposure": "Long Exposure",
"low": "Low",
"maint-health": "Maint Health",
"make-trade": "Make a trade",
@ -262,14 +263,15 @@
"no-account-found": "No Account Found",
"no-address": "No {{tokenSymbol}} wallet address found",
"no-balances": "No balances",
"no-borrows": "No borrows found.",
"no-borrows": "No borrows found",
"no-chart": "No chart available",
"no-funding": "No funding earned or paid",
"no-history": "No trade history",
"no-interest": "No interest earned or paid",
"no-margin": "No margin accounts found",
"no-markets": "No markets found",
"no-orders": "No open orders",
"no-perp": "No perp positions",
"no-perp": "No futures positions",
"no-trades-found": "No trades found...",
"no-unsettled": "There are no unsettled funds",
"no-wallet": "No wallet address",
@ -301,9 +303,9 @@
"perp": "Perp",
"perp-desc": "Perpetual swaps settled in USDC",
"perp-fees": "Mango Perp Fees",
"perp-positions": "Perp Positions",
"perp-positions-tip-desc": "Perp positions accrue Unsettled PnL as price moves. Redeeming adds or removes that amount from your USDC balance.",
"perp-positions-tip-title": "Perp Position Details",
"perp-positions": "Futures Positions",
"perp-positions-tip-desc": "Futures positions accrue Unsettled PnL as price moves. Redeeming adds or removes that amount from your USDC balance.",
"perp-positions-tip-title": "Futures Position Details",
"perpetual-futures": "Perpetual Futures",
"perps": "Perps",
"pnl": "PnL",
@ -364,7 +366,8 @@
"settle-all": "Settle All",
"settle-error": "Error settling funds",
"settle-success": "Successfully settled funds",
"short": "short",
"short": "Short",
"short-exposure": "Short Exposure",
"show-all": "Show all in Nav",
"show-less": "Show less",
"show-more": "Show More",
@ -432,10 +435,8 @@
"total-funding": "Total Funding",
"total-funding-stats": "Total Funding Earned/Paid",
"total-liabilities": "Total Liabilities Value",
"total-long": "Long Exposure",
"total-long-tooltip": "Deposits, long futures positions and positive pnl unsettled positions",
"total-pnl": "Total PnL",
"total-short": "Short Exposure",
"total-short-tooltip": "Borrows, short futures positions and negative pnl unsettled positions",
"total-srm": "Total SRM in Mango",
"totals": "Totals",

View File

@ -11,6 +11,7 @@
"error-deleting-account": "Error deleting account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}"
}

View File

@ -214,6 +214,7 @@
"liquidity": "Liquidez",
"liquidity-mining": "Minería de liquidez",
"long": "Larga",
"long-exposure": "Long Exposure",
"low": "Bajo",
"maint-health": "Salud de mantenimiento",
"make-trade": "Hacer un trato",
@ -262,7 +263,8 @@
"no-account-found": "Cuenta no encontrada",
"no-address": "No ${tokenSymbol} dirección de billetera encontrada",
"no-balances": "Sin saldos",
"no-borrows": "No se encontraron préstamos.",
"no-borrows": "No se encontraron préstamos",
"no-chart": "No chart available",
"no-funding": "Sin fondos ganados / pagados",
"no-history": "Sin historial comercial",
"no-interest": "Sin intereses ganados / pagados",
@ -365,6 +367,7 @@
"settle-error": "Error al liquidar fondos",
"settle-success": "Fondos liquidados con éxito",
"short": "Vender",
"short-exposure": "Short Exposure",
"show-all": "Mostrar todo en Nav",
"show-less": "Mostrar menos",
"show-more": "Mostrar más",
@ -432,10 +435,8 @@
"total-funding": "Financiamiento total",
"total-funding-stats": "Financiamiento total ganado / pagado",
"total-liabilities": "Valor del pasivo total",
"total-long": "Long Exposure",
"total-long-tooltip": "Deposits, long futures positions and positive pnl unsettled positions",
"total-pnl": "Total PnL",
"total-short": "Short Exposure",
"total-short-tooltip": "Borrows, short futures positions and negative pnl unsettled positions",
"total-srm": "SRM total en mango",
"totals": "Totales",

View File

@ -11,6 +11,7 @@
"error-deleting-account": "删除帐户出错",
"goodbye": "再见 👋",
"recover-x-sol": "收回{{amount}}SOL帐户租金",
"settle-balances": "Settle all balances",
"transaction-confirmed": "交易成功",
"withdraw-assets-worth": "将总价值{{value}}提出到您的钱包"
}

View File

@ -214,6 +214,7 @@
"liquidity": "流动性",
"liquidity-mining": "流动性挖矿",
"long": "做多",
"long-exposure": "Long Exposure",
"low": "低",
"maint-health": "维持健康度",
"make-trade": "下订单",
@ -263,6 +264,7 @@
"no-address": "没有{{tokenSymbol}}钱包地址",
"no-balances": "您没有余额",
"no-borrows": "您没有借贷。",
"no-chart": "No chart available",
"no-funding": "您未收/付过资金费",
"no-history": "您没有交易纪录",
"no-interest": "您未收/付过利息",
@ -365,6 +367,7 @@
"settle-error": "结清出错",
"settle-success": "已结清好",
"short": "做空",
"short-exposure": "Short Exposure",
"show-all": "在导航栏中显示全部",
"show-less": "显示较少",
"show-more": "显示更多",

View File

@ -11,6 +11,7 @@
"error-deleting-account": "刪除帳戶出錯",
"goodbye": "再見 👋",
"recover-x-sol": "收回{{amount}}SOL帳戶租金",
"settle-balances": "Settle all balances",
"transaction-confirmed": "交易成功",
"withdraw-assets-worth": "將總價值{{value}}提出到您的錢包"
}

View File

@ -214,6 +214,7 @@
"liquidity": "流動性",
"liquidity-mining": "流動性挖礦",
"long": "做多",
"long-exposure": "Long Exposure",
"low": "低",
"maint-health": "維持健康度",
"make-trade": "下訂單",
@ -263,6 +264,7 @@
"no-address": "沒有{{tokenSymbol}}錢包地址",
"no-balances": "您沒有餘額",
"no-borrows": "您沒有借貸。",
"no-chart": "No chart available",
"no-funding": "您未收/付過資金費",
"no-history": "您沒有交易紀錄",
"no-interest": "您未收/付過利息",
@ -365,6 +367,7 @@
"settle-error": "結清出錯",
"settle-success": "已結清好",
"short": "做空",
"short-exposure": "Short Exposure",
"show-all": "在導航欄中顯示全部",
"show-less": "顯示較少",
"show-more": "顯示更多",

View File

@ -63,9 +63,15 @@ export const ENDPOINTS: EndpointInfo[] = [
websocket: 'https://api.devnet.solana.com',
custom: false,
},
{
name: 'testnet',
url: 'https://api.testnet.solana.com',
websocket: 'https://api.testnet.solana.com',
custom: false,
},
]
type ClusterType = 'mainnet' | 'devnet'
type ClusterType = 'mainnet' | 'devnet' | 'testnet'
const DEFAULT_MANGO_GROUP_NAME = process.env.NEXT_PUBLIC_GROUP || 'mainnet.1'
export const CLUSTER = DEFAULT_MANGO_GROUP_NAME.split('.')[0] as ClusterType
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER) as EndpointInfo

View File

@ -528,3 +528,55 @@ body::-webkit-scrollbar-corner {
.react-horizontal-scrolling-menu--item {
@apply w-full;
}
/*
* react-circular-progressbar styles
* All of the styles in this file are configurable!
*/
.CircularProgressbar {
/*
* This fixes an issue where the CircularProgressbar svg has
* 0 width inside a "display: flex" container, and thus not visible.
*/
width: 100%;
/*
* This fixes a centering issue with CircularProgressbarWithChildren:
* https://github.com/kevinsqi/react-circular-progressbar/issues/94
*/
vertical-align: middle;
}
.CircularProgressbar .CircularProgressbar-path {
@apply stroke-th-primary;
stroke-linecap: round;
transition: stroke-dashoffset 0.5s ease 0s;
}
.CircularProgressbar .CircularProgressbar-trail {
@apply stroke-th-bkg-4;
/* Used when trail is not full diameter, i.e. when props.circleRatio is set */
stroke-linecap: round;
}
.CircularProgressbar .CircularProgressbar-text {
@apply fill-th-fgd-3 text-sm;
dominant-baseline: middle;
text-anchor: middle;
}
.CircularProgressbar .CircularProgressbar-background {
@apply fill-th-bkg-4;
}
.CircularProgressbar.CircularProgressbar-green .CircularProgressbar-path {
@apply stroke-th-green;
}
.CircularProgressbar.CircularProgressbar-orange .CircularProgressbar-path {
@apply stroke-th-orange;
}
.CircularProgressbar.CircularProgressbar-red .CircularProgressbar-path {
@apply stroke-th-red;
}

View File

@ -1008,10 +1008,10 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@blockworks-foundation/mango-client@^3.5.5":
version "3.5.5"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.5.5.tgz#de71141571e97acae5165b8d94d1149b67f00e4f"
integrity sha512-FSGSo8rqWthfa1ClLmbFrZr6J7iEpw+/rrI04x3a4pPZwpQfiIRaBlwc5SCrD7+q+DR1MD+9vFDRxBpU9hKwfw==
"@blockworks-foundation/mango-client@^3.5.7":
version "3.5.7"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.5.7.tgz#14a2ac00f2bec9198293179d507358f8f6f916d9"
integrity sha512-Yc9UEjEJBaoaV6ZvbAhAZAUYQr/fpjGrub1uUuQYmtiknZwfLb1kZo01+Rfbs/YpMfyZoKTAGHg2ianAMITT6A==
dependencies:
"@project-serum/anchor" "^0.21.0"
"@project-serum/serum" "0.13.55"