add performance chart to account page
This commit is contained in:
parent
46e90570ce
commit
b6999cd220
|
@ -70,7 +70,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
description?.includes('Timed out awaiting') ||
|
||||
description?.includes('was not confirmed')
|
||||
) {
|
||||
parsedTitle = 'Unable to confirm transaction'
|
||||
parsedTitle = 'Transaction status unknown'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,9 +57,9 @@ const TopBar = () => {
|
|||
className={`hidden md:flex md:items-center md:space-x-2 lg:space-x-3 md:ml-4`}
|
||||
>
|
||||
<TradeNavMenu />
|
||||
<MenuItem href="/swap">{t('swap')}</MenuItem>
|
||||
<MenuItem href="/account">{t('account')}</MenuItem>
|
||||
<MenuItem href="/borrow">{t('borrow')}</MenuItem>
|
||||
<MenuItem href="/swap">{t('swap')}</MenuItem>
|
||||
<MenuItem href="/stats">{t('stats')}</MenuItem>
|
||||
<div className="relative">
|
||||
<MenuItem href="/referral">
|
||||
|
|
|
@ -115,7 +115,7 @@ export default function AccountHistory() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="bg-th-bkg-3 flex mb-4 md:mb-6 md:-mt-6 md:-mx-6 px-3 md:px-4 py-2 rounded-md md:rounded-none md:rounded-t-md">
|
||||
<div className="bg-th-bkg-3 flex mb-4 md:mb-6 md:-mx-6 px-3 md:px-4 py-2">
|
||||
{historyViews.map(({ label, key }, index) => (
|
||||
<div
|
||||
className={`font-bold md:px-2 py-1 text-xs md:text-sm ${
|
||||
|
|
|
@ -1,39 +1,77 @@
|
|||
import { useMemo } from 'react'
|
||||
import {
|
||||
ScaleIcon,
|
||||
CurrencyDollarIcon,
|
||||
GiftIcon,
|
||||
HeartIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import { nativeToUi, ZERO_BN } from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore, { MNGO_INDEX } from '../../stores/useMangoStore'
|
||||
import { formatUsdValue } from '../../utils'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { LinkButton } from '../Button'
|
||||
import BalancesTable from '../BalancesTable'
|
||||
import PositionsTable from '../PerpPositionsTable'
|
||||
import Switch from '../Switch'
|
||||
import useLocalStorageState from '../../hooks/useLocalStorageState'
|
||||
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 { useRouter } from 'next/router'
|
||||
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import { formatUsdValue } from '../../utils'
|
||||
import BalancesTable from '../BalancesTable'
|
||||
import Switch from '../Switch'
|
||||
import useLocalStorageState from '../../hooks/useLocalStorageState'
|
||||
import ButtonGroup from '../ButtonGroup'
|
||||
import PerformanceChart from './PerformanceChart'
|
||||
import PositionsTable from '../PerpPositionsTable'
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
const SHOW_ZERO_BALANCE_KEY = 'showZeroAccountBalances-0.2'
|
||||
|
||||
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 fetchHourlyPerformanceStats = async (mangoAccountPk: string) => {
|
||||
const range =
|
||||
performanceRangePresets[performanceRangePresets.length - 1].value
|
||||
const response = await fetch(
|
||||
`https://mango-transaction-log.herokuapp.com/v3/stats/account-performance-detailed?mango-account=${mangoAccountPk}&start-date=${dayjs()
|
||||
.subtract(range, 'day')
|
||||
.format('YYYY-MM-DD')}`
|
||||
)
|
||||
const parsedResponse = await response.json()
|
||||
const entries: any = Object.entries(parsedResponse)
|
||||
|
||||
const stats = entries
|
||||
.map(([key, value]) => {
|
||||
return { ...value, time: key }
|
||||
})
|
||||
.filter((x) => x)
|
||||
.reverse()
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
export default function AccountOverview() {
|
||||
const { t } = useTranslation('common')
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const mangoClient = useMangoStore((s) => s.connection.client)
|
||||
const router = useRouter()
|
||||
const { pubkey } = router.query
|
||||
const [showZeroBalances, setShowZeroBalances] = useLocalStorageState(
|
||||
SHOW_ZERO_BALANCE_KEY,
|
||||
true
|
||||
)
|
||||
|
||||
const [pnl, setPnl] = useState(0)
|
||||
const [performanceRange, setPerformanceRange] = useState('30d')
|
||||
const [hourlyPerformanceStats, setHourlyPerformanceStats] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const stats = await fetchHourlyPerformanceStats(
|
||||
mangoAccount.publicKey.toString()
|
||||
)
|
||||
|
||||
setPnl(stats?.length ? stats?.[0]?.['pnl'] : 0)
|
||||
setHourlyPerformanceStats(stats)
|
||||
}
|
||||
fetchData()
|
||||
}, [mangoAccount.publicKey])
|
||||
|
||||
const maintHealthRatio = useMemo(() => {
|
||||
return mangoAccount
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
|
||||
|
@ -46,93 +84,63 @@ export default function AccountOverview() {
|
|||
: 100
|
||||
}, [mangoAccount, mangoGroup, mangoCache])
|
||||
|
||||
const mngoAccrued = useMemo(() => {
|
||||
return mangoAccount
|
||||
? mangoAccount.perpAccounts.reduce((acc, perpAcct) => {
|
||||
return perpAcct.mngoAccrued.add(acc)
|
||||
}, ZERO_BN)
|
||||
: ZERO_BN
|
||||
const mangoAccountValue = useMemo(() => {
|
||||
return +mangoAccount.computeValue(mangoGroup, mangoCache)
|
||||
}, [mangoAccount])
|
||||
|
||||
const handleRedeemMngo = async () => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const mngoNodeBank =
|
||||
mangoGroup.rootBankAccounts[MNGO_INDEX].nodeBankAccounts[0]
|
||||
|
||||
try {
|
||||
const txid = await mangoClient.redeemAllMngo(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
mangoGroup.tokens[MNGO_INDEX].rootBank,
|
||||
mngoNodeBank.publicKey,
|
||||
mngoNodeBank.vault
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
notify({
|
||||
title: t('redeem-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: t('redeem-failure'),
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return mangoAccount ? (
|
||||
<>
|
||||
<div className="grid grid-flow-col grid-cols-2 grid-rows-2 lg:grid-cols-4 lg:grid-rows-1 gap-2 sm:gap-4 pb-8">
|
||||
<div className="border border-th-bkg-4 p-3 sm:p-4 rounded-md sm:rounded-lg">
|
||||
<div className="pb-0.5 sm:pb-2 text-th-fgd-3 text-xs sm:text-sm">
|
||||
{t('account-value')}
|
||||
</div>
|
||||
<div className="flex items-center pb-1 sm:pb-3">
|
||||
<CurrencyDollarIcon className="flex-shrink-0 h-5 w-5 sm:h-7 sm:w-7 mr-1.5 text-th-primary" />
|
||||
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
|
||||
{formatUsdValue(
|
||||
+mangoAccount.computeValue(mangoGroup, mangoCache)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between pb-4">
|
||||
<h2 className="mb-4 sm:mb-0">{t('summary')}</h2>
|
||||
<div className="w-full sm:w-56">
|
||||
<ButtonGroup
|
||||
activeValue={performanceRange}
|
||||
onChange={(p) => setPerformanceRange(p)}
|
||||
values={performanceRangePresetLabels}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="border border-th-bkg-4 p-3 sm:p-4 rounded-md sm:rounded-lg">
|
||||
<div className="pb-0.5 sm:pb-2 text-th-fgd-3 text-xs sm:text-sm">PNL</div>
|
||||
<div className="flex items-center pb-1 sm:pb-3">
|
||||
<ChartBarIcon className="flex-shrink-0 h-5 w-5 sm:h-7 sm:w-7 mr-1.5 text-th-primary" />
|
||||
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
|
||||
{formatUsdValue(
|
||||
+mangoAccount.computeValue(mangoGroup, mangoCache)
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row lg:space-x-6 pb-8 lg:pb-12">
|
||||
<div className="border-t border-th-bkg-4 pb-6 lg:pb-0 w-full lg:w-1/4">
|
||||
<div className="border-b border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-th-fgd-3 text-xs sm:text-sm">
|
||||
{t('account-value')}
|
||||
</div>
|
||||
<div className="font-bold text-th-fgd-3 text-xl sm:text-2xl">
|
||||
{formatUsdValue(mangoAccountValue)}
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="border border-th-bkg-4 p-3 sm:p-4 rounded-md sm:rounded-lg">
|
||||
<div className="pb-0.5 sm:pb-2 text-th-fgd-3 text-xs sm:text-sm">
|
||||
{t('leverage')}
|
||||
<div className="border-b border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-th-fgd-3 text-xs sm:text-sm">
|
||||
{t('pnl')} ({t('all-time')})
|
||||
</div>
|
||||
<div className="font-bold text-th-fgd-3 text-xl sm:text-2xl">
|
||||
{formatUsdValue(pnl)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center pb-1 sm:pb-3">
|
||||
<ScaleIcon className="flex-shrink-0 h-5 w-5 sm:h-7 sm:w-7 mr-1.5 text-th-primary" />
|
||||
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
|
||||
<div className="border-b border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-th-fgd-3 text-xs sm:text-sm">
|
||||
{t('leverage')}
|
||||
</div>
|
||||
<div className="font-bold text-th-fgd-3 text-xl sm:text-2xl">
|
||||
{mangoAccount.getLeverage(mangoGroup, mangoCache).toFixed(2)}x
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-th-bkg-4 p-3 sm:p-4 rounded-md sm:rounded-lg">
|
||||
<div className="pb-0.5 sm:pb-2 text-th-fgd-3 text-xs sm:text-sm">
|
||||
{t('health-ratio')}
|
||||
</div>
|
||||
<div className="flex items-center pb-3 sm:pb-4">
|
||||
<HeartIcon className="flex-shrink-0 h-5 w-5 sm:h-7 sm:w-7 mr-1.5 text-th-primary" />
|
||||
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
|
||||
<div className="p-3 sm:p-4">
|
||||
<div className="pb-0.5 text-th-fgd-3 text-xs sm:text-sm">
|
||||
{t('health-ratio')}
|
||||
</div>
|
||||
<div className={`font-bold text-th-fgd-3 text-xl sm:text-2xl`}>
|
||||
{maintHealthRatio < 1000 ? maintHealthRatio.toFixed(2) : '>100'}%
|
||||
</div>
|
||||
{mangoAccount.beingLiquidated ? (
|
||||
<div className="pt-0.5 sm:pt-2 text-xs sm:text-sm flex items-center">
|
||||
<ExclamationIcon className="flex-shrink-0 h-5 w-5 sm:h-7 sm:w-7 mr-1.5 text-th-red" />
|
||||
<span className="text-th-red">{t('being-liquidated')}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="h-1.5 flex rounded bg-th-bkg-3">
|
||||
<div className="h-1 flex rounded bg-th-bkg-3">
|
||||
<div
|
||||
style={{
|
||||
width: `${maintHealthRatio}%`,
|
||||
|
@ -146,37 +154,13 @@ export default function AccountOverview() {
|
|||
}`}
|
||||
></div>
|
||||
</div>
|
||||
{mangoAccount.beingLiquidated ? (
|
||||
<div className="pt-0.5 sm:pt-2 text-xs sm:text-sm flex items-center">
|
||||
<ExclamationIcon className="flex-shrink-0 h-5 w-5 sm:h-7 sm:w-7 mr-1.5 text-th-red" />
|
||||
<span className="text-th-red">{t('being-liquidated')}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="border border-th-bkg-4 p-3 sm:p-4 rounded-md sm:rounded-lg">
|
||||
<div className="pb-0.5 sm:pb-2 text-th-fgd-3 text-xs sm:text-sm">
|
||||
{t('mngo-rewards')}
|
||||
</div>
|
||||
<div className="flex items-center pb-1 sm:pb-2">
|
||||
<GiftIcon className="flex-shrink-0 h-5 w-5 sm:h-7 sm:w-7 mr-1.5 text-th-primary" />
|
||||
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
|
||||
{mangoGroup
|
||||
? nativeToUi(
|
||||
mngoAccrued.toNumber(),
|
||||
mangoGroup.tokens[MNGO_INDEX].decimals
|
||||
)
|
||||
: 0}
|
||||
</div>
|
||||
</div>
|
||||
{!pubkey ? (
|
||||
<LinkButton
|
||||
onClick={handleRedeemMngo}
|
||||
disabled={mngoAccrued.eq(ZERO_BN)}
|
||||
className="text-th-primary text-xs"
|
||||
>
|
||||
{t('claim-reward')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
<div className="border-t border-th-bkg-4 h-80 lg:h-auto w-full lg:w-3/4">
|
||||
<PerformanceChart
|
||||
hourlyPerformanceStats={hourlyPerformanceStats}
|
||||
performanceRange={performanceRange}
|
||||
accountValue={mangoAccountValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-8">
|
||||
|
@ -185,7 +169,7 @@ export default function AccountOverview() {
|
|||
</div>
|
||||
<h2 className="mb-4">{t('assets-liabilities')}</h2>
|
||||
|
||||
<div className="grid grid-flow-col grid-cols-1 grid-rows-2 md:grid-cols-2 md:grid-rows-1 gap-2 sm:gap-4 pb-8">
|
||||
<div className="grid grid-flow-col grid-cols-1 grid-rows-2 md:grid-cols-2 md:grid-rows-1 gap-2 sm:gap-4 pb-8 lg:pb-12">
|
||||
<div className="border border-th-bkg-4 p-3 sm:p-4 rounded-md sm:rounded-lg">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
{t('total-assets')}
|
||||
|
|
|
@ -0,0 +1,383 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { cloneDeep } from 'lodash'
|
||||
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 PerformanceChart = ({
|
||||
hourlyPerformanceStats,
|
||||
performanceRange,
|
||||
accountValue,
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { observe, width, height } = useDimensions()
|
||||
|
||||
const [chartData, setChartData] = useState([])
|
||||
const [mouseData, setMouseData] = useState<string | null>(null)
|
||||
const [chartToShow, setChartToShow] = useState('Value')
|
||||
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')} PnL`
|
||||
}
|
||||
if (!showPerpPnl) {
|
||||
return `${t('spot')} 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'
|
||||
|
||||
console.log('chartData', chartData)
|
||||
|
||||
return (
|
||||
<div className="h-64 mt-4 w-full" ref={observe}>
|
||||
<div className="flex justify-between 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="cursor-help h-5 ml-1.5 text-th-fgd-3 w-5" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{mouseData ? (
|
||||
<>
|
||||
<div className="font-bold pb-1 text-xl text-th-fgd-3">
|
||||
{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="font-bold pb-1 text-xl text-th-fgd-3">
|
||||
{formatUsdValue(0.0)}
|
||||
</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="font-bold pb-1 text-xl text-th-fgd-3">
|
||||
{chartToShow === 'PnL'
|
||||
? formatUsdValue(
|
||||
chartData[chartData.length - 1][pnlChartDataKey()]
|
||||
)
|
||||
: formatUsdValue(accountValue)}
|
||||
</div>
|
||||
<div className="text-xs font-normal text-th-fgd-4">
|
||||
{dayjs(chartData[chartData.length - 1]['time']).format(
|
||||
'ddd MMM D YYYY, h:mma'
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="animate-pulse bg-th-bkg-3 h-8 mt-1 rounded w-48" />
|
||||
<div className="animate-pulse bg-th-bkg-3 h-4 mt-1 rounded w-24" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<div className="w-36">
|
||||
<ButtonGroup
|
||||
activeValue={chartToShow}
|
||||
className="pb-2 pt-2 text-sm"
|
||||
onChange={(v) => setChartToShow(v)}
|
||||
values={[t('value'), 'PnL']}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{chartToShow === 'PnL' ? (
|
||||
<div className="flex pt-4 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>
|
||||
{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
|
|
@ -1,15 +1,20 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
BellIcon,
|
||||
CurrencyDollarIcon,
|
||||
DuplicateIcon,
|
||||
ExclamationCircleIcon,
|
||||
ExternalLinkIcon,
|
||||
GiftIcon,
|
||||
LinkIcon,
|
||||
PencilIcon,
|
||||
TrashIcon,
|
||||
UsersIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import useMangoStore, { serumProgramId } from '../stores/useMangoStore'
|
||||
import { nativeToUi, ZERO_BN } from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore, {
|
||||
serumProgramId,
|
||||
MNGO_INDEX,
|
||||
} from '../stores/useMangoStore'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
import AccountOrders from '../components/account_page/AccountOrders'
|
||||
|
@ -18,9 +23,8 @@ import AccountsModal from '../components/AccountsModal'
|
|||
import AccountOverview from '../components/account_page/AccountOverview'
|
||||
import AccountInterest from '../components/account_page/AccountInterest'
|
||||
import AccountFunding from '../components/account_page/AccountFunding'
|
||||
import AccountPerformance from '../components/account_page/AccountPerformance'
|
||||
import AccountNameModal from '../components/AccountNameModal'
|
||||
import Button, { IconButton } from '../components/Button'
|
||||
import Button, { IconButton, LinkButton } from '../components/Button'
|
||||
import EmptyState from '../components/EmptyState'
|
||||
import Loading from '../components/Loading'
|
||||
import Swipeable from '../components/mobile/Swipeable'
|
||||
|
@ -33,6 +37,7 @@ import Select from '../components/Select'
|
|||
import { useRouter } from 'next/router'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import CloseAccountModal from '../components/CloseAccountModal'
|
||||
import { notify } from '../utils/notifications'
|
||||
import {
|
||||
actionsSelector,
|
||||
mangoAccountSelector,
|
||||
|
@ -40,6 +45,7 @@ import {
|
|||
walletConnectedSelector,
|
||||
} from '../stores/selectors'
|
||||
import CreateAlertModal from '../components/CreateAlertModal'
|
||||
import { copyToClipboard } from '../utils'
|
||||
import DelegateModal from '../components/DelegateModal'
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
|
@ -56,24 +62,13 @@ export async function getStaticProps({ locale }) {
|
|||
}
|
||||
}
|
||||
|
||||
const TABS = [
|
||||
'Portfolio',
|
||||
'Orders',
|
||||
'History',
|
||||
'Interest',
|
||||
'Funding',
|
||||
'Performance',
|
||||
]
|
||||
const TABS = ['Portfolio', 'Orders', 'History', 'Interest', 'Funding']
|
||||
|
||||
export default function Account() {
|
||||
const { t } = useTranslation(['common', 'close-account', 'delegate'])
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [showNameModal, setShowNameModal] = useState(false)
|
||||
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
|
||||
const [showAlertsModal, setShowAlertsModal] = useState(false)
|
||||
const [showDelegateModal, setShowDelegateModal] = useState(false)
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const [resetOnLeave, setResetOnLeave] = useState(false)
|
||||
const { width } = useViewport()
|
||||
const router = useRouter()
|
||||
|
||||
const connected = useMangoStore(walletConnectedSelector)
|
||||
const mangoAccount = useMangoStore(mangoAccountSelector)
|
||||
const mangoClient = useMangoStore((s) => s.connection.client)
|
||||
|
@ -82,14 +77,21 @@ export default function Account() {
|
|||
const isLoading = useMangoStore((s) => s.selectedMangoAccount.initialLoad)
|
||||
const actions = useMangoStore(actionsSelector)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [showNameModal, setShowNameModal] = useState(false)
|
||||
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
|
||||
const [showAlertsModal, setShowAlertsModal] = useState(false)
|
||||
const [showDelegateModal, setShowDelegateModal] = useState(false)
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const [resetOnLeave, setResetOnLeave] = useState(false)
|
||||
const [mngoAccrued, setMngoAccrued] = useState(ZERO_BN)
|
||||
const [viewIndex, setViewIndex] = useState(0)
|
||||
const [activeTab, setActiveTab] = useState(TABS[0])
|
||||
const { width } = useViewport()
|
||||
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
const router = useRouter()
|
||||
const { pubkey } = router.query
|
||||
const isDelegatedAccount = !mangoAccount?.owner.equals(wallet?.publicKey)
|
||||
const buttonCols = isDelegatedAccount ? 2 : 4
|
||||
|
||||
const handleCloseAlertModal = useCallback(() => {
|
||||
setShowAlertsModal(false)
|
||||
|
@ -169,6 +171,11 @@ export default function Account() {
|
|||
}
|
||||
}, [isCopied])
|
||||
|
||||
const handleCopyAddress = (address) => {
|
||||
setIsCopied(true)
|
||||
copyToClipboard(address)
|
||||
}
|
||||
|
||||
const handleChangeViewIndex = (index) => {
|
||||
setViewIndex(index)
|
||||
}
|
||||
|
@ -177,6 +184,47 @@ export default function Account() {
|
|||
setActiveTab(tabName)
|
||||
}
|
||||
|
||||
useMemo(() => {
|
||||
setMngoAccrued(
|
||||
mangoAccount
|
||||
? mangoAccount.perpAccounts.reduce((acc, perpAcct) => {
|
||||
return perpAcct.mngoAccrued.add(acc)
|
||||
}, ZERO_BN)
|
||||
: ZERO_BN
|
||||
)
|
||||
}, [mangoAccount])
|
||||
|
||||
const handleRedeemMngo = async () => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const mngoNodeBank =
|
||||
mangoGroup.rootBankAccounts[MNGO_INDEX].nodeBankAccounts[0]
|
||||
|
||||
try {
|
||||
const txid = await mangoClient.redeemAllMngo(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
wallet,
|
||||
mangoGroup.tokens[MNGO_INDEX].rootBank,
|
||||
mngoNodeBank.publicKey,
|
||||
mngoNodeBank.vault
|
||||
)
|
||||
actions.reloadMangoAccount()
|
||||
setMngoAccrued(ZERO_BN)
|
||||
notify({
|
||||
title: t('redeem-success'),
|
||||
description: '',
|
||||
txid,
|
||||
})
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: t('redeem-failure'),
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
|
@ -190,44 +238,69 @@ export default function Account() {
|
|||
{mangoAccount?.name || t('account')}
|
||||
</h1>
|
||||
{!pubkey ? (
|
||||
<IconButton onClick={() => setShowNameModal(true)}>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<IconButton
|
||||
className="h-7 w-7"
|
||||
onClick={() => setShowNameModal(true)}
|
||||
>
|
||||
<PencilIcon className="h-3.5 w-3.5" />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</div>
|
||||
<a
|
||||
className="flex items-center text-th-fgd-3"
|
||||
href={`https://explorer.solana.com/address/${mangoAccount?.publicKey}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="text-xxs sm:text-xs">
|
||||
{mangoAccount.publicKey.toString()}
|
||||
</span>
|
||||
<ExternalLinkIcon className="cursor-pointer default-transition h-4 w-4 ml-1.5 hover:text-th-fgd-1" />
|
||||
</a>
|
||||
<div className="flex items-center text-th-red text-xxs">
|
||||
<div className="flex h-4 items-center">
|
||||
<LinkButton
|
||||
className="flex items-center no-underline text-th-fgd-4"
|
||||
onClick={() =>
|
||||
handleCopyAddress(mangoAccount.publicKey.toString())
|
||||
}
|
||||
>
|
||||
<span className="text-xxs sm:text-xs font-normal">
|
||||
{mangoAccount.publicKey.toBase58()}
|
||||
</span>
|
||||
<DuplicateIcon className="h-4 w-4 ml-1.5" />
|
||||
</LinkButton>
|
||||
{isCopied ? (
|
||||
<span className="bg-th-bkg-3 ml-2 px-1.5 py-0.5 rounded text-xs">
|
||||
Copied
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center text-th-fgd-4 text-xxs">
|
||||
<ExclamationCircleIcon className="h-4 mr-1.5 w-4" />
|
||||
{t('account-address-warning')}
|
||||
</div>
|
||||
</div>
|
||||
{!pubkey ? (
|
||||
<div
|
||||
className={`grid grid-cols-${buttonCols} grid-rows-1 gap-2 auto-cols-min`}
|
||||
>
|
||||
{!isDelegatedAccount && (
|
||||
<div className="flex flex-col sm:flex-row items-center pb-1.5 space-y-2 sm:space-y-0 sm:space-x-2">
|
||||
<button
|
||||
className="bg-th-primary flex items-center justify-center h-8 text-th-bkg-1 text-xs px-3 py-0 rounded-full w-full hover:brightness-[1.15] focus:outline-none disabled:bg-th-bkg-4 disabled:text-th-fgd-4 disabled:cursor-not-allowed disabled:hover:brightness-100"
|
||||
disabled={mngoAccrued.eq(ZERO_BN)}
|
||||
onClick={handleRedeemMngo}
|
||||
>
|
||||
<div className="flex items-center whitespace-nowrap">
|
||||
<GiftIcon className="flex-shrink-0 h-4 w-4 mr-1.5" />
|
||||
{!mngoAccrued.eq(ZERO_BN)
|
||||
? `Claim ${nativeToUi(
|
||||
mngoAccrued.toNumber(),
|
||||
mangoGroup.tokens[MNGO_INDEX].decimals
|
||||
).toLocaleString(undefined, {
|
||||
minimumSignificantDigits: 1,
|
||||
})} MNGO`
|
||||
: '0 MNGO Rewards'}
|
||||
</div>
|
||||
</button>
|
||||
{!isDelegatedAccount ? (
|
||||
<Button
|
||||
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs"
|
||||
className="flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs w-full"
|
||||
onClick={() => setShowCloseAccountModal(true)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<TrashIcon className="h-4 w-4 mr-1.5" />
|
||||
<div className="flex items-center whitespace-nowrap">
|
||||
<TrashIcon className="flex-shrink-0 h-4 w-4 mr-1.5" />
|
||||
{t('close-account:close-account')}
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
) : null}
|
||||
<Button
|
||||
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs"
|
||||
className="flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs w-full"
|
||||
onClick={() => setShowAlertsModal(true)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
|
@ -246,43 +319,34 @@ export default function Account() {
|
|||
</div>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="col-span-1 flex items-center justify-center pt-0 pb-0 h-8 pl-3 pr-3 text-xs"
|
||||
onClick={() => setShowAccountsModal(true)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<CurrencyDollarIcon className="h-4 w-4 mr-1.5" />
|
||||
{t('accounts')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{mangoAccount ? (
|
||||
!isMobile ? (
|
||||
<Tabs
|
||||
activeTab={activeTab}
|
||||
onChange={handleTabChange}
|
||||
tabs={TABS}
|
||||
/>
|
||||
) : (
|
||||
<div className="pb-2 pt-3">
|
||||
<Select
|
||||
value={t(TABS[viewIndex].toLowerCase())}
|
||||
onChange={(e) => handleChangeViewIndex(e)}
|
||||
>
|
||||
{TABS.map((tab, index) => (
|
||||
<Select.Option key={index + tab} value={index}>
|
||||
{t(tab.toLowerCase())}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
<div className="bg-th-bkg-2 p-4 sm:p-6 rounded-lg">
|
||||
{mangoAccount ? (
|
||||
!isMobile ? (
|
||||
<Tabs
|
||||
activeTab={activeTab}
|
||||
onChange={handleTabChange}
|
||||
tabs={TABS}
|
||||
/>
|
||||
) : (
|
||||
<div className="pb-2 pt-3">
|
||||
<Select
|
||||
value={t(TABS[viewIndex].toLowerCase())}
|
||||
onChange={(e) => handleChangeViewIndex(e)}
|
||||
>
|
||||
{TABS.map((tab, index) => (
|
||||
<Select.Option key={index + tab} value={index}>
|
||||
{t(tab.toLowerCase())}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
{mangoAccount ? (
|
||||
!isMobile ? (
|
||||
<TabContent activeTab={activeTab} />
|
||||
|
@ -380,8 +444,6 @@ const TabContent = ({ activeTab }) => {
|
|||
return <AccountInterest />
|
||||
case 'Funding':
|
||||
return <AccountFunding />
|
||||
case 'Performance':
|
||||
return <AccountPerformance />
|
||||
default:
|
||||
return <AccountOverview />
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
"account-health-tip-title": "Account Health",
|
||||
"account-name": "Account Name",
|
||||
"account-performance": "Account Performance",
|
||||
"account-pnl": "Account PNL",
|
||||
"account-pnl-chart-title": "Account PNL",
|
||||
"account-pnl": "Account PnL",
|
||||
"account-pnl-chart-title": "Account PnL",
|
||||
"account-risk": "Account Risk",
|
||||
"account-value": "Account Value",
|
||||
"accounts": "Accounts",
|
||||
|
@ -23,6 +23,7 @@
|
|||
"add-name": "Add Name",
|
||||
"alerts": "Alerts",
|
||||
"all-assets": "All Assets",
|
||||
"all-time": "All Time",
|
||||
"amount": "Amount",
|
||||
"approximate-time": "Time",
|
||||
"asset": "Asset",
|
||||
|
@ -99,6 +100,7 @@
|
|||
"default-market": "Default Market",
|
||||
"default-spot-margin": "Trade with margin by default",
|
||||
"delay-displaying-recent": "There may be a delay in displaying the latest activity.",
|
||||
"delayed-info": "Data updates hourly",
|
||||
"deposit": "Deposit",
|
||||
"deposit-before": "You need more {{tokenSymbol}} in your wallet to fully repay your borrow",
|
||||
"deposit-failed": "Deposit failed",
|
||||
|
@ -151,6 +153,8 @@
|
|||
"hourly-deposit-interest": "Hourly Deposit Interest",
|
||||
"hourly-funding": "Hourly Funding",
|
||||
"in-orders": "In Orders",
|
||||
"include-perp": "Include Perp",
|
||||
"include-spot": "Include Spot",
|
||||
"includes-borrow": "Includes borrow of",
|
||||
"init-error": "Could not perform init mango account and deposit operation",
|
||||
"init-health": "Init Health",
|
||||
|
@ -172,7 +176,7 @@
|
|||
"languages-tip-title": "Multilingual?",
|
||||
"layout-tip-desc": "Unlock to re-arrange and re-size the trading panels to your liking.",
|
||||
"layout-tip-title": "Customize Layout",
|
||||
"learn": "Learn",
|
||||
"learn": "Documentation",
|
||||
"learn-more": "Learn more",
|
||||
"lets-go": "Let's Go",
|
||||
"leverage": "Leverage",
|
||||
|
@ -262,13 +266,14 @@
|
|||
"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. Settling PnL adds or removes that amount from your USDC balance.",
|
||||
"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",
|
||||
"perpetual-futures": "Perpetual Futures",
|
||||
"perps": "Perps",
|
||||
"pnl-error": "Error settling PNL",
|
||||
"pnl": "PnL",
|
||||
"pnl-error": "Error redeeming",
|
||||
"pnl-help": "Redeeming will update your USDC balance to reflect the redeemed PnL amount.",
|
||||
"pnl-success": "Successfully settled PNL",
|
||||
"pnl-success": "Successfully redeemed",
|
||||
"portfolio": "Portfolio",
|
||||
"position": "Position",
|
||||
"position-size": "Position Size",
|
||||
|
@ -334,6 +339,7 @@
|
|||
"stop-loss": "Stop Loss",
|
||||
"stop-price": "Stop Price",
|
||||
"successfully-placed": "Successfully placed order",
|
||||
"summary": "Summary",
|
||||
"supported-assets": "Please fund wallet with one of the supported assets.",
|
||||
"swap": "Swap",
|
||||
"take-profit": "Take Profit",
|
||||
|
@ -344,6 +350,7 @@
|
|||
"themes-tip-desc": "Mango, Dark or Light (if you're that way inclined).",
|
||||
"themes-tip-title": "Color Themes",
|
||||
"time": "Time",
|
||||
"timeframe-desc": "Last {{timeframe}}",
|
||||
"token": "Token",
|
||||
"too-large": "Size Too Large",
|
||||
"tooltip-account-liquidated": "Account will be liquidated if Health Ratio reaches 0% and will continue until Init Health is above 0.",
|
||||
|
|
|
@ -521,4 +521,4 @@ body::-webkit-scrollbar-corner {
|
|||
/* CJK characters */
|
||||
.keep-break {
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue