re-design account page
This commit is contained in:
parent
93923f937f
commit
6362bd3cc1
|
@ -105,13 +105,11 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
||||||
|
|
||||||
<div className="fixed z-20 hidden h-screen md:block">
|
<div className="fixed z-20 hidden h-screen md:block">
|
||||||
<button
|
<button
|
||||||
className="absolute right-0 top-1/2 z-20 hidden h-8 w-3 -translate-y-1/2 rounded-none rounded-l bg-th-bkg-3 hover:bg-th-bkg-4 focus:outline-none focus-visible:bg-th-bkg-4 2xl:block"
|
className="absolute right-0 top-1/2 z-20 hidden h-8 w-3 -translate-y-1/2 rounded-none rounded-l bg-th-bkg-3 hover:bg-th-bkg-4 focus:outline-none focus-visible:bg-th-bkg-4 2xl:flex 2xl:items-center 2xl:justify-center"
|
||||||
onClick={handleToggleSidebar}
|
onClick={handleToggleSidebar}
|
||||||
>
|
>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
className={`absolute bottom-2 h-4 w-4 shrink-0 ${
|
className={`h-4 w-4 shrink-0 ${!isCollapsed ? 'rotate-180' : ''}`}
|
||||||
!isCollapsed ? 'rotate-180' : ''
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
ArchiveBoxArrowDownIcon,
|
ArchiveBoxArrowDownIcon,
|
||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
DocumentTextIcon,
|
DocumentTextIcon,
|
||||||
SparklesIcon,
|
|
||||||
} from '@heroicons/react/20/solid'
|
} from '@heroicons/react/20/solid'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
@ -224,13 +223,6 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
||||||
title={t('trade')}
|
title={t('trade')}
|
||||||
pagePath="/trade"
|
pagePath="/trade"
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
|
||||||
active={pathname === '/explore'}
|
|
||||||
collapsed={collapsed}
|
|
||||||
icon={<SparklesIcon className="h-5 w-5" />}
|
|
||||||
title={t('explore')}
|
|
||||||
pagePath="/explore"
|
|
||||||
/>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
active={pathname === '/borrow'}
|
active={pathname === '/borrow'}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
|
|
|
@ -201,10 +201,21 @@ const TokenList = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const balancesNumber = useMemo(() => {
|
||||||
|
if (!banks.length) return 0
|
||||||
|
const activeBalancesNumber = banks.filter(
|
||||||
|
(bank) => Math.abs(bank.balance) > 0,
|
||||||
|
).length
|
||||||
|
return activeBalancesNumber
|
||||||
|
}, [banks])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentBox hideBorder hidePadding>
|
<ContentBox hideBorder hidePadding>
|
||||||
{mangoAccountAddress ? (
|
{mangoAccountAddress ? (
|
||||||
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 px-6 py-3 lg:-mt-[36px] lg:mb-4 lg:mr-12 lg:w-auto lg:border-0 lg:py-0">
|
<div className="flex w-full items-center justify-between border-b border-th-bkg-3 px-6 py-3">
|
||||||
|
<p className="text-xs text-th-fgd-2">{`${balancesNumber} ${t(
|
||||||
|
'balances',
|
||||||
|
)}`}</p>
|
||||||
<Switch
|
<Switch
|
||||||
checked={showZeroBalances}
|
checked={showZeroBalances}
|
||||||
disabled={!mangoAccountAddress}
|
disabled={!mangoAccountAddress}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
ArrowLeftIcon,
|
|
||||||
ArrowRightIcon,
|
ArrowRightIcon,
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
|
@ -12,17 +11,14 @@ import {
|
||||||
import { useWallet } from '@solana/wallet-adapter-react'
|
import { useWallet } from '@solana/wallet-adapter-react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import WalletIcon from './icons/WalletIcon'
|
import WalletIcon from './icons/WalletIcon'
|
||||||
import Button from './shared/Button'
|
|
||||||
import ConnectedMenu from './wallet/ConnectedMenu'
|
import ConnectedMenu from './wallet/ConnectedMenu'
|
||||||
import ConnectWalletButton from './wallet/ConnectWalletButton'
|
import ConnectWalletButton from './wallet/ConnectWalletButton'
|
||||||
import CreateAccountModal from './modals/CreateAccountModal'
|
import CreateAccountModal from './modals/CreateAccountModal'
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
// import SolanaTps from './SolanaTps'
|
// import SolanaTps from './SolanaTps'
|
||||||
import useMangoAccount from 'hooks/useMangoAccount'
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
import useOnlineStatus from 'hooks/useOnlineStatus'
|
import useOnlineStatus from 'hooks/useOnlineStatus'
|
||||||
import { abbreviateAddress } from 'utils/formatting'
|
import { abbreviateAddress } from 'utils/formatting'
|
||||||
import DepositWithdrawModal from './modals/DepositWithdrawModal'
|
import DepositWithdrawModal from './modals/DepositWithdrawModal'
|
||||||
import { useViewport } from 'hooks/useViewport'
|
|
||||||
import AccountsButton from './AccountsButton'
|
import AccountsButton from './AccountsButton'
|
||||||
import useUnownedAccount from 'hooks/useUnownedAccount'
|
import useUnownedAccount from 'hooks/useUnownedAccount'
|
||||||
import NotificationsButton from './notifications/NotificationsButton'
|
import NotificationsButton from './notifications/NotificationsButton'
|
||||||
|
@ -41,7 +37,7 @@ import Link from 'next/link'
|
||||||
import { useIsWhiteListed } from 'hooks/useIsWhiteListed'
|
import { useIsWhiteListed } from 'hooks/useIsWhiteListed'
|
||||||
|
|
||||||
export const TOPBAR_ICON_BUTTON_CLASSES =
|
export const TOPBAR_ICON_BUTTON_CLASSES =
|
||||||
'relative flex h-16 w-16 items-center justify-center border-l border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2'
|
'relative flex h-16 w-10 sm:w-16 items-center justify-center sm:border-l sm:border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2'
|
||||||
|
|
||||||
const set = mangoStore.getState().set
|
const set = mangoStore.getState().set
|
||||||
|
|
||||||
|
@ -63,11 +59,6 @@ const TopBar = () => {
|
||||||
const [showSettingsModal, setShowSettingsModal] = useState(false)
|
const [showSettingsModal, setShowSettingsModal] = useState(false)
|
||||||
const isOnline = useOnlineStatus()
|
const isOnline = useOnlineStatus()
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const { query } = router
|
|
||||||
|
|
||||||
const { isDesktop } = useViewport()
|
|
||||||
|
|
||||||
const { isUnownedAccount } = useUnownedAccount()
|
const { isUnownedAccount } = useUnownedAccount()
|
||||||
const showUserSetup = mangoStore((s) => s.showUserSetup)
|
const showUserSetup = mangoStore((s) => s.showUserSetup)
|
||||||
const [, setIsOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
|
const [, setIsOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
|
||||||
|
@ -110,17 +101,9 @@ const TopBar = () => {
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-between md:space-x-4">
|
<div className="flex w-full items-center justify-between md:space-x-4">
|
||||||
<span className="mb-0 flex items-center">
|
<span className="mb-0 flex items-center">
|
||||||
{query.token || query.market ? (
|
|
||||||
<button
|
|
||||||
className="flex h-16 w-16 items-center justify-center border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
|
|
||||||
onClick={() => router.back()}
|
|
||||||
>
|
|
||||||
<ArrowLeftIcon className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
<div className="flex h-[63px] w-16 items-center justify-center bg-th-bkg-1 md:hidden">
|
<div className="flex h-[63px] w-16 items-center justify-center bg-th-bkg-1 md:hidden">
|
||||||
<img
|
<img
|
||||||
className="h-9 w-9 flex-shrink-0"
|
className="h-8 w-8 flex-shrink-0"
|
||||||
src={themeData.logoPath}
|
src={themeData.logoPath}
|
||||||
alt="logo"
|
alt="logo"
|
||||||
/>
|
/>
|
||||||
|
@ -189,7 +172,7 @@ const TopBar = () => {
|
||||||
)
|
)
|
||||||
) : isWhiteListed ? (
|
) : isWhiteListed ? (
|
||||||
<Link href="/rewards" shallow={true}>
|
<Link href="/rewards" shallow={true}>
|
||||||
<div className="flex h-16 items-center justify-between bg-gradient-to-br from-th-bkg-2 to-th-bkg-3 px-4 lg:pl-6">
|
<div className="flex h-16 items-center justify-between border-x border-th-bkg-3 px-4 md:border-l-0 lg:pl-6">
|
||||||
<div>
|
<div>
|
||||||
<span className="whitespace-nowrap font-bold text-th-fgd-2">
|
<span className="whitespace-nowrap font-bold text-th-fgd-2">
|
||||||
Points
|
Points
|
||||||
|
@ -208,7 +191,7 @@ const TopBar = () => {
|
||||||
</SheenLoader>
|
</SheenLoader>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ChevronRightIcon className="ml-2 hidden h-7 w-7 text-th-fgd-4 lg:block" />
|
<ChevronRightIcon className="ml-2 hidden h-6 w-6 text-th-fgd-4 lg:block" />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -222,8 +205,7 @@ const TopBar = () => {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{isUnownedAccount ||
|
{isUnownedAccount || !connected ? null : (
|
||||||
(!connected && !isDesktop) ? null : !isDesktop ? (
|
|
||||||
<div className="h-[63px] bg-th-bkg-1">
|
<div className="h-[63px] bg-th-bkg-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDepositWithdrawModal('deposit')}
|
onClick={() => handleDepositWithdrawModal('deposit')}
|
||||||
|
@ -232,12 +214,6 @@ const TopBar = () => {
|
||||||
<DepositWithdrawIcon className="h-6 w-6" />
|
<DepositWithdrawIcon className="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
onClick={() => handleDepositWithdrawModal('deposit')}
|
|
||||||
secondary
|
|
||||||
className="mr-4"
|
|
||||||
>{`${t('deposit')} / ${t('withdraw')}`}</Button>
|
|
||||||
)}
|
)}
|
||||||
<div className="h-[63px] bg-th-bkg-1">
|
<div className="h-[63px] bg-th-bkg-1">
|
||||||
<button
|
<button
|
||||||
|
@ -252,10 +228,14 @@ const TopBar = () => {
|
||||||
<div className="flex h-[63px] items-center bg-th-bkg-1">
|
<div className="flex h-[63px] items-center bg-th-bkg-1">
|
||||||
{mangoAccountAddress && <NotificationsButton />}
|
{mangoAccountAddress && <NotificationsButton />}
|
||||||
<AccountsButton />
|
<AccountsButton />
|
||||||
<ConnectedMenu />
|
<div className="pl-2 sm:pl-0">
|
||||||
|
<ConnectedMenu />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ConnectWalletButton handleShowSetup={handleShowSetup} />
|
<div className="pl-2 sm:pl-0">
|
||||||
|
<ConnectWalletButton handleShowSetup={handleShowSetup} />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,35 +1,19 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Button from '../shared/Button'
|
|
||||||
import {
|
|
||||||
ArrowDownRightIcon,
|
|
||||||
ArrowUpLeftIcon,
|
|
||||||
// DocumentDuplicateIcon,
|
|
||||||
// EyeIcon,
|
|
||||||
// EyeSlashIcon,
|
|
||||||
// PencilIcon,
|
|
||||||
// SquaresPlusIcon,
|
|
||||||
// TrashIcon,
|
|
||||||
// UserPlusIcon,
|
|
||||||
// WrenchIcon,
|
|
||||||
} from '@heroicons/react/20/solid'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import CloseAccountModal from '../modals/CloseAccountModal'
|
|
||||||
import AccountNameModal from '../modals/AccountNameModal'
|
|
||||||
import { copyToClipboard } from 'utils'
|
import { copyToClipboard } from 'utils'
|
||||||
import { notify } from 'utils/notifications'
|
import { notify } from 'utils/notifications'
|
||||||
import DelegateModal from '@components/modals/DelegateModal'
|
|
||||||
import useMangoAccount from 'hooks/useMangoAccount'
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
|
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
|
||||||
import { useWallet } from '@solana/wallet-adapter-react'
|
import { useWallet } from '@solana/wallet-adapter-react'
|
||||||
import CreateAccountModal from '@components/modals/CreateAccountModal'
|
import CreateAccountModal from '@components/modals/CreateAccountModal'
|
||||||
// import { Popover, Transition } from '@headlessui/react'
|
|
||||||
// import ActionsLinkButton from './ActionsLinkButton'
|
|
||||||
import useUnownedAccount from 'hooks/useUnownedAccount'
|
import useUnownedAccount from 'hooks/useUnownedAccount'
|
||||||
// import { useViewport } from 'hooks/useViewport'
|
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
|
||||||
import MangoAccountSizeModal from '@components/modals/MangoAccountSizeModal'
|
import {
|
||||||
// import useMangoAccountAccounts from 'hooks/useMangoAccountAccounts'
|
ArrowDownRightIcon,
|
||||||
// import useLocalStorageState from 'hooks/useLocalStorageState'
|
ArrowDownTrayIcon,
|
||||||
// import { PRIVACY_MODE } from 'utils/constants'
|
ArrowUpLeftIcon,
|
||||||
|
ArrowUpTrayIcon,
|
||||||
|
} from '@heroicons/react/20/solid'
|
||||||
|
|
||||||
export const handleCopyAddress = (
|
export const handleCopyAddress = (
|
||||||
mangoAccountAddress: string,
|
mangoAccountAddress: string,
|
||||||
|
@ -42,28 +26,24 @@ export const handleCopyAddress = (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActionType = 'deposit' | 'withdraw' | 'borrow' | 'repay'
|
||||||
|
|
||||||
|
const ACTION_BUTTON_CLASSES =
|
||||||
|
'flex h-10 items-center justify-center space-x-1 sm:space-x-2 font-bold focus:outline-none md:hover:bg-th-bkg-2 text-xs sm:text-sm'
|
||||||
|
|
||||||
|
const ACTION_BUTTON_ICON_CLASSES = 'h-3.5 w-3.5 sm:h-5 sm:w-5'
|
||||||
|
|
||||||
const AccountActions = () => {
|
const AccountActions = () => {
|
||||||
const { t } = useTranslation(['common', 'close-account', 'settings'])
|
const { t } = useTranslation(['common', 'close-account', 'settings'])
|
||||||
const { mangoAccountAddress } = useMangoAccount()
|
const { mangoAccountAddress } = useMangoAccount()
|
||||||
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
|
const [modalToShow, setModalToShow] = useState('')
|
||||||
const [showEditAccountModal, setShowEditAccountModal] = useState(false)
|
|
||||||
const [showBorrowModal, setShowBorrowModal] = useState(false)
|
|
||||||
const [showRepayModal, setShowRepayModal] = useState(false)
|
|
||||||
const [showDelegateModal, setShowDelegateModal] = useState(false)
|
|
||||||
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
|
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
|
||||||
const [showAccountSizeModal, setShowAccountSizeModal] = useState(false)
|
|
||||||
// const [privacyMode, setPrivacyMode] = useLocalStorageState(PRIVACY_MODE)
|
|
||||||
const { connected } = useWallet()
|
const { connected } = useWallet()
|
||||||
const {
|
const { isUnownedAccount } = useUnownedAccount()
|
||||||
// isDelegatedAccount,
|
|
||||||
isUnownedAccount,
|
|
||||||
} = useUnownedAccount()
|
|
||||||
// const { isMobile } = useViewport()
|
|
||||||
// const { isAccountFull } = useMangoAccountAccounts()
|
|
||||||
|
|
||||||
const handleBorrowModal = () => {
|
const handleActionModal = (type: ActionType) => {
|
||||||
if (mangoAccountAddress || !connected) {
|
if (mangoAccountAddress || !connected) {
|
||||||
setShowBorrowModal(true)
|
setModalToShow(type)
|
||||||
} else {
|
} else {
|
||||||
setShowCreateAccountModal(true)
|
setShowCreateAccountModal(true)
|
||||||
}
|
}
|
||||||
|
@ -72,161 +52,63 @@ const AccountActions = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isUnownedAccount ? null : (
|
{isUnownedAccount ? null : (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="grid grid-cols-4">
|
||||||
<Button
|
<button
|
||||||
className="flex w-1/2 items-center justify-center md:w-auto"
|
className={`${ACTION_BUTTON_CLASSES} border-r border-th-bkg-3`}
|
||||||
disabled={!mangoAccountAddress}
|
onClick={() => handleActionModal('deposit')}
|
||||||
onClick={() => setShowRepayModal(true)}
|
|
||||||
secondary
|
|
||||||
>
|
>
|
||||||
<ArrowDownRightIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
<ArrowDownTrayIcon className={ACTION_BUTTON_ICON_CLASSES} />
|
||||||
{t('repay')}
|
<span>{t('deposit')}</span>
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
className="flex w-1/2 items-center justify-center md:w-auto"
|
className={`${ACTION_BUTTON_CLASSES} border-r border-th-bkg-3`}
|
||||||
onClick={handleBorrowModal}
|
onClick={() => handleActionModal('withdraw')}
|
||||||
secondary
|
|
||||||
>
|
>
|
||||||
<ArrowUpLeftIcon className="mr-2 h-5 w-5 flex-shrink-0" />
|
<ArrowUpTrayIcon className={ACTION_BUTTON_ICON_CLASSES} />
|
||||||
{t('borrow')}
|
<span>{t('withdraw')}</span>
|
||||||
</Button>
|
</button>
|
||||||
{/* <Popover className="relative sm:w-1/3 md:w-auto">
|
<button
|
||||||
{({ open }) => (
|
className={`${ACTION_BUTTON_CLASSES} border-r border-th-bkg-3`}
|
||||||
<>
|
onClick={() => handleActionModal('borrow')}
|
||||||
<Popover.Button
|
>
|
||||||
className={`w-full focus:outline-none`}
|
<ArrowUpLeftIcon className={ACTION_BUTTON_ICON_CLASSES} />
|
||||||
as="div"
|
<span>{t('borrow')}</span>
|
||||||
>
|
</button>
|
||||||
{!isMobile ? (
|
<button
|
||||||
<Button
|
className={ACTION_BUTTON_CLASSES}
|
||||||
className="flex w-full items-center justify-center"
|
onClick={() => handleActionModal('repay')}
|
||||||
secondary
|
>
|
||||||
>
|
<ArrowDownRightIcon className={ACTION_BUTTON_ICON_CLASSES} />
|
||||||
<WrenchIcon className="mr-2 h-4 w-4 flex-shrink-0" />
|
<span>{t('repay')}</span>
|
||||||
{t('actions')}
|
</button>
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<IconButton size="medium">
|
|
||||||
<WrenchIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Popover.Button>
|
|
||||||
<Transition
|
|
||||||
appear={true}
|
|
||||||
show={open}
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-in duration-75"
|
|
||||||
enterFrom="opacity-0 nice scale-75"
|
|
||||||
enterTo="opacity-100 scale-100"
|
|
||||||
leave="transition ease-out duration-100"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<Popover.Panel className="absolute right-0 top-10 mt-1 space-y-2 rounded-md bg-th-bkg-2 px-4 py-2.5">
|
|
||||||
<ActionsLinkButton
|
|
||||||
onClick={() =>
|
|
||||||
handleCopyAddress(
|
|
||||||
mangoAccountAddress,
|
|
||||||
t('copy-address-success', {
|
|
||||||
pk: `${mangoAccountAddress.slice(
|
|
||||||
0,
|
|
||||||
4,
|
|
||||||
)}...${mangoAccountAddress.slice(-4)}`,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DocumentDuplicateIcon className="h-4 w-4" />
|
|
||||||
<span className="ml-2">{t('copy-address')}</span>
|
|
||||||
</ActionsLinkButton>
|
|
||||||
<ActionsLinkButton
|
|
||||||
disabled={isDelegatedAccount}
|
|
||||||
onClick={() => setShowEditAccountModal(true)}
|
|
||||||
>
|
|
||||||
<PencilIcon className="h-4 w-4" />
|
|
||||||
<span className="ml-2">{t('edit-account')}</span>
|
|
||||||
</ActionsLinkButton>
|
|
||||||
<ActionsLinkButton
|
|
||||||
disabled={isDelegatedAccount && isUnownedAccount}
|
|
||||||
onClick={() => setShowDelegateModal(true)}
|
|
||||||
>
|
|
||||||
<UserPlusIcon className="h-4 w-4" />
|
|
||||||
<span className="ml-2">{t('delegate-account')}</span>
|
|
||||||
</ActionsLinkButton>
|
|
||||||
{!isAccountFull ? (
|
|
||||||
<ActionsLinkButton
|
|
||||||
disabled={isDelegatedAccount}
|
|
||||||
onClick={() => setShowAccountSizeModal(true)}
|
|
||||||
>
|
|
||||||
<SquaresPlusIcon className="h-4 w-4" />
|
|
||||||
<span className="ml-2">
|
|
||||||
{t('settings:increase-account-slots')}
|
|
||||||
</span>
|
|
||||||
</ActionsLinkButton>
|
|
||||||
) : null}
|
|
||||||
<ActionsLinkButton
|
|
||||||
disabled={isDelegatedAccount}
|
|
||||||
onClick={() => setShowCloseAccountModal(true)}
|
|
||||||
>
|
|
||||||
<TrashIcon className="h-4 w-4" />
|
|
||||||
<span className="ml-2">{t('close-account')}</span>
|
|
||||||
</ActionsLinkButton>
|
|
||||||
<ActionsLinkButton
|
|
||||||
onClick={() => setPrivacyMode(!privacyMode)}
|
|
||||||
>
|
|
||||||
{privacyMode ? (
|
|
||||||
<>
|
|
||||||
<EyeIcon className="h-4 w-4" />
|
|
||||||
<span className="ml-2">
|
|
||||||
{t('settings:privacy-disable')}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<EyeSlashIcon className="h-4 w-4" />
|
|
||||||
<span className="ml-2">
|
|
||||||
{t('settings:privacy-enable')}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ActionsLinkButton>
|
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Popover> */}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showCloseAccountModal ? (
|
{modalToShow === 'deposit' ? (
|
||||||
<CloseAccountModal
|
<DepositWithdrawModal
|
||||||
isOpen={showCloseAccountModal}
|
action="deposit"
|
||||||
onClose={() => setShowCloseAccountModal(false)}
|
isOpen={modalToShow === 'deposit'}
|
||||||
|
onClose={() => setModalToShow('')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showEditAccountModal ? (
|
{modalToShow === 'withdraw' ? (
|
||||||
<AccountNameModal
|
<DepositWithdrawModal
|
||||||
isOpen={showEditAccountModal}
|
action="withdraw"
|
||||||
onClose={() => setShowEditAccountModal(false)}
|
isOpen={modalToShow === 'withdraw'}
|
||||||
|
onClose={() => setModalToShow('')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showBorrowModal ? (
|
{modalToShow === 'borrow' ? (
|
||||||
<BorrowRepayModal
|
<BorrowRepayModal
|
||||||
action="borrow"
|
action="borrow"
|
||||||
isOpen={showBorrowModal}
|
isOpen={modalToShow === 'borrow'}
|
||||||
onClose={() => setShowBorrowModal(false)}
|
onClose={() => setModalToShow('')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showRepayModal ? (
|
{modalToShow === 'repay' ? (
|
||||||
<BorrowRepayModal
|
<BorrowRepayModal
|
||||||
action="repay"
|
action="repay"
|
||||||
isOpen={showRepayModal}
|
isOpen={modalToShow === 'repay'}
|
||||||
onClose={() => setShowRepayModal(false)}
|
onClose={() => setModalToShow('')}
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{showDelegateModal ? (
|
|
||||||
<DelegateModal
|
|
||||||
isOpen={showDelegateModal}
|
|
||||||
onClose={() => setShowDelegateModal(false)}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showCreateAccountModal ? (
|
{showCreateAccountModal ? (
|
||||||
|
@ -235,12 +117,6 @@ const AccountActions = () => {
|
||||||
onClose={() => setShowCreateAccountModal(false)}
|
onClose={() => setShowCreateAccountModal(false)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showAccountSizeModal ? (
|
|
||||||
<MangoAccountSizeModal
|
|
||||||
isOpen={showAccountSizeModal}
|
|
||||||
onClose={() => setShowAccountSizeModal(false)}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
import { useTranslation } from 'next-i18next'
|
|
||||||
import { useMemo, useState } from 'react'
|
|
||||||
import { formatYAxis } from 'utils/formatting'
|
|
||||||
import { HourlyFundingChartData, PerformanceDataItem } from 'types'
|
|
||||||
import { ContentType } from 'recharts/types/component/Tooltip'
|
|
||||||
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
|
||||||
import { ViewToShow } from './AccountPage'
|
|
||||||
import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
|
||||||
|
|
||||||
const CHART_TABS: ViewToShow[] = [
|
|
||||||
'account-value',
|
|
||||||
'pnl',
|
|
||||||
'cumulative-interest-value',
|
|
||||||
]
|
|
||||||
|
|
||||||
const AccountChart = ({
|
|
||||||
chartName,
|
|
||||||
handleViewChange,
|
|
||||||
customTooltip,
|
|
||||||
data,
|
|
||||||
hideChart,
|
|
||||||
loading,
|
|
||||||
yDecimals,
|
|
||||||
yKey,
|
|
||||||
}: {
|
|
||||||
chartName: ViewToShow
|
|
||||||
handleViewChange: (view: ViewToShow) => void
|
|
||||||
customTooltip?: ContentType<number, string>
|
|
||||||
data: PerformanceDataItem[] | HourlyFundingChartData[] | undefined
|
|
||||||
hideChart: () => void
|
|
||||||
loading?: boolean
|
|
||||||
yDecimals?: number
|
|
||||||
yKey: string
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation('common')
|
|
||||||
const [daysToShow, setDaysToShow] = useState<string>('1')
|
|
||||||
|
|
||||||
const chartData = useMemo(() => {
|
|
||||||
if (!data || !data.length) return []
|
|
||||||
if (chartName === 'cumulative-interest-value') {
|
|
||||||
return data.map((d) => ({
|
|
||||||
interest_value:
|
|
||||||
d.borrow_interest_cumulative_usd * -1 +
|
|
||||||
d.deposit_interest_cumulative_usd,
|
|
||||||
time: d.time,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}, [data, chartName])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="hide-scroll mb-3 flex h-14 items-center space-x-4 overflow-x-auto border-b border-th-bkg-3">
|
|
||||||
<button
|
|
||||||
className="flex h-14 w-14 flex-shrink-0 items-center justify-center border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
|
|
||||||
onClick={hideChart}
|
|
||||||
>
|
|
||||||
<ArrowLeftIcon className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
{CHART_TABS.map((tab) => (
|
|
||||||
<button
|
|
||||||
className={`whitespace-nowrap rounded-md px-2.5 py-1.5 text-sm font-medium focus-visible:bg-th-bkg-3 focus-visible:text-th-fgd-1 ${
|
|
||||||
chartName === tab
|
|
||||||
? 'bg-th-bkg-3 text-th-active md:hover:text-th-active'
|
|
||||||
: 'text-th-fgd-3 md:hover:text-th-fgd-2'
|
|
||||||
}`}
|
|
||||||
onClick={() => handleViewChange(tab)}
|
|
||||||
key={tab}
|
|
||||||
>
|
|
||||||
{t(tab)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="px-6 pt-4">
|
|
||||||
{chartData.length ? (
|
|
||||||
<DetailedAreaOrBarChart
|
|
||||||
customTooltip={customTooltip}
|
|
||||||
data={chartData}
|
|
||||||
daysToShow={daysToShow}
|
|
||||||
heightClass="h-[calc(100vh-240px)]"
|
|
||||||
loaderHeightClass="h-[calc(100vh-166px)]"
|
|
||||||
loading={loading}
|
|
||||||
prefix="$"
|
|
||||||
setDaysToShow={setDaysToShow}
|
|
||||||
tickFormat={(x) => `$${formatYAxis(x)}`}
|
|
||||||
xKey="time"
|
|
||||||
yDecimals={yDecimals}
|
|
||||||
yKey={yKey}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col items-center rounded-lg border border-th-bkg-3 p-8">
|
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
|
||||||
<p>{t('account:no-data')}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AccountChart
|
|
|
@ -1,11 +1,8 @@
|
||||||
import mangoStore from '@store/mangoStore'
|
|
||||||
import useMangoAccount from 'hooks/useMangoAccount'
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
import useMangoGroup from 'hooks/useMangoGroup'
|
import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { useEffect, useMemo } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { ViewToShow } from './AccountPage'
|
import { handleViewChange } from './AccountPage'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
|
||||||
import { fetchFundingTotals, fetchVolumeTotals } from 'utils/account'
|
|
||||||
import Tooltip from '@components/shared/Tooltip'
|
import Tooltip from '@components/shared/Tooltip'
|
||||||
import {
|
import {
|
||||||
HealthType,
|
HealthType,
|
||||||
|
@ -14,73 +11,23 @@ import {
|
||||||
import HealthBar from './HealthBar'
|
import HealthBar from './HealthBar'
|
||||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||||
import { IconButton } from '@components/shared/Button'
|
import { IconButton } from '@components/shared/Button'
|
||||||
import { CalendarIcon, ChartBarIcon } from '@heroicons/react/20/solid'
|
import {
|
||||||
|
CalendarIcon,
|
||||||
|
ChartBarIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
} from '@heroicons/react/20/solid'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
||||||
import Change from '@components/shared/Change'
|
import Change from '@components/shared/Change'
|
||||||
import SheenLoader from '@components/shared/SheenLoader'
|
import PnlHistoryModal from '@components/modals/PnlHistoryModal'
|
||||||
import { PerformanceDataItem } from 'types'
|
|
||||||
import useAccountHourlyVolumeStats from 'hooks/useAccountHourlyVolumeStats'
|
|
||||||
|
|
||||||
const AccountHeroStats = ({
|
const AccountHeroStats = ({ accountValue }: { accountValue: number }) => {
|
||||||
accountPnl,
|
|
||||||
accountValue,
|
|
||||||
rollingDailyData,
|
|
||||||
setShowPnlHistory,
|
|
||||||
handleViewChange,
|
|
||||||
}: {
|
|
||||||
accountPnl: number
|
|
||||||
accountValue: number
|
|
||||||
rollingDailyData: PerformanceDataItem[]
|
|
||||||
setShowPnlHistory: (show: boolean) => void
|
|
||||||
handleViewChange: (view: ViewToShow) => void
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation(['common', 'account'])
|
const { t } = useTranslation(['common', 'account'])
|
||||||
const { group } = useMangoGroup()
|
const { group } = useMangoGroup()
|
||||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||||
const { hourlyVolumeData, loadingHourlyVolume } =
|
const { rollingDailyData } = useAccountPerformanceData()
|
||||||
useAccountHourlyVolumeStats()
|
const router = useRouter()
|
||||||
|
const [showPnlHistory, setShowPnlHistory] = useState(false)
|
||||||
const totalInterestData = mangoStore(
|
|
||||||
(s) => s.mangoAccount.interestTotals.data,
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (mangoAccountAddress) {
|
|
||||||
const actions = mangoStore.getState().actions
|
|
||||||
actions.fetchAccountInterestTotals(mangoAccountAddress)
|
|
||||||
}
|
|
||||||
}, [mangoAccountAddress])
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: fundingData,
|
|
||||||
isLoading: loadingFunding,
|
|
||||||
isFetching: fetchingFunding,
|
|
||||||
} = useQuery(
|
|
||||||
['funding', mangoAccountAddress],
|
|
||||||
() => fetchFundingTotals(mangoAccountAddress),
|
|
||||||
{
|
|
||||||
cacheTime: 1000 * 60 * 10,
|
|
||||||
staleTime: 1000 * 60,
|
|
||||||
retry: 3,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
enabled: !!mangoAccountAddress,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: volumeTotalData,
|
|
||||||
isLoading: loadingVolumeTotalData,
|
|
||||||
isFetching: fetchingVolumeTotalData,
|
|
||||||
} = useQuery(
|
|
||||||
['total-volume', mangoAccountAddress],
|
|
||||||
() => fetchVolumeTotals(mangoAccountAddress),
|
|
||||||
{
|
|
||||||
cacheTime: 1000 * 60 * 10,
|
|
||||||
staleTime: 1000 * 60,
|
|
||||||
retry: 3,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
enabled: !!mangoAccountAddress,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const maintHealth = useMemo(() => {
|
const maintHealth = useMemo(() => {
|
||||||
return group && mangoAccount
|
return group && mangoAccount
|
||||||
|
@ -101,397 +48,217 @@ const AccountHeroStats = ({
|
||||||
}
|
}
|
||||||
}, [mangoAccount, group, accountValue])
|
}, [mangoAccount, group, accountValue])
|
||||||
|
|
||||||
|
const accountPnl = useMemo(() => {
|
||||||
|
if (!group || !mangoAccount) return 0
|
||||||
|
return toUiDecimalsForQuote(mangoAccount.getPnl(group).toNumber())
|
||||||
|
}, [group, mangoAccount])
|
||||||
|
|
||||||
|
const pnlChangeToday = useMemo(() => {
|
||||||
|
if (!accountPnl || !rollingDailyData.length) return 0
|
||||||
|
const startHour = rollingDailyData.find((item) => {
|
||||||
|
const itemHour = new Date(item.time).getHours()
|
||||||
|
return itemHour === 0
|
||||||
|
})
|
||||||
|
const startDayPnl = startHour?.pnl
|
||||||
|
const pnlChangeToday = startDayPnl ? accountPnl - startDayPnl : 0
|
||||||
|
|
||||||
|
return pnlChangeToday
|
||||||
|
}, [accountPnl, rollingDailyData])
|
||||||
|
|
||||||
const rollingDailyPnlChange = useMemo(() => {
|
const rollingDailyPnlChange = useMemo(() => {
|
||||||
if (!accountPnl || !rollingDailyData.length) return 0
|
if (!accountPnl || !rollingDailyData.length) return 0
|
||||||
return accountPnl - rollingDailyData[0].pnl
|
return accountPnl - rollingDailyData[0].pnl
|
||||||
}, [accountPnl, rollingDailyData])
|
}, [accountPnl, rollingDailyData])
|
||||||
|
|
||||||
const interestTotalValue = useMemo(() => {
|
|
||||||
if (totalInterestData.length) {
|
|
||||||
return totalInterestData.reduce(
|
|
||||||
(a, c) => a + (c.borrow_interest_usd * -1 + c.deposit_interest_usd),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return 0.0
|
|
||||||
}, [totalInterestData])
|
|
||||||
|
|
||||||
const fundingTotalValue = useMemo(() => {
|
|
||||||
if (fundingData?.length && mangoAccountAddress) {
|
|
||||||
return fundingData.reduce(
|
|
||||||
(a, c) => a + c.long_funding + c.short_funding,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return 0.0
|
|
||||||
}, [fundingData, mangoAccountAddress])
|
|
||||||
|
|
||||||
const oneDayInterestChange = useMemo(() => {
|
|
||||||
if (rollingDailyData.length) {
|
|
||||||
const first = rollingDailyData[0]
|
|
||||||
const latest = rollingDailyData[rollingDailyData.length - 1]
|
|
||||||
|
|
||||||
const startDayInterest =
|
|
||||||
first.borrow_interest_cumulative_usd +
|
|
||||||
first.deposit_interest_cumulative_usd
|
|
||||||
|
|
||||||
const endDayInterest =
|
|
||||||
latest.borrow_interest_cumulative_usd +
|
|
||||||
latest.deposit_interest_cumulative_usd
|
|
||||||
|
|
||||||
return endDayInterest - startDayInterest
|
|
||||||
}
|
|
||||||
return 0.0
|
|
||||||
}, [rollingDailyData])
|
|
||||||
|
|
||||||
const dailyVolume = useMemo(() => {
|
|
||||||
if (!hourlyVolumeData || !hourlyVolumeData.length) return 0
|
|
||||||
// Calculate the current time in milliseconds
|
|
||||||
const currentTime = new Date().getTime()
|
|
||||||
|
|
||||||
// Calculate the start time for the last 24 hours in milliseconds
|
|
||||||
const last24HoursStartTime = currentTime - 24 * 60 * 60 * 1000
|
|
||||||
|
|
||||||
// Filter the formatted data based on the timestamp
|
|
||||||
const last24HoursData = hourlyVolumeData.filter((entry) => {
|
|
||||||
const timestampMs = new Date(entry.time).getTime()
|
|
||||||
return timestampMs >= last24HoursStartTime && timestampMs <= currentTime
|
|
||||||
})
|
|
||||||
|
|
||||||
const volume = last24HoursData.reduce((a, c) => a + c.total_volume_usd, 0)
|
|
||||||
return volume
|
|
||||||
}, [hourlyVolumeData])
|
|
||||||
|
|
||||||
const loadingTotalVolume = fetchingVolumeTotalData || loadingVolumeTotalData
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-6 border-b border-th-bkg-3">
|
<div className="border-b border-th-bkg-3 px-4 pb-4 pt-3 md:px-6">
|
||||||
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 lg:col-span-2 lg:border-t-0 2xl:col-span-1">
|
<div id="account-step-four">
|
||||||
<div id="account-step-four">
|
<div className="flex justify-between">
|
||||||
<div className="flex justify-between">
|
|
||||||
<Tooltip
|
|
||||||
maxWidth="20rem"
|
|
||||||
placement="top-start"
|
|
||||||
delay={100}
|
|
||||||
content={
|
|
||||||
<div className="flex-col space-y-2 text-sm">
|
|
||||||
<p className="text-xs">
|
|
||||||
Health describes how close your account is to liquidation.
|
|
||||||
The lower your account health is the more likely you are
|
|
||||||
to get liquidated when prices fluctuate.
|
|
||||||
</p>
|
|
||||||
{maintHealth < 100 && mangoAccountAddress ? (
|
|
||||||
<>
|
|
||||||
<p className="text-xs font-bold text-th-fgd-1">
|
|
||||||
Your account health is {maintHealth}%
|
|
||||||
</p>
|
|
||||||
<p className="text-xs">
|
|
||||||
<span className="font-bold text-th-fgd-1">
|
|
||||||
Scenario:
|
|
||||||
</span>{' '}
|
|
||||||
If the prices of all your liabilities increase by{' '}
|
|
||||||
{maintHealth}%, even for just a moment, some of your
|
|
||||||
liabilities will be liquidated.
|
|
||||||
</p>
|
|
||||||
<p className="text-xs">
|
|
||||||
<span className="font-bold text-th-fgd-1">
|
|
||||||
Scenario:
|
|
||||||
</span>{' '}
|
|
||||||
If the value of your total collateral decreases by{' '}
|
|
||||||
{(
|
|
||||||
(1 - 1 / ((maintHealth || 0) / 100 + 1)) *
|
|
||||||
100
|
|
||||||
).toFixed(2)}
|
|
||||||
% , some of your liabilities will be liquidated.
|
|
||||||
</p>
|
|
||||||
<p className="text-xs">
|
|
||||||
These are examples. A combination of events can also
|
|
||||||
lead to liquidation.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<p className="tooltip-underline">{t('health')}</p>
|
|
||||||
</Tooltip>
|
|
||||||
{mangoAccountAddress ? (
|
|
||||||
<Tooltip
|
|
||||||
className="hidden md:block"
|
|
||||||
content={t('account:health-contributions')}
|
|
||||||
delay={100}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
className="text-th-fgd-3"
|
|
||||||
hideBg
|
|
||||||
onClick={() => handleViewChange('health-contributions')}
|
|
||||||
>
|
|
||||||
<ChartBarIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div className="mb-0.5 mt-1 flex items-center space-x-3">
|
|
||||||
<p className="text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
|
||||||
{maintHealth}%
|
|
||||||
</p>
|
|
||||||
<HealthBar health={maintHealth} />
|
|
||||||
</div>
|
|
||||||
<span className="flex text-xs font-normal text-th-fgd-4">
|
|
||||||
<Tooltip
|
|
||||||
content={t('account:tooltip-leverage')}
|
|
||||||
maxWidth="20rem"
|
|
||||||
placement="top-start"
|
|
||||||
delay={100}
|
|
||||||
>
|
|
||||||
<span className="tooltip-underline">{t('leverage')}</span>:
|
|
||||||
</Tooltip>
|
|
||||||
<span className="ml-1 font-mono text-th-fgd-2">
|
|
||||||
<FormatNumericValue value={leverage} decimals={2} roundUp />x
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-6 flex border-t border-th-bkg-3 py-3 pl-6 md:col-span-3 md:border-l lg:col-span-2 lg:border-t-0 2xl:col-span-1">
|
|
||||||
<div id="account-step-five">
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={t('account:tooltip-free-collateral')}
|
|
||||||
maxWidth="20rem"
|
maxWidth="20rem"
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
delay={100}
|
delay={100}
|
||||||
>
|
content={
|
||||||
<p className="tooltip-underline">{t('free-collateral')}</p>
|
<div className="flex-col space-y-2 text-sm">
|
||||||
</Tooltip>
|
<p className="text-xs">
|
||||||
<p className="mb-0.5 mt-1 text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
Health describes how close your account is to liquidation.
|
||||||
<FormatNumericValue
|
The lower your account health is the more likely you are to
|
||||||
value={
|
get liquidated when prices fluctuate.
|
||||||
group && mangoAccount
|
</p>
|
||||||
? toUiDecimalsForQuote(
|
{maintHealth < 100 && mangoAccountAddress ? (
|
||||||
mangoAccount.getCollateralValue(group),
|
<>
|
||||||
)
|
<p className="text-xs font-bold text-th-fgd-1">
|
||||||
: 0
|
Your account health is {maintHealth}%
|
||||||
}
|
</p>
|
||||||
decimals={2}
|
<p className="text-xs">
|
||||||
isUsd={true}
|
<span className="font-bold text-th-fgd-1">
|
||||||
/>
|
Scenario:
|
||||||
</p>
|
</span>{' '}
|
||||||
<span className="text-xs font-normal text-th-fgd-4">
|
If the prices of all your liabilities increase by{' '}
|
||||||
<Tooltip
|
{maintHealth}%, even for just a moment, some of your
|
||||||
content={t('account:tooltip-total-collateral')}
|
liabilities will be liquidated.
|
||||||
maxWidth="20rem"
|
</p>
|
||||||
placement="top-start"
|
<p className="text-xs">
|
||||||
delay={100}
|
<span className="font-bold text-th-fgd-1">
|
||||||
>
|
Scenario:
|
||||||
<span className="tooltip-underline">{t('total')}</span>:
|
</span>{' '}
|
||||||
<span className="ml-1 font-mono text-th-fgd-2">
|
If the value of your total collateral decreases by{' '}
|
||||||
<FormatNumericValue
|
{(
|
||||||
value={
|
(1 - 1 / ((maintHealth || 0) / 100 + 1)) *
|
||||||
group && mangoAccount
|
100
|
||||||
? toUiDecimalsForQuote(
|
).toFixed(2)}
|
||||||
mangoAccount.getAssetsValue(group, HealthType.init),
|
% , some of your liabilities will be liquidated.
|
||||||
)
|
</p>
|
||||||
: 0
|
<p className="text-xs">
|
||||||
}
|
These are examples. A combination of events can also
|
||||||
decimals={2}
|
lead to liquidation.
|
||||||
isUsd={true}
|
</p>
|
||||||
/>
|
</>
|
||||||
</span>
|
) : null}
|
||||||
</Tooltip>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-6 flex border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 lg:col-span-2 lg:border-l lg:border-t-0 2xl:col-span-1">
|
|
||||||
<div
|
|
||||||
id="account-step-seven"
|
|
||||||
className="flex w-full flex-col items-start"
|
|
||||||
>
|
|
||||||
<div className="flex w-full items-center justify-between">
|
|
||||||
<Tooltip
|
|
||||||
content={t('account:tooltip-pnl')}
|
|
||||||
placement="top-start"
|
|
||||||
delay={100}
|
|
||||||
>
|
|
||||||
<p className="tooltip-underline">{t('pnl')}</p>
|
|
||||||
</Tooltip>
|
|
||||||
{mangoAccountAddress ? (
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<Tooltip
|
|
||||||
className="hidden md:block"
|
|
||||||
content={t('account:pnl-chart')}
|
|
||||||
delay={100}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
className="text-th-fgd-3"
|
|
||||||
hideBg
|
|
||||||
onClick={() => handleViewChange('pnl')}
|
|
||||||
>
|
|
||||||
<ChartBarIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip
|
|
||||||
className="hidden md:block"
|
|
||||||
content={t('account:pnl-history')}
|
|
||||||
delay={100}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
className="text-th-fgd-3"
|
|
||||||
hideBg
|
|
||||||
onClick={() => setShowPnlHistory(true)}
|
|
||||||
>
|
|
||||||
<CalendarIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
}
|
||||||
</div>
|
|
||||||
<p className="mb-0.5 mt-1 text-left text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
|
||||||
<FormatNumericValue
|
|
||||||
value={accountPnl}
|
|
||||||
decimals={2}
|
|
||||||
isUsd={true}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<div className="flex space-x-1.5">
|
|
||||||
<Change change={rollingDailyPnlChange} prefix="$" size="small" />
|
|
||||||
<p className="text-xs text-th-fgd-4">{t('rolling-change')}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 md:col-span-3 md:border-l lg:col-span-2 lg:border-l-0 2xl:col-span-1 2xl:border-l 2xl:border-t-0">
|
|
||||||
<div id="account-step-six">
|
|
||||||
<div className="flex w-full items-center justify-between">
|
|
||||||
<p>{t('account:lifetime-volume')}</p>
|
|
||||||
{mangoAccountAddress ? (
|
|
||||||
<Tooltip
|
|
||||||
className="hidden md:block"
|
|
||||||
content={t('account:volume-chart')}
|
|
||||||
delay={100}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
className="text-th-fgd-3"
|
|
||||||
hideBg
|
|
||||||
onClick={() => handleViewChange('hourly-volume')}
|
|
||||||
>
|
|
||||||
<ChartBarIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
{loadingTotalVolume && mangoAccountAddress ? (
|
|
||||||
<SheenLoader className="mt-1">
|
|
||||||
<div className="h-7 w-16 bg-th-bkg-2" />
|
|
||||||
</SheenLoader>
|
|
||||||
) : (
|
|
||||||
<p className="mt-1 text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
|
||||||
<FormatNumericValue value={volumeTotalData || 0} isUsd />
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<span className="flex items-center text-xs font-normal text-th-fgd-4">
|
|
||||||
<span>{t('account:daily-volume')}</span>:
|
|
||||||
{loadingHourlyVolume && mangoAccountAddress ? (
|
|
||||||
<SheenLoader className="ml-1">
|
|
||||||
<div className="h-3.5 w-10 bg-th-bkg-2" />
|
|
||||||
</SheenLoader>
|
|
||||||
) : (
|
|
||||||
<span className="ml-1 font-mono text-th-fgd-2">
|
|
||||||
<FormatNumericValue
|
|
||||||
value={dailyVolume}
|
|
||||||
decimals={2}
|
|
||||||
isUsd={true}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 text-left md:col-span-3 lg:col-span-2 lg:border-l 2xl:col-span-1 2xl:border-t-0">
|
|
||||||
<div id="account-step-eight">
|
|
||||||
<div className="flex w-full items-center justify-between">
|
|
||||||
<Tooltip
|
|
||||||
content={t('account:tooltip-total-interest')}
|
|
||||||
maxWidth="20rem"
|
|
||||||
placement="top-start"
|
|
||||||
delay={100}
|
|
||||||
>
|
|
||||||
<p className="tooltip-underline">
|
|
||||||
{t('total-interest-earned')}
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
|
||||||
{mangoAccountAddress ? (
|
|
||||||
<Tooltip
|
|
||||||
className="hidden md:block"
|
|
||||||
content={t('account:cumulative-interest-chart')}
|
|
||||||
delay={100}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
className="text-th-fgd-3"
|
|
||||||
hideBg
|
|
||||||
onClick={() =>
|
|
||||||
handleViewChange('cumulative-interest-value')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ChartBarIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<p className="mb-0.5 mt-1 text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
|
||||||
<FormatNumericValue
|
|
||||||
value={interestTotalValue}
|
|
||||||
decimals={2}
|
|
||||||
isUsd={true}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<div className="flex space-x-1.5">
|
|
||||||
<Change change={oneDayInterestChange} prefix="$" size="small" />
|
|
||||||
<p className="text-xs text-th-fgd-4">{t('rolling-change')}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-6 border-t border-th-bkg-3 py-3 pl-6 pr-4 text-left md:col-span-3 md:border-l lg:col-span-2 2xl:col-span-1 2xl:border-t-0">
|
|
||||||
<div className="flex w-full items-center justify-between">
|
|
||||||
<Tooltip
|
|
||||||
content={t('account:tooltip-total-funding')}
|
|
||||||
maxWidth="20rem"
|
|
||||||
placement="top-start"
|
|
||||||
delay={100}
|
|
||||||
>
|
>
|
||||||
<p className="tooltip-underline">
|
<p className="tooltip-underline">{t('health')}</p>
|
||||||
{t('account:total-funding-earned')}
|
|
||||||
</p>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{mangoAccountAddress ? (
|
{mangoAccountAddress ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className="hidden md:block"
|
className="hidden md:block"
|
||||||
content={t('account:funding-chart')}
|
content={t('account:health-contributions')}
|
||||||
delay={100}
|
delay={100}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
className="text-th-fgd-3"
|
className="text-th-fgd-3"
|
||||||
hideBg
|
hideBg
|
||||||
onClick={() => handleViewChange('hourly-funding')}
|
onClick={() =>
|
||||||
|
handleViewChange('health-contributions', router)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ChartBarIcon className="h-5 w-5" />
|
<ChartBarIcon className="h-5 w-5" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{(loadingFunding || fetchingFunding) && mangoAccountAddress ? (
|
<div className="mb-0.5 mt-2 flex items-center space-x-3">
|
||||||
<SheenLoader className="mt-2">
|
<p className="text-2xl font-bold text-th-fgd-1 lg:text-3xl">
|
||||||
<div className="h-7 w-16 bg-th-bkg-2" />
|
{maintHealth}%
|
||||||
</SheenLoader>
|
|
||||||
) : (
|
|
||||||
<p className="mb-0.5 mt-1 text-2xl font-bold text-th-fgd-1 lg:text-xl xl:text-2xl">
|
|
||||||
<FormatNumericValue
|
|
||||||
value={fundingTotalValue}
|
|
||||||
decimals={2}
|
|
||||||
isUsd={true}
|
|
||||||
/>
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
<HealthBar health={maintHealth} />
|
||||||
|
</div>
|
||||||
|
<span className="flex font-normal text-th-fgd-4">
|
||||||
|
<Tooltip
|
||||||
|
content={t('account:tooltip-leverage')}
|
||||||
|
maxWidth="20rem"
|
||||||
|
placement="top-start"
|
||||||
|
delay={100}
|
||||||
|
>
|
||||||
|
<span className="tooltip-underline">{t('leverage')}</span>:
|
||||||
|
</Tooltip>
|
||||||
|
<span className="ml-1 font-mono text-th-fgd-2">
|
||||||
|
<FormatNumericValue value={leverage} decimals={2} roundUp />x
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex border-b border-th-bkg-3 px-4 pb-4 pt-3 md:px-6">
|
||||||
|
<div id="account-step-five">
|
||||||
|
<Tooltip
|
||||||
|
content={t('account:tooltip-free-collateral')}
|
||||||
|
maxWidth="20rem"
|
||||||
|
placement="top-start"
|
||||||
|
delay={100}
|
||||||
|
>
|
||||||
|
<p className="tooltip-underline">{t('free-collateral')}</p>
|
||||||
|
</Tooltip>
|
||||||
|
<p className="mb-0.5 mt-2 text-2xl font-bold text-th-fgd-1 lg:text-3xl">
|
||||||
|
<FormatNumericValue
|
||||||
|
value={
|
||||||
|
group && mangoAccount
|
||||||
|
? toUiDecimalsForQuote(mangoAccount.getCollateralValue(group))
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
decimals={2}
|
||||||
|
isUsd={true}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<span className="font-normal text-th-fgd-4">
|
||||||
|
<Tooltip
|
||||||
|
content={t('account:tooltip-total-collateral')}
|
||||||
|
maxWidth="20rem"
|
||||||
|
placement="top-start"
|
||||||
|
delay={100}
|
||||||
|
>
|
||||||
|
<span className="tooltip-underline">{t('total')}</span>:
|
||||||
|
<span className="ml-1 font-mono text-th-fgd-2">
|
||||||
|
<FormatNumericValue
|
||||||
|
value={
|
||||||
|
group && mangoAccount
|
||||||
|
? toUiDecimalsForQuote(
|
||||||
|
mangoAccount.getAssetsValue(group, HealthType.init),
|
||||||
|
)
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
decimals={2}
|
||||||
|
isUsd={true}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="account-step-seven"
|
||||||
|
className="border-b border-th-bkg-3 px-4 pb-4 pt-3 md:px-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Tooltip
|
||||||
|
content={t('account:tooltip-pnl')}
|
||||||
|
placement="top-start"
|
||||||
|
delay={100}
|
||||||
|
>
|
||||||
|
<p className="tooltip-underline">{t('pnl')}</p>
|
||||||
|
</Tooltip>
|
||||||
|
{mangoAccountAddress ? (
|
||||||
|
<Tooltip
|
||||||
|
className="hidden md:block"
|
||||||
|
content={t('account:pnl-history')}
|
||||||
|
delay={100}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
className="text-th-fgd-3"
|
||||||
|
hideBg
|
||||||
|
onClick={() => setShowPnlHistory(true)}
|
||||||
|
>
|
||||||
|
<CalendarIcon className="h-5 w-5" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<p className="mb-0.5 mt-2 text-2xl font-bold text-th-fgd-1 lg:text-3xl">
|
||||||
|
<FormatNumericValue value={accountPnl} decimals={2} isUsd={true} />
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center space-x-1.5">
|
||||||
|
<Change change={rollingDailyPnlChange} prefix="$" />
|
||||||
|
<p className="text-th-fgd-4">{t('rolling-change')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="default-transition flex h-10 w-full items-center justify-between px-4 focus:outline-none disabled:cursor-not-allowed md:px-6 md:hover:bg-th-bkg-2"
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/?view=account-stats', undefined, { shallow: true })
|
||||||
|
}
|
||||||
|
disabled={!mangoAccountAddress}
|
||||||
|
>
|
||||||
|
<p>{t('account:more-account-stats')}</p>
|
||||||
|
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
||||||
|
</button>
|
||||||
|
{showPnlHistory ? (
|
||||||
|
<PnlHistoryModal
|
||||||
|
isOpen={showPnlHistory}
|
||||||
|
onClose={() => setShowPnlHistory(false)}
|
||||||
|
pnlChangeToday={pnlChangeToday}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import SwapOrders from '@components/swap/SwapTriggerOrders'
|
||||||
|
import OpenOrders from '@components/trade/OpenOrders'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const AccountOrders = () => {
|
||||||
|
const { t } = useTranslation('trade')
|
||||||
|
const { mangoAccount } = useMangoAccount()
|
||||||
|
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||||
|
const [activeTab, setActiveTab] = useState('trade:limit')
|
||||||
|
|
||||||
|
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||||
|
const stopOrdersCount =
|
||||||
|
mangoAccount?.tokenConditionalSwaps.filter((tcs) => tcs.hasData)
|
||||||
|
?.length || 0
|
||||||
|
const tabs: [string, number][] = [
|
||||||
|
['trade:limit', Object.values(openOrders).flat().length],
|
||||||
|
['trade:trigger-orders', stopOrdersCount],
|
||||||
|
]
|
||||||
|
return tabs
|
||||||
|
}, [mangoAccount, openOrders])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex space-x-6 px-4 py-4 md:px-6">
|
||||||
|
{tabsWithCount.map((tab) => (
|
||||||
|
<button
|
||||||
|
className={`flex items-center space-x-2 text-base font-bold focus:outline-none ${
|
||||||
|
activeTab === tab[0] ? 'text-th-active' : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => setActiveTab(tab[0])}
|
||||||
|
key={tab[0]}
|
||||||
|
>
|
||||||
|
<span>{t(tab[0])}</span>
|
||||||
|
<div className="rounded-md bg-th-bkg-3 px-1.5 py-0.5 font-body text-xs font-medium text-th-fgd-2">
|
||||||
|
<span>{tab[1]}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 flex-col border-t border-th-bkg-3">
|
||||||
|
{activeTab === 'trade:limit' ? <OpenOrders /> : <SwapOrders />}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountOrders
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import AccountActions from './AccountActions'
|
||||||
|
import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
|
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
||||||
|
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import AccountHeroStats from './AccountHeroStats'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Explore from '@components/explore/Explore'
|
||||||
|
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
||||||
|
import { formatYAxis } from 'utils/formatting'
|
||||||
|
import { useWallet } from '@solana/wallet-adapter-react'
|
||||||
|
import ConnectEmptyState from '@components/shared/ConnectEmptyState'
|
||||||
|
import CreateAccountModal from '@components/modals/CreateAccountModal'
|
||||||
|
import { FaceSmileIcon } from '@heroicons/react/20/solid'
|
||||||
|
import Button from '@components/shared/Button'
|
||||||
|
|
||||||
|
const EMPTY_STATE_WRAPPER_CLASSES =
|
||||||
|
'flex h-[180px] flex-col justify-center pb-4 md:h-full'
|
||||||
|
|
||||||
|
const AccountOverview = () => {
|
||||||
|
const { t } = useTranslation(['common', 'governance'])
|
||||||
|
const { group } = useMangoGroup()
|
||||||
|
const { mangoAccount, initialLoad } = useMangoAccount()
|
||||||
|
const { connected } = useWallet()
|
||||||
|
const { performanceData, rollingDailyData, loadingPerformanceData } =
|
||||||
|
useAccountPerformanceData()
|
||||||
|
const [daysToShow, setDaysToShow] = useState('1')
|
||||||
|
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
|
||||||
|
|
||||||
|
const accountValue = useMemo(() => {
|
||||||
|
if (!group || !mangoAccount) return 0
|
||||||
|
return toUiDecimalsForQuote(mangoAccount.getEquity(group).toNumber())
|
||||||
|
}, [group, mangoAccount])
|
||||||
|
|
||||||
|
const latestAccountData = useMemo(() => {
|
||||||
|
if (!accountValue || !performanceData || !performanceData.length) return []
|
||||||
|
const latestDataItem = performanceData[performanceData.length - 1]
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
account_equity: accountValue,
|
||||||
|
time: dayjs(Date.now()).toISOString(),
|
||||||
|
borrow_interest_cumulative_usd:
|
||||||
|
latestDataItem.borrow_interest_cumulative_usd,
|
||||||
|
deposit_interest_cumulative_usd:
|
||||||
|
latestDataItem.deposit_interest_cumulative_usd,
|
||||||
|
pnl: latestDataItem.pnl,
|
||||||
|
spot_value: latestDataItem.spot_value,
|
||||||
|
transfer_balance: latestDataItem.transfer_balance,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [accountValue, performanceData])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="grid grid-cols-12 border-b border-th-bkg-3">
|
||||||
|
<div className="col-span-12 border-b border-th-bkg-3 pt-4 md:col-span-8 md:border-b-0 md:border-r">
|
||||||
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
|
{mangoAccount || (connected && initialLoad) ? (
|
||||||
|
<div className="px-4 pb-4 md:px-6">
|
||||||
|
<DetailedAreaOrBarChart
|
||||||
|
data={rollingDailyData.concat(latestAccountData)}
|
||||||
|
daysToShow={daysToShow}
|
||||||
|
setDaysToShow={setDaysToShow}
|
||||||
|
loading={loadingPerformanceData || initialLoad}
|
||||||
|
heightClass="h-[180px] lg:h-[205px]"
|
||||||
|
hideAxis
|
||||||
|
loaderHeightClass="h-[290px] lg:h-[315px]"
|
||||||
|
prefix="$"
|
||||||
|
tickFormat={(x) => `$${formatYAxis(x)}`}
|
||||||
|
title={t('account-value')}
|
||||||
|
xKey="time"
|
||||||
|
yKey="account_equity"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : connected ? (
|
||||||
|
<div className={EMPTY_STATE_WRAPPER_CLASSES}>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<FaceSmileIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
<p className="mb-4">Create a Mango Account to get started</p>
|
||||||
|
<Button onClick={() => setShowCreateAccountModal(true)}>
|
||||||
|
{t('create-account')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={EMPTY_STATE_WRAPPER_CLASSES}>
|
||||||
|
<ConnectEmptyState text={t('governance:connect-wallet')} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="border-t border-th-bkg-3">
|
||||||
|
<AccountActions />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 md:col-span-4">
|
||||||
|
<AccountHeroStats accountValue={accountValue} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Explore />
|
||||||
|
{showCreateAccountModal ? (
|
||||||
|
<CreateAccountModal
|
||||||
|
isOpen={showCreateAccountModal}
|
||||||
|
onClose={() => setShowCreateAccountModal(false)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountOverview
|
|
@ -1,30 +1,15 @@
|
||||||
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
|
import { useCallback } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
|
||||||
import AccountActions from './AccountActions'
|
|
||||||
import AccountTabs from './AccountTabs'
|
import AccountTabs from './AccountTabs'
|
||||||
import AccountChart from './AccountChart'
|
|
||||||
import useMangoAccount from '../../hooks/useMangoAccount'
|
|
||||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { useViewport } from 'hooks/useViewport'
|
|
||||||
import useMangoGroup from 'hooks/useMangoGroup'
|
|
||||||
import PnlHistoryModal from '@components/modals/PnlHistoryModal'
|
|
||||||
import AssetsLiabilities from './AssetsLiabilities'
|
|
||||||
import FundingChart from './FundingChart'
|
|
||||||
import VolumeChart from './VolumeChart'
|
|
||||||
import AccountHeroStats from './AccountHeroStats'
|
|
||||||
import AccountValue from './AccountValue'
|
|
||||||
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
|
||||||
import HealthContributions from './HealthContributions'
|
import HealthContributions from './HealthContributions'
|
||||||
import { PerformanceDataItem } from 'types'
|
import { NextRouter, useRouter } from 'next/router'
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import { useWallet } from '@solana/wallet-adapter-react'
|
import { useWallet } from '@solana/wallet-adapter-react'
|
||||||
|
import AccountStats from './AccountStats'
|
||||||
const TABS = ['account-value', 'account:assets-liabilities']
|
import PerpStatsPage from '@components/stats/perps/PerpStatsPage'
|
||||||
|
import TokenPage from '@components/token/TokenPage'
|
||||||
|
|
||||||
export type ViewToShow =
|
export type ViewToShow =
|
||||||
| ''
|
| ''
|
||||||
|
| 'account-stats'
|
||||||
| 'account-value'
|
| 'account-value'
|
||||||
| 'cumulative-interest-value'
|
| 'cumulative-interest-value'
|
||||||
| 'pnl'
|
| 'pnl'
|
||||||
|
@ -32,147 +17,32 @@ export type ViewToShow =
|
||||||
| 'hourly-volume'
|
| 'hourly-volume'
|
||||||
| 'health-contributions'
|
| 'health-contributions'
|
||||||
|
|
||||||
|
export const handleViewChange = (view: ViewToShow, router: NextRouter) => {
|
||||||
|
const query = { ...router.query, ['view']: view }
|
||||||
|
router.push({ pathname: router.pathname, query })
|
||||||
|
}
|
||||||
|
|
||||||
const AccountPage = () => {
|
const AccountPage = () => {
|
||||||
const { t } = useTranslation(['common', 'account'])
|
|
||||||
const { group } = useMangoGroup()
|
|
||||||
const { mangoAccount } = useMangoAccount()
|
|
||||||
const [showPnlHistory, setShowPnlHistory] = useState<boolean>(false)
|
|
||||||
const { isTablet } = useViewport()
|
|
||||||
const [activeTab, setActiveTab] = useLocalStorageState(
|
|
||||||
'accountHeroKey-0.1',
|
|
||||||
'account-value',
|
|
||||||
)
|
|
||||||
const { performanceData, rollingDailyData } = useAccountPerformanceData()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { view } = router.query
|
const { market, token, view } = router.query
|
||||||
|
|
||||||
const handleViewChange = useCallback(
|
return market ? (
|
||||||
(view: ViewToShow) => {
|
<PerpStatsPage />
|
||||||
const query = { ...router.query, ['view']: view }
|
) : token ? (
|
||||||
router.push({ pathname: router.pathname, query })
|
<TokenPage />
|
||||||
},
|
) : view ? (
|
||||||
[router],
|
<AccountView view={view as ViewToShow} />
|
||||||
)
|
|
||||||
|
|
||||||
const handleCloseDailyPnlModal = () => {
|
|
||||||
setShowPnlHistory(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const [accountPnl, accountValue] = useMemo(() => {
|
|
||||||
if (!group || !mangoAccount) return [0, 0]
|
|
||||||
return [
|
|
||||||
toUiDecimalsForQuote(mangoAccount.getPnl(group).toNumber()),
|
|
||||||
toUiDecimalsForQuote(mangoAccount.getEquity(group).toNumber()),
|
|
||||||
]
|
|
||||||
}, [group, mangoAccount])
|
|
||||||
|
|
||||||
const pnlChangeToday = useMemo(() => {
|
|
||||||
if (!accountPnl || !rollingDailyData.length) return 0
|
|
||||||
const startHour = rollingDailyData.find((item) => {
|
|
||||||
const itemHour = new Date(item.time).getHours()
|
|
||||||
return itemHour === 0
|
|
||||||
})
|
|
||||||
const startDayPnl = startHour?.pnl
|
|
||||||
const pnlChangeToday = startDayPnl ? accountPnl - startDayPnl : 0
|
|
||||||
|
|
||||||
return pnlChangeToday
|
|
||||||
}, [accountPnl, rollingDailyData])
|
|
||||||
|
|
||||||
const latestAccountData = useMemo(() => {
|
|
||||||
if (!accountValue || !performanceData || !performanceData.length) return []
|
|
||||||
const latestDataItem = performanceData[performanceData.length - 1]
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
account_equity: accountValue,
|
|
||||||
time: dayjs(Date.now()).toISOString(),
|
|
||||||
borrow_interest_cumulative_usd:
|
|
||||||
latestDataItem.borrow_interest_cumulative_usd,
|
|
||||||
deposit_interest_cumulative_usd:
|
|
||||||
latestDataItem.deposit_interest_cumulative_usd,
|
|
||||||
pnl: accountPnl,
|
|
||||||
spot_value: latestDataItem.spot_value,
|
|
||||||
transfer_balance: latestDataItem.transfer_balance,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}, [accountPnl, accountValue, performanceData])
|
|
||||||
|
|
||||||
return !view ? (
|
|
||||||
<>
|
|
||||||
<div className="flex flex-col border-b-0 border-th-bkg-3 px-6 py-4 lg:flex-row lg:items-center lg:justify-between lg:border-b">
|
|
||||||
<div>
|
|
||||||
<div className="hide-scroll flex justify-center space-x-2 md:justify-start">
|
|
||||||
{TABS.map((tab) => (
|
|
||||||
<button
|
|
||||||
className={`rounded-md px-2.5 py-1.5 text-sm font-medium focus-visible:bg-th-bkg-3 focus-visible:text-th-fgd-1 ${
|
|
||||||
activeTab === tab
|
|
||||||
? 'bg-th-bkg-3 text-th-active md:hover:text-th-active'
|
|
||||||
: 'text-th-fgd-3 md:hover:text-th-fgd-2'
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab(tab)}
|
|
||||||
key={tab}
|
|
||||||
>
|
|
||||||
{t(tab)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="md:h-24">
|
|
||||||
{activeTab === 'account-value' ? (
|
|
||||||
<AccountValue
|
|
||||||
accountValue={accountValue}
|
|
||||||
latestAccountData={latestAccountData}
|
|
||||||
rollingDailyData={rollingDailyData}
|
|
||||||
handleViewChange={handleViewChange}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{activeTab === 'account:assets-liabilities' ? (
|
|
||||||
<AssetsLiabilities isMobile={isTablet} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-1 mt-6 lg:mt-0">
|
|
||||||
<AccountActions />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<AccountHeroStats
|
|
||||||
accountPnl={accountPnl}
|
|
||||||
accountValue={accountValue}
|
|
||||||
rollingDailyData={rollingDailyData}
|
|
||||||
setShowPnlHistory={setShowPnlHistory}
|
|
||||||
handleViewChange={handleViewChange}
|
|
||||||
/>
|
|
||||||
<AccountTabs />
|
|
||||||
{showPnlHistory ? (
|
|
||||||
<PnlHistoryModal
|
|
||||||
pnlChangeToday={pnlChangeToday}
|
|
||||||
isOpen={showPnlHistory}
|
|
||||||
onClose={handleCloseDailyPnlModal}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<AccountView
|
<AccountTabs />
|
||||||
view={view as ViewToShow}
|
|
||||||
latestAccountData={latestAccountData}
|
|
||||||
handleViewChange={handleViewChange}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AccountPage
|
export default AccountPage
|
||||||
|
|
||||||
const AccountView = ({
|
const AccountView = ({ view }: { view: ViewToShow }) => {
|
||||||
view,
|
|
||||||
handleViewChange,
|
|
||||||
latestAccountData,
|
|
||||||
}: {
|
|
||||||
view: ViewToShow
|
|
||||||
latestAccountData: PerformanceDataItem[]
|
|
||||||
handleViewChange: (view: ViewToShow) => void
|
|
||||||
}) => {
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { connected } = useWallet()
|
const { connected } = useWallet()
|
||||||
const { address } = router.query
|
const { address } = router.query
|
||||||
const { performanceData } = useAccountPerformanceData()
|
|
||||||
|
|
||||||
const handleHideChart = useCallback(() => {
|
const handleHideChart = useCallback(() => {
|
||||||
if (address && !connected) {
|
if (address && !connected) {
|
||||||
|
@ -183,40 +53,8 @@ const AccountView = ({
|
||||||
}, [address, router, connected])
|
}, [address, router, connected])
|
||||||
|
|
||||||
switch (view) {
|
switch (view) {
|
||||||
case 'account-value':
|
case 'account-stats':
|
||||||
return (
|
return <AccountStats hideView={handleHideChart} />
|
||||||
<AccountChart
|
|
||||||
chartName="account-value"
|
|
||||||
handleViewChange={handleViewChange}
|
|
||||||
data={performanceData?.concat(latestAccountData)}
|
|
||||||
hideChart={handleHideChart}
|
|
||||||
yKey="account_equity"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'pnl':
|
|
||||||
return (
|
|
||||||
<AccountChart
|
|
||||||
chartName="pnl"
|
|
||||||
handleViewChange={handleViewChange}
|
|
||||||
data={performanceData?.concat(latestAccountData)}
|
|
||||||
hideChart={handleHideChart}
|
|
||||||
yKey="pnl"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'cumulative-interest-value':
|
|
||||||
return (
|
|
||||||
<AccountChart
|
|
||||||
chartName="cumulative-interest-value"
|
|
||||||
handleViewChange={handleViewChange}
|
|
||||||
data={performanceData?.concat(latestAccountData)}
|
|
||||||
hideChart={handleHideChart}
|
|
||||||
yKey="interest_value"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'hourly-funding':
|
|
||||||
return <FundingChart hideChart={handleHideChart} />
|
|
||||||
case 'hourly-volume':
|
|
||||||
return <VolumeChart hideChart={handleHideChart} />
|
|
||||||
case 'health-contributions':
|
case 'health-contributions':
|
||||||
return <HealthContributions hideView={handleHideChart} />
|
return <HealthContributions hideView={handleHideChart} />
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
||||||
|
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
|
||||||
|
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { formatYAxis } from 'utils/formatting'
|
||||||
|
import FundingChart from './FundingChart'
|
||||||
|
import VolumeChart from './VolumeChart'
|
||||||
|
|
||||||
|
const AccountStats = ({ hideView }: { hideView: () => void }) => {
|
||||||
|
const { t } = useTranslation(['common', 'account'])
|
||||||
|
const { performanceData, loadingPerformanceData } =
|
||||||
|
useAccountPerformanceData()
|
||||||
|
const [pnlDaysToShow, setPnlDaysToShow] = useState('1')
|
||||||
|
const [interestDaysToShow, setInterestDaysToShow] = useState('1')
|
||||||
|
|
||||||
|
const chartData = useMemo(() => {
|
||||||
|
if (!performanceData || !performanceData.length) return []
|
||||||
|
const chartData = []
|
||||||
|
for (const item of performanceData) {
|
||||||
|
const interest =
|
||||||
|
item.borrow_interest_cumulative_usd * -1 +
|
||||||
|
item.deposit_interest_cumulative_usd
|
||||||
|
chartData.push({ ...item, interest_value: interest })
|
||||||
|
}
|
||||||
|
return chartData
|
||||||
|
}, [performanceData])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex h-14 items-center space-x-4 border-b border-th-bkg-3">
|
||||||
|
<button
|
||||||
|
className="flex h-14 w-14 flex-shrink-0 items-center justify-center border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
|
||||||
|
onClick={hideView}
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<h2 className="text-lg">{t('account:account-stats')}</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2">
|
||||||
|
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:border-r">
|
||||||
|
<DetailedAreaOrBarChart
|
||||||
|
data={chartData}
|
||||||
|
daysToShow={pnlDaysToShow}
|
||||||
|
setDaysToShow={setPnlDaysToShow}
|
||||||
|
loading={loadingPerformanceData}
|
||||||
|
heightClass="h-64"
|
||||||
|
loaderHeightClass="h-[350px]"
|
||||||
|
prefix="$"
|
||||||
|
tickFormat={(x) => `$${formatYAxis(x)}`}
|
||||||
|
title={t('pnl')}
|
||||||
|
xKey="time"
|
||||||
|
yKey="pnl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:pl-6">
|
||||||
|
<DetailedAreaOrBarChart
|
||||||
|
data={chartData}
|
||||||
|
daysToShow={interestDaysToShow}
|
||||||
|
setDaysToShow={setInterestDaysToShow}
|
||||||
|
loading={loadingPerformanceData}
|
||||||
|
heightClass="h-64"
|
||||||
|
loaderHeightClass="h-[350px]"
|
||||||
|
prefix="$"
|
||||||
|
tickFormat={(x) => `$${formatYAxis(x)}`}
|
||||||
|
title={t('cumulative-interest-value')}
|
||||||
|
xKey="time"
|
||||||
|
yKey="interest_value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 h-[400px] border-b border-th-bkg-3 md:col-span-1 md:border-r">
|
||||||
|
<FundingChart />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 h-[400px] border-b border-th-bkg-3 md:col-span-1">
|
||||||
|
<VolumeChart />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountStats
|
|
@ -8,14 +8,15 @@ import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
import PerpPositions from '@components/trade/PerpPositions'
|
import PerpPositions from '@components/trade/PerpPositions'
|
||||||
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
||||||
import OpenOrders from '@components/trade/OpenOrders'
|
|
||||||
import HistoryTabs from './HistoryTabs'
|
import HistoryTabs from './HistoryTabs'
|
||||||
import ManualRefresh from '@components/shared/ManualRefresh'
|
import ManualRefresh from '@components/shared/ManualRefresh'
|
||||||
import useMangoAccount from 'hooks/useMangoAccount'
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
import SwapTriggerOrders from '@components/swap/SwapTriggerOrders'
|
import SwapTriggerOrders from '@components/swap/SwapTriggerOrders'
|
||||||
|
import AccountOverview from './AccountOverview'
|
||||||
|
import AccountOrders from './AccountOrders'
|
||||||
|
|
||||||
const AccountTabs = () => {
|
const AccountTabs = () => {
|
||||||
const [activeTab, setActiveTab] = useState('balances')
|
const [activeTab, setActiveTab] = useState('overview')
|
||||||
const { mangoAccount } = useMangoAccount()
|
const { mangoAccount } = useMangoAccount()
|
||||||
const { isMobile, isTablet } = useViewport()
|
const { isMobile, isTablet } = useViewport()
|
||||||
const unsettledSpotBalances = useUnsettledSpotBalances()
|
const unsettledSpotBalances = useUnsettledSpotBalances()
|
||||||
|
@ -28,17 +29,21 @@ const AccountTabs = () => {
|
||||||
Object.values(unsettledSpotBalances).flat().length +
|
Object.values(unsettledSpotBalances).flat().length +
|
||||||
unsettledPerpPositions?.length
|
unsettledPerpPositions?.length
|
||||||
|
|
||||||
const tabs: [string, number][] = [
|
|
||||||
['balances', 0],
|
|
||||||
['trade:positions', openPerpPositions.length],
|
|
||||||
['trade:orders', Object.values(openOrders).flat().length],
|
|
||||||
['trade:unsettled', unsettledTradeCount],
|
|
||||||
['history', 0],
|
|
||||||
]
|
|
||||||
const stopOrdersCount =
|
const stopOrdersCount =
|
||||||
mangoAccount?.tokenConditionalSwaps.filter((tcs) => tcs.hasData)
|
mangoAccount?.tokenConditionalSwaps.filter((tcs) => tcs.hasData)
|
||||||
?.length || 0
|
?.length || 0
|
||||||
tabs.splice(3, 0, ['trade:trigger-orders', stopOrdersCount])
|
|
||||||
|
const tabs: [string, number][] = [
|
||||||
|
['overview', 0],
|
||||||
|
['balances', 0],
|
||||||
|
['trade:positions', openPerpPositions.length],
|
||||||
|
[
|
||||||
|
'trade:orders',
|
||||||
|
Object.values(openOrders).flat().length + stopOrdersCount,
|
||||||
|
],
|
||||||
|
['trade:unsettled', unsettledTradeCount],
|
||||||
|
['history', 0],
|
||||||
|
]
|
||||||
return tabs
|
return tabs
|
||||||
}, [
|
}, [
|
||||||
mangoAccount,
|
mangoAccount,
|
||||||
|
@ -64,7 +69,9 @@ const AccountTabs = () => {
|
||||||
size={isTablet ? 'large' : 'small'}
|
size={isTablet ? 'large' : 'small'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<TabContent activeTab={activeTab} />
|
<div className="flex min-h-[calc(100vh-140px)] flex-col">
|
||||||
|
<TabContent activeTab={activeTab} />
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -73,12 +80,14 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
||||||
const unsettledSpotBalances = useUnsettledSpotBalances()
|
const unsettledSpotBalances = useUnsettledSpotBalances()
|
||||||
const unsettledPerpPositions = useUnsettledPerpPositions()
|
const unsettledPerpPositions = useUnsettledPerpPositions()
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
|
case 'overview':
|
||||||
|
return <AccountOverview />
|
||||||
case 'balances':
|
case 'balances':
|
||||||
return <TokenList />
|
return <TokenList />
|
||||||
case 'trade:positions':
|
case 'trade:positions':
|
||||||
return <PerpPositions />
|
return <PerpPositions />
|
||||||
case 'trade:orders':
|
case 'trade:orders':
|
||||||
return <OpenOrders />
|
return <AccountOrders />
|
||||||
case 'trade:trigger-orders':
|
case 'trade:trigger-orders':
|
||||||
return <SwapTriggerOrders />
|
return <SwapTriggerOrders />
|
||||||
case 'trade:unsettled':
|
case 'trade:unsettled':
|
||||||
|
@ -91,7 +100,7 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
||||||
case 'history':
|
case 'history':
|
||||||
return <HistoryTabs />
|
return <HistoryTabs />
|
||||||
default:
|
default:
|
||||||
return <TokenList />
|
return <AccountOverview />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
import { formatCurrencyValue } from '../../utils/numbers'
|
import { formatCurrencyValue } from '../../utils/numbers'
|
||||||
import FlipNumbers from 'react-flip-numbers'
|
import FlipNumbers from 'react-flip-numbers'
|
||||||
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
|
|
||||||
import { COLORS } from '../../styles/colors'
|
|
||||||
import { IconButton } from '../shared/Button'
|
|
||||||
import { ArrowsPointingOutIcon } from '@heroicons/react/20/solid'
|
|
||||||
import { Transition } from '@headlessui/react'
|
|
||||||
import SheenLoader from '../shared/SheenLoader'
|
|
||||||
import Change from '../shared/Change'
|
import Change from '../shared/Change'
|
||||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||||
|
@ -14,35 +8,23 @@ import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettin
|
||||||
import useMangoGroup from 'hooks/useMangoGroup'
|
import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
import useMangoAccount from 'hooks/useMangoAccount'
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
import { PerformanceDataItem } from 'types'
|
import { PerformanceDataItem } from 'types'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { useViewport } from 'hooks/useViewport'
|
|
||||||
import { ViewToShow } from './AccountPage'
|
|
||||||
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
|
||||||
import useThemeWrapper from 'hooks/useThemeWrapper'
|
|
||||||
|
|
||||||
const AccountValue = ({
|
const AccountValue = ({
|
||||||
accountValue,
|
accountValue,
|
||||||
latestAccountData,
|
|
||||||
rollingDailyData,
|
rollingDailyData,
|
||||||
handleViewChange,
|
|
||||||
}: {
|
}: {
|
||||||
accountValue: number
|
accountValue: number
|
||||||
latestAccountData: PerformanceDataItem[]
|
|
||||||
rollingDailyData: PerformanceDataItem[]
|
rollingDailyData: PerformanceDataItem[]
|
||||||
handleViewChange: (view: ViewToShow) => void
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
const { theme } = useThemeWrapper()
|
|
||||||
const { group } = useMangoGroup()
|
const { group } = useMangoGroup()
|
||||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
const { mangoAccount } = useMangoAccount()
|
||||||
const [showExpandChart, setShowExpandChart] = useState<boolean>(false)
|
|
||||||
const [animationSettings] = useLocalStorageState(
|
const [animationSettings] = useLocalStorageState(
|
||||||
ANIMATION_SETTINGS_KEY,
|
ANIMATION_SETTINGS_KEY,
|
||||||
INITIAL_ANIMATION_SETTINGS,
|
INITIAL_ANIMATION_SETTINGS,
|
||||||
)
|
)
|
||||||
const { isTablet } = useViewport()
|
|
||||||
const { performanceLoading: loading } = useAccountPerformanceData()
|
|
||||||
|
|
||||||
const accountValueChange = useMemo(() => {
|
const accountValueChange = useMemo(() => {
|
||||||
if (!accountValue || !rollingDailyData.length) return 0
|
if (!accountValue || !rollingDailyData.length) return 0
|
||||||
|
@ -50,24 +32,10 @@ const AccountValue = ({
|
||||||
return accountValueChange
|
return accountValueChange
|
||||||
}, [accountValue, rollingDailyData])
|
}, [accountValue, rollingDailyData])
|
||||||
|
|
||||||
const onHoverMenu = (open: boolean, action: string) => {
|
|
||||||
if (
|
|
||||||
(!open && action === 'onMouseEnter') ||
|
|
||||||
(open && action === 'onMouseLeave')
|
|
||||||
) {
|
|
||||||
setShowExpandChart(!open)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleShowAccountValueChart = () => {
|
|
||||||
handleViewChange('account-value')
|
|
||||||
setShowExpandChart(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col md:flex-row md:items-end md:space-x-6">
|
<div className="flex flex-col md:flex-row md:items-end md:space-x-6">
|
||||||
<div className="mx-auto mt-4 md:mx-0">
|
<div className="mx-auto md:mx-0">
|
||||||
<div className="mb-2 flex justify-start font-display text-5xl text-th-fgd-1">
|
<div className="mb-2 flex justify-start font-display text-5xl text-th-fgd-1 xl:text-6xl">
|
||||||
{animationSettings['number-scroll'] ? (
|
{animationSettings['number-scroll'] ? (
|
||||||
group && mangoAccount ? (
|
group && mangoAccount ? (
|
||||||
<FlipNumbers
|
<FlipNumbers
|
||||||
|
@ -93,52 +61,10 @@ const AccountValue = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center space-x-1.5 md:justify-start">
|
<div className="flex items-center justify-center space-x-1.5 md:justify-start">
|
||||||
<Change change={accountValueChange} prefix="$" />
|
<Change change={accountValueChange} prefix="$" size="large" />
|
||||||
<p className="text-xs text-th-fgd-4">{t('rolling-change')}</p>
|
<p className="text-base text-th-fgd-4">{t('rolling-change')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!loading ? (
|
|
||||||
rollingDailyData.length ? (
|
|
||||||
<div
|
|
||||||
className="relative mt-4 flex h-40 items-end md:mt-0 md:h-20 md:w-52 lg:w-60"
|
|
||||||
onMouseEnter={() => onHoverMenu(showExpandChart, 'onMouseEnter')}
|
|
||||||
onMouseLeave={() => onHoverMenu(showExpandChart, 'onMouseLeave')}
|
|
||||||
>
|
|
||||||
<SimpleAreaChart
|
|
||||||
color={
|
|
||||||
accountValueChange >= 0 ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
|
||||||
}
|
|
||||||
data={rollingDailyData.concat(latestAccountData)}
|
|
||||||
name="accountValue"
|
|
||||||
xKey="time"
|
|
||||||
yKey="account_equity"
|
|
||||||
/>
|
|
||||||
<Transition
|
|
||||||
appear={true}
|
|
||||||
className="absolute bottom-2 right-2"
|
|
||||||
show={showExpandChart || isTablet}
|
|
||||||
enter="transition ease-in duration-300"
|
|
||||||
enterFrom="opacity-0 scale-75"
|
|
||||||
enterTo="opacity-100 scale-100"
|
|
||||||
leave="transition ease-out duration-200"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
className="text-th-fgd-3"
|
|
||||||
hideBg
|
|
||||||
onClick={() => handleShowAccountValueChart()}
|
|
||||||
>
|
|
||||||
<ArrowsPointingOutIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
) : null
|
|
||||||
) : mangoAccountAddress ? (
|
|
||||||
<SheenLoader className="mt-4 flex flex-1 md:mt-0">
|
|
||||||
<div className="h-40 w-full rounded-md bg-th-bkg-2 md:h-20 md:w-52 lg:w-60" />
|
|
||||||
</SheenLoader>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
|
||||||
|
import { COLORS } from '../../styles/colors'
|
||||||
|
import { IconButton } from '../shared/Button'
|
||||||
|
import { ArrowsPointingOutIcon } from '@heroicons/react/20/solid'
|
||||||
|
import { Transition } from '@headlessui/react'
|
||||||
|
import SheenLoader from '../shared/SheenLoader'
|
||||||
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
|
import { PerformanceDataItem } from 'types'
|
||||||
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
|
import { useViewport } from 'hooks/useViewport'
|
||||||
|
import { handleViewChange } from './AccountPage'
|
||||||
|
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
||||||
|
import useThemeWrapper from 'hooks/useThemeWrapper'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
|
const AccountValueChart = ({
|
||||||
|
accountValue,
|
||||||
|
latestAccountData,
|
||||||
|
rollingDailyData,
|
||||||
|
}: {
|
||||||
|
accountValue: number
|
||||||
|
latestAccountData: PerformanceDataItem[]
|
||||||
|
rollingDailyData: PerformanceDataItem[]
|
||||||
|
}) => {
|
||||||
|
const { theme } = useThemeWrapper()
|
||||||
|
const router = useRouter()
|
||||||
|
const { mangoAccountAddress } = useMangoAccount()
|
||||||
|
const [showExpandChart, setShowExpandChart] = useState<boolean>(false)
|
||||||
|
const { isTablet } = useViewport()
|
||||||
|
const { performanceLoading: loading } = useAccountPerformanceData()
|
||||||
|
|
||||||
|
const accountValueChange = useMemo(() => {
|
||||||
|
if (!accountValue || !rollingDailyData.length) return 0
|
||||||
|
const accountValueChange = accountValue - rollingDailyData[0].account_equity
|
||||||
|
return accountValueChange
|
||||||
|
}, [accountValue, rollingDailyData])
|
||||||
|
|
||||||
|
const onHoverMenu = (open: boolean, action: string) => {
|
||||||
|
if (
|
||||||
|
(!open && action === 'onMouseEnter') ||
|
||||||
|
(open && action === 'onMouseLeave')
|
||||||
|
) {
|
||||||
|
setShowExpandChart(!open)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleShowAccountValueChart = useCallback(() => {
|
||||||
|
handleViewChange('account-value', router)
|
||||||
|
setShowExpandChart(false)
|
||||||
|
}, [router])
|
||||||
|
|
||||||
|
return !loading ? (
|
||||||
|
rollingDailyData.length ? (
|
||||||
|
<div
|
||||||
|
className="relative mt-4 flex h-40 items-end md:mt-0 md:h-28 md:w-52 lg:w-56"
|
||||||
|
onMouseEnter={() => onHoverMenu(showExpandChart, 'onMouseEnter')}
|
||||||
|
onMouseLeave={() => onHoverMenu(showExpandChart, 'onMouseLeave')}
|
||||||
|
>
|
||||||
|
<SimpleAreaChart
|
||||||
|
color={
|
||||||
|
accountValueChange >= 0 ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
||||||
|
}
|
||||||
|
data={rollingDailyData.concat(latestAccountData)}
|
||||||
|
name="accountValue"
|
||||||
|
xKey="time"
|
||||||
|
yKey="account_equity"
|
||||||
|
/>
|
||||||
|
<Transition
|
||||||
|
appear={true}
|
||||||
|
className="absolute bottom-2 right-2"
|
||||||
|
show={showExpandChart || isTablet}
|
||||||
|
enter="transition ease-in duration-300"
|
||||||
|
enterFrom="opacity-0 scale-75"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="transition ease-out duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
className="text-th-fgd-3"
|
||||||
|
hideBg
|
||||||
|
onClick={() => handleShowAccountValueChart()}
|
||||||
|
>
|
||||||
|
<ArrowsPointingOutIcon className="h-5 w-5" />
|
||||||
|
</IconButton>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
) : mangoAccountAddress ? (
|
||||||
|
<SheenLoader className="mt-4 flex flex-1 md:mt-0">
|
||||||
|
<div className="h-40 w-full rounded-md bg-th-bkg-2 md:h-20 md:w-52 lg:w-60" />
|
||||||
|
</SheenLoader>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountValueChart
|
|
@ -447,12 +447,14 @@ const ActivityFeedTable = () => {
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : mangoAccountAddress || connected ? (
|
) : mangoAccountAddress || connected ? (
|
||||||
<div className="flex flex-col items-center p-8">
|
<div className="flex flex-1 flex-col items-center justify-center">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<div className="flex flex-col items-center p-8">
|
||||||
<p>{t('activity:no-activity')}</p>
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
<p>{t('activity:no-activity')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-8">
|
<div className="flex flex-1 flex-col items-center justify-center p-8">
|
||||||
<ConnectEmptyState text={t('activity:connect-activity')} />
|
<ConnectEmptyState text={t('activity:connect-activity')} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,8 +26,7 @@ import { formatDateAxis } from '@components/shared/DetailedAreaOrBarChart'
|
||||||
import { formatYAxis } from 'utils/formatting'
|
import { formatYAxis } from 'utils/formatting'
|
||||||
import ChartRangeButtons from '@components/shared/ChartRangeButtons'
|
import ChartRangeButtons from '@components/shared/ChartRangeButtons'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { IconButton } from '@components/shared/Button'
|
import { NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||||
import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
|
||||||
import { FadeInFadeOut } from '@components/shared/Transitions'
|
import { FadeInFadeOut } from '@components/shared/Transitions'
|
||||||
import ContentBox from '@components/shared/ContentBox'
|
import ContentBox from '@components/shared/ContentBox'
|
||||||
import SheenLoader from '@components/shared/SheenLoader'
|
import SheenLoader from '@components/shared/SheenLoader'
|
||||||
|
@ -69,7 +68,7 @@ const fetchHourlyFunding = async (mangoAccountPk: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
|
const FundingChart = () => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
const { mangoAccountAddress } = useMangoAccount()
|
const { mangoAccountAddress } = useMangoAccount()
|
||||||
const [daysToShow, setDaysToShow] = useState('30')
|
const [daysToShow, setDaysToShow] = useState('30')
|
||||||
|
@ -222,9 +221,6 @@ const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
|
||||||
<ContentBox className="px-6 pt-4" hideBorder hidePadding>
|
<ContentBox className="px-6 pt-4" hideBorder hidePadding>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-4 md:space-x-6">
|
<div className="flex items-center space-x-4 md:space-x-6">
|
||||||
<IconButton onClick={hideChart} size="medium">
|
|
||||||
<ArrowLeftIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
<h2 className="text-lg">{t('funding')}</h2>
|
<h2 className="text-lg">{t('funding')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<ChartRangeButtons
|
<ChartRangeButtons
|
||||||
|
@ -235,13 +231,11 @@ const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{loadingFunding || fetchingFunding ? (
|
{loadingFunding || fetchingFunding ? (
|
||||||
<SheenLoader className="mt-6 flex flex-1">
|
<SheenLoader className="mt-4 flex flex-1">
|
||||||
<div
|
<div className={`h-[318px] w-full rounded-lg bg-th-bkg-2`} />
|
||||||
className={`h-[calc(100vh-166px)] w-full rounded-lg bg-th-bkg-2`}
|
|
||||||
/>
|
|
||||||
</SheenLoader>
|
</SheenLoader>
|
||||||
) : filteredData.find((d) => Math.abs(d.total) > 0) ? (
|
) : filteredData.find((d) => Math.abs(d.total) > 0) ? (
|
||||||
<div className="-mx-6 mt-6 h-[calc(100vh-170px)]">
|
<div className="-mx-6 mt-6 h-80">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart data={filteredData}>
|
<BarChart data={filteredData}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -335,7 +329,7 @@ const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-6 flex flex-col items-center rounded-lg border border-th-bkg-3 p-8">
|
<div className="mt-4 flex h-80 flex-col items-center justify-center rounded-lg border border-th-bkg-3 p-8">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
<p>{t('account:no-data')}</p>
|
<p>{t('account:no-data')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,8 +18,7 @@ import { formatDateAxis } from '@components/shared/DetailedAreaOrBarChart'
|
||||||
import { formatYAxis } from 'utils/formatting'
|
import { formatYAxis } from 'utils/formatting'
|
||||||
import ChartRangeButtons from '@components/shared/ChartRangeButtons'
|
import ChartRangeButtons from '@components/shared/ChartRangeButtons'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { IconButton } from '@components/shared/Button'
|
import { NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||||
import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
|
||||||
import { FadeInFadeOut } from '@components/shared/Transitions'
|
import { FadeInFadeOut } from '@components/shared/Transitions'
|
||||||
import ContentBox from '@components/shared/ContentBox'
|
import ContentBox from '@components/shared/ContentBox'
|
||||||
import SheenLoader from '@components/shared/SheenLoader'
|
import SheenLoader from '@components/shared/SheenLoader'
|
||||||
|
@ -28,7 +27,7 @@ import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
import { DAILY_MILLISECONDS } from 'utils/constants'
|
import { DAILY_MILLISECONDS } from 'utils/constants'
|
||||||
import useThemeWrapper from 'hooks/useThemeWrapper'
|
import useThemeWrapper from 'hooks/useThemeWrapper'
|
||||||
|
|
||||||
const VolumeChart = ({ hideChart }: { hideChart: () => void }) => {
|
const VolumeChart = () => {
|
||||||
const { t } = useTranslation(['account', 'common', 'stats'])
|
const { t } = useTranslation(['account', 'common', 'stats'])
|
||||||
const { mangoAccountAddress } = useMangoAccount()
|
const { mangoAccountAddress } = useMangoAccount()
|
||||||
const { hourlyVolumeData: chartData, loadingHourlyVolume: loading } =
|
const { hourlyVolumeData: chartData, loadingHourlyVolume: loading } =
|
||||||
|
@ -147,9 +146,6 @@ const VolumeChart = ({ hideChart }: { hideChart: () => void }) => {
|
||||||
<ContentBox className="px-6 pt-4" hideBorder hidePadding>
|
<ContentBox className="px-6 pt-4" hideBorder hidePadding>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-4 md:space-x-6">
|
<div className="flex items-center space-x-4 md:space-x-6">
|
||||||
<IconButton onClick={hideChart} size="medium">
|
|
||||||
<ArrowLeftIcon className="h-5 w-5" />
|
|
||||||
</IconButton>
|
|
||||||
<h2 className="text-lg">{t('stats:volume')}</h2>
|
<h2 className="text-lg">{t('stats:volume')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<ChartRangeButtons
|
<ChartRangeButtons
|
||||||
|
@ -160,13 +156,11 @@ const VolumeChart = ({ hideChart }: { hideChart: () => void }) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{loading && mangoAccountAddress ? (
|
{loading && mangoAccountAddress ? (
|
||||||
<SheenLoader className="mt-6 flex flex-1">
|
<SheenLoader className="mt-4 flex flex-1">
|
||||||
<div
|
<div className={`h-[318px] w-full rounded-lg bg-th-bkg-2`} />
|
||||||
className={`h-[calc(100vh-166px)] w-full rounded-lg bg-th-bkg-2`}
|
|
||||||
/>
|
|
||||||
</SheenLoader>
|
</SheenLoader>
|
||||||
) : filteredData.find((d) => d.total_volume_usd > 0) ? (
|
) : filteredData.find((d) => d.total_volume_usd > 0) ? (
|
||||||
<div className="-mx-6 mt-6 h-[calc(100vh-170px)]">
|
<div className="-mx-6 mt-6 h-80">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart data={filteredData}>
|
<BarChart data={filteredData}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -238,7 +232,7 @@ const VolumeChart = ({ hideChart }: { hideChart: () => void }) => {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-6 flex flex-col items-center rounded-lg border border-th-bkg-3 p-8">
|
<div className="mt-4 flex h-80 flex-col items-center justify-center rounded-lg border border-th-bkg-3 p-8">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
<p>{t('account:no-data')}</p>
|
<p>{t('account:no-data')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import PerpMarketsTable from './PerpMarketsTable'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import ButtonGroup from '@components/forms/ButtonGroup'
|
||||||
|
import RecentGainersLosers from './RecentGainersLosers'
|
||||||
|
import Spot from './Spot'
|
||||||
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
|
const TABS = ['spot', 'perp']
|
||||||
|
|
||||||
|
const Explore = () => {
|
||||||
|
const { t } = useTranslation(['common'])
|
||||||
|
const perpStats = mangoStore((s) => s.perpStats.data)
|
||||||
|
const [activeTab, setActiveTab] = useState('spot')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!perpStats || !perpStats.length) {
|
||||||
|
const actions = mangoStore.getState().actions
|
||||||
|
actions.fetchPerpStats()
|
||||||
|
}
|
||||||
|
}, [perpStats])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="px-4 pt-8 md:px-6">
|
||||||
|
<h2 className="mb-4 text-center text-lg md:text-left xl:text-xl">
|
||||||
|
{t('explore')}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<RecentGainersLosers />
|
||||||
|
<div className="mb-6 flex flex-col px-4 pt-8 md:mb-0 md:flex-row md:items-center md:space-x-4 md:px-6">
|
||||||
|
<h2 className="mb-3 text-center text-lg md:mb-0 md:text-left xl:text-xl">
|
||||||
|
{t('markets')}
|
||||||
|
</h2>
|
||||||
|
<div className="mx-auto max-w-[112px]">
|
||||||
|
<ButtonGroup
|
||||||
|
activeValue={activeTab}
|
||||||
|
onChange={(t) => setActiveTab(t)}
|
||||||
|
names={TABS.map((tab) => t(tab))}
|
||||||
|
values={TABS}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TabContent activeTab={activeTab} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Explore
|
||||||
|
|
||||||
|
const TabContent = ({ activeTab }: { activeTab: string }) => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'spot':
|
||||||
|
return <Spot />
|
||||||
|
case 'perp':
|
||||||
|
return (
|
||||||
|
<div className="mt-6 border-t border-th-bkg-3">
|
||||||
|
<PerpMarketsTable />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return <Spot />
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,92 +0,0 @@
|
||||||
import useListedMarketsWithMarketData from 'hooks/useListedMarketsWithMarketData'
|
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
|
||||||
import PerpMarketsTable from './PerpMarketsTable'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import TokenPage from '@components/token/TokenPage'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
|
||||||
import Spot from './Spot'
|
|
||||||
import mangoStore from '@store/mangoStore'
|
|
||||||
import PerpStatsPage from '@components/stats/perps/PerpStatsPage'
|
|
||||||
import useBanks from 'hooks/useBanks'
|
|
||||||
dayjs.extend(relativeTime)
|
|
||||||
|
|
||||||
const ExplorePage = () => {
|
|
||||||
const { t } = useTranslation(['common'])
|
|
||||||
const router = useRouter()
|
|
||||||
const { token } = router.query
|
|
||||||
const { market } = router.query
|
|
||||||
const perpStats = mangoStore((s) => s.perpStats.data)
|
|
||||||
const [activeTab, setActiveTab] = useState('spot')
|
|
||||||
const { perpMarketsWithData } = useListedMarketsWithMarketData()
|
|
||||||
const { banks } = useBanks()
|
|
||||||
|
|
||||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
|
||||||
const tabs: [string, number][] = [
|
|
||||||
['spot', banks.length],
|
|
||||||
['perp', perpMarketsWithData.length],
|
|
||||||
// ['accounts', 0],
|
|
||||||
]
|
|
||||||
return tabs
|
|
||||||
}, [banks, perpMarketsWithData])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!perpStats || !perpStats.length) {
|
|
||||||
const actions = mangoStore.getState().actions
|
|
||||||
actions.fetchPerpStats()
|
|
||||||
}
|
|
||||||
}, [perpStats])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="pb-16 md:pb-[27px]">
|
|
||||||
{market ? (
|
|
||||||
<PerpStatsPage />
|
|
||||||
) : token ? (
|
|
||||||
<TokenPage />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<div className="flex flex-col items-center py-8">
|
|
||||||
<h1 className="mb-4">{t('explore')}</h1>
|
|
||||||
<div className="flex justify-center">
|
|
||||||
{tabsWithCount.map((tab) => (
|
|
||||||
<button
|
|
||||||
className={`flex items-center space-x-2 border-y-2 border-r-2 border-th-bkg-3 px-6 py-3 font-display text-base first:rounded-l-lg first:border-l-2 last:rounded-r-lg focus:outline-none ${
|
|
||||||
activeTab === tab[0] ? 'bg-th-bkg-2 text-th-active' : ''
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab(tab[0])}
|
|
||||||
key={tab[0]}
|
|
||||||
>
|
|
||||||
<span>{t(tab[0])}</span>
|
|
||||||
<div className="rounded-md bg-th-bkg-3 px-1 py-0.5 font-body text-xs font-medium text-th-fgd-3">
|
|
||||||
<span>{tab[1]}</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<TabContent activeTab={activeTab} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ExplorePage
|
|
||||||
|
|
||||||
const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|
||||||
switch (activeTab) {
|
|
||||||
case 'spot':
|
|
||||||
return <Spot />
|
|
||||||
case 'perp':
|
|
||||||
return (
|
|
||||||
<div className="border-t border-th-bkg-3">
|
|
||||||
<PerpMarketsTable />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return <Spot />
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import MarketLogos from '@components/trade/MarketLogos'
|
import MarketLogos from '@components/trade/MarketLogos'
|
||||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||||
|
@ -10,7 +9,7 @@ import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
|
||||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||||
import { getDecimalCount, numberCompacter } from 'utils/numbers'
|
import { getDecimalCount, numberCompacter } from 'utils/numbers'
|
||||||
import Tooltip from '@components/shared/Tooltip'
|
import Tooltip from '@components/shared/Tooltip'
|
||||||
import { NextRouter, useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
|
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
|
||||||
import { Disclosure, Transition } from '@headlessui/react'
|
import { Disclosure, Transition } from '@headlessui/react'
|
||||||
import { LinkButton } from '@components/shared/Button'
|
import { LinkButton } from '@components/shared/Button'
|
||||||
|
@ -25,14 +24,7 @@ import { useViewport } from 'hooks/useViewport'
|
||||||
import { breakpoints } from 'utils/theme'
|
import { breakpoints } from 'utils/theme'
|
||||||
import ContentBox from '@components/shared/ContentBox'
|
import ContentBox from '@components/shared/ContentBox'
|
||||||
import { COLORS } from 'styles/colors'
|
import { COLORS } from 'styles/colors'
|
||||||
|
import { goToPerpMarketDetails } from '@components/stats/perps/PerpMarketDetailsTable'
|
||||||
export const goToPerpMarketDetails = (
|
|
||||||
market: PerpMarket,
|
|
||||||
router: NextRouter,
|
|
||||||
) => {
|
|
||||||
const query = { ...router.query, ['market']: market.name }
|
|
||||||
router.push({ pathname: router.pathname, query })
|
|
||||||
}
|
|
||||||
|
|
||||||
const PerpMarketsTable = () => {
|
const PerpMarketsTable = () => {
|
||||||
const { t } = useTranslation(['common', 'trade'])
|
const { t } = useTranslation(['common', 'trade'])
|
||||||
|
@ -106,7 +98,7 @@ const PerpMarketsTable = () => {
|
||||||
<TrBody
|
<TrBody
|
||||||
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
||||||
key={market.publicKey.toString()}
|
key={market.publicKey.toString()}
|
||||||
onClick={() => goToPerpMarketDetails(market, router)}
|
onClick={() => goToPerpMarketDetails(market.name, router)}
|
||||||
>
|
>
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
@ -436,7 +428,7 @@ const MobilePerpMarketItem = ({
|
||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<LinkButton
|
<LinkButton
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
onClick={() => goToPerpMarketDetails(market, router)}
|
onClick={() => goToPerpMarketDetails(market.name, router)}
|
||||||
>
|
>
|
||||||
{t('token:token-stats', { token: market.name })}
|
{t('token:token-stats', { token: market.name })}
|
||||||
<ChevronRightIcon className="ml-2 h-5 w-5" />
|
<ChevronRightIcon className="ml-2 h-5 w-5" />
|
||||||
|
|
|
@ -0,0 +1,329 @@
|
||||||
|
import Change from '@components/shared/Change'
|
||||||
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||||
|
import TokenLogo from '@components/shared/TokenLogo'
|
||||||
|
import useListedMarketsWithMarketData, {
|
||||||
|
SerumMarketWithMarketData,
|
||||||
|
} from 'hooks/useListedMarketsWithMarketData'
|
||||||
|
import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { goToTokenPage } from '@components/stats/tokens/TokenOverviewTable'
|
||||||
|
import {
|
||||||
|
BoltIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
FaceFrownIcon,
|
||||||
|
RocketLaunchIcon,
|
||||||
|
} from '@heroicons/react/20/solid'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
|
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import useBanks from 'hooks/useBanks'
|
||||||
|
import SheenLoader from '@components/shared/SheenLoader'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import { goToPerpMarketDetails } from '@components/stats/perps/PerpMarketDetailsTable'
|
||||||
|
import MarketLogos from '@components/trade/MarketLogos'
|
||||||
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
|
export type BankWithMarketData = {
|
||||||
|
bank: Bank
|
||||||
|
market: SerumMarketWithMarketData | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const CALLOUT_TILES_WRAPPER_CLASSES =
|
||||||
|
'col-span-12 flex flex-col rounded-lg border border-th-bkg-3 p-6 lg:col-span-4'
|
||||||
|
|
||||||
|
const RecentGainersLosers = () => {
|
||||||
|
const { t } = useTranslation(['common', 'explore', 'trade'])
|
||||||
|
const router = useRouter()
|
||||||
|
const { group } = useMangoGroup()
|
||||||
|
const { banks } = useBanks()
|
||||||
|
const {
|
||||||
|
serumMarketsWithData,
|
||||||
|
perpMarketsWithData,
|
||||||
|
isLoading: loadingSerumMarkets,
|
||||||
|
} = useListedMarketsWithMarketData()
|
||||||
|
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
||||||
|
|
||||||
|
const banksWithMarketData = useMemo(() => {
|
||||||
|
if (!banks.length || !group || !serumMarketsWithData.length) return []
|
||||||
|
const banksWithMarketData = []
|
||||||
|
const usdcQuoteMarkets = serumMarketsWithData.filter(
|
||||||
|
(market) => market.quoteTokenIndex === 0,
|
||||||
|
)
|
||||||
|
for (const bank of banks) {
|
||||||
|
const market = usdcQuoteMarkets.find(
|
||||||
|
(market) => market.baseTokenIndex === bank.tokenIndex,
|
||||||
|
)
|
||||||
|
if (market) {
|
||||||
|
banksWithMarketData.push({ bank, market })
|
||||||
|
} else {
|
||||||
|
banksWithMarketData.push({ bank, market: undefined })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return banksWithMarketData
|
||||||
|
}, [banks, group, serumMarketsWithData])
|
||||||
|
|
||||||
|
const newlyListedMintInfo = useMemo(() => {
|
||||||
|
if (!group) return []
|
||||||
|
const mintInfos = Array.from(group.mintInfosMapByTokenIndex).map(
|
||||||
|
([, mintInfo]) => mintInfo,
|
||||||
|
)
|
||||||
|
const sortByRegistrationTime = mintInfos
|
||||||
|
.sort((a, b) => {
|
||||||
|
return b.registrationTime.toNumber() - a.registrationTime.toNumber()
|
||||||
|
})
|
||||||
|
.slice(0, 3)
|
||||||
|
return sortByRegistrationTime
|
||||||
|
}, [group])
|
||||||
|
|
||||||
|
const newlyListed = useMemo(() => {
|
||||||
|
if (!newlyListedMintInfo.length || !banks.length) return []
|
||||||
|
const newlyListed = []
|
||||||
|
for (const listing of newlyListedMintInfo) {
|
||||||
|
const bank = banks.find((bank) => bank.tokenIndex === listing.tokenIndex)
|
||||||
|
if (bank) {
|
||||||
|
newlyListed.push(bank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newlyListed
|
||||||
|
}, [newlyListedMintInfo, banks])
|
||||||
|
|
||||||
|
const [gainers, losers] = useMemo(() => {
|
||||||
|
const group = mangoStore.getState().group
|
||||||
|
if (!banksWithMarketData.length || !perpMarketsWithData || !group)
|
||||||
|
return [[], []]
|
||||||
|
const tradeableAssets = []
|
||||||
|
for (const token of banksWithMarketData) {
|
||||||
|
const volume = token.market?.marketData?.quote_volume_24h || 0
|
||||||
|
if (token.market?.quoteTokenIndex === 0 && volume > 0) {
|
||||||
|
const pastPrice = token.market?.marketData?.price_24h
|
||||||
|
const change = pastPrice
|
||||||
|
? ((token.bank.uiPrice - pastPrice) / pastPrice) * 100
|
||||||
|
: 0
|
||||||
|
tradeableAssets.push({ bank: token.bank, change, type: 'spot' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const market of perpMarketsWithData) {
|
||||||
|
const volume = market.marketData?.quote_volume_24h || 0
|
||||||
|
if (volume > 0) {
|
||||||
|
const pastPrice = market.marketData?.price_24h
|
||||||
|
const change = pastPrice
|
||||||
|
? ((market.uiPrice - pastPrice) / pastPrice) * 100
|
||||||
|
: 0
|
||||||
|
const perpMarket = group.getPerpMarketByMarketIndex(
|
||||||
|
market.perpMarketIndex,
|
||||||
|
)
|
||||||
|
tradeableAssets.push({ market: perpMarket, change, type: 'perp' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sortedAssets = tradeableAssets.sort((a, b) => b.change - a.change)
|
||||||
|
const gainers = sortedAssets.slice(0, 3).filter((item) => {
|
||||||
|
return item.change > 0
|
||||||
|
})
|
||||||
|
const losers = sortedAssets
|
||||||
|
.slice(-3)
|
||||||
|
.filter((item) => {
|
||||||
|
return item.change < 0
|
||||||
|
})
|
||||||
|
.reverse()
|
||||||
|
return [gainers, losers]
|
||||||
|
}, [banksWithMarketData, perpMarketsWithData])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="grid grid-cols-12 gap-4 px-4 md:px-6">
|
||||||
|
<div className={CALLOUT_TILES_WRAPPER_CLASSES}>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<BoltIcon className="h-5 w-5" />
|
||||||
|
<h2 className="text-base">{t('explore:recently-listed')}</h2>
|
||||||
|
</div>
|
||||||
|
<Link href="/governance/list" shallow>
|
||||||
|
<span className="default-transition font-bold text-th-active md:hover:text-th-active-dark">
|
||||||
|
{t('governance:list-token')}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
{groupLoaded ? (
|
||||||
|
<div className="border-t border-th-bkg-3">
|
||||||
|
{newlyListed.map((token) => {
|
||||||
|
const mintInfo = newlyListedMintInfo.find(
|
||||||
|
(info) => info.tokenIndex === token.tokenIndex,
|
||||||
|
)
|
||||||
|
let timeSinceListing = ''
|
||||||
|
if (mintInfo) {
|
||||||
|
timeSinceListing = dayjs().to(
|
||||||
|
mintInfo.registrationTime.toNumber() * 1000,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="default-transition flex h-16 cursor-pointer items-center justify-between border-b border-th-bkg-3 px-4 md:hover:bg-th-bkg-2"
|
||||||
|
key={token.tokenIndex}
|
||||||
|
onClick={() =>
|
||||||
|
goToTokenPage(token.name.split(' ')[0], router)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<TokenLogo bank={token} />
|
||||||
|
<p className="ml-3 font-body text-th-fgd-2">
|
||||||
|
{token.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="mr-3">
|
||||||
|
<span className="text-th-fgd-3">
|
||||||
|
{timeSinceListing}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<CalloutTilesLoader />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={CALLOUT_TILES_WRAPPER_CLASSES}>
|
||||||
|
<div className="mb-4 flex items-center space-x-2">
|
||||||
|
<RocketLaunchIcon className="h-5 w-5" />
|
||||||
|
<h2 className="text-base">{t('explore:gainers')}</h2>
|
||||||
|
</div>
|
||||||
|
{!loadingSerumMarkets && groupLoaded ? (
|
||||||
|
<div className="h-full border-t border-th-bkg-3">
|
||||||
|
{gainers.length ? (
|
||||||
|
gainers.map((gainer) => {
|
||||||
|
const bank = gainer?.bank
|
||||||
|
|
||||||
|
const onClick = bank
|
||||||
|
? () => goToTokenPage(bank.name.split(' ')[0], router)
|
||||||
|
: () => goToPerpMarketDetails(gainer?.market?.name, router)
|
||||||
|
|
||||||
|
const price = bank
|
||||||
|
? bank.uiPrice
|
||||||
|
: gainer?.market?.uiPrice || 0
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="default-transition flex h-16 cursor-pointer items-center justify-between border-b border-th-bkg-3 px-4 md:hover:bg-th-bkg-2"
|
||||||
|
key={
|
||||||
|
`${bank?.tokenIndex}${bank?.name}` ||
|
||||||
|
`${gainer?.market?.perpMarketIndex}${gainer?.market?.name}`
|
||||||
|
}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{bank ? (
|
||||||
|
<div className="mr-3">
|
||||||
|
<TokenLogo bank={bank} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<MarketLogos market={gainer?.market} size="large" />
|
||||||
|
)}
|
||||||
|
<p className="font-body text-th-fgd-2">
|
||||||
|
{bank?.name || gainer?.market?.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="mr-3 flex flex-col items-end">
|
||||||
|
<span className="font-mono">
|
||||||
|
<FormatNumericValue value={price} isUsd />
|
||||||
|
</span>
|
||||||
|
<Change change={gainer.change} suffix="%" />
|
||||||
|
</div>
|
||||||
|
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full flex-col items-center justify-center">
|
||||||
|
<FaceFrownIcon className="mb-1.5 h-5 w-5" />
|
||||||
|
<p>{t('explore:no-gainers')}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<CalloutTilesLoader />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={CALLOUT_TILES_WRAPPER_CLASSES}>
|
||||||
|
<div className="mb-4 flex items-center space-x-2">
|
||||||
|
<FaceFrownIcon className="h-5 w-5" />
|
||||||
|
<h2 className="text-base">{t('explore:losers')}</h2>
|
||||||
|
</div>
|
||||||
|
{!loadingSerumMarkets && groupLoaded ? (
|
||||||
|
<div className="h-full border-t border-th-bkg-3">
|
||||||
|
{losers.length ? (
|
||||||
|
losers.map((loser) => {
|
||||||
|
const bank = loser?.bank
|
||||||
|
|
||||||
|
const onClick = bank
|
||||||
|
? () => goToTokenPage(bank.name.split(' ')[0], router)
|
||||||
|
: () => goToPerpMarketDetails(loser?.market?.name, router)
|
||||||
|
|
||||||
|
const price = bank
|
||||||
|
? bank.uiPrice
|
||||||
|
: loser?.market?.uiPrice || 0
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="default-transition flex h-16 cursor-pointer items-center justify-between border-b border-th-bkg-3 px-4 md:hover:bg-th-bkg-2"
|
||||||
|
key={bank?.tokenIndex || loser?.market?.perpMarketIndex}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{bank ? (
|
||||||
|
<TokenLogo bank={bank} />
|
||||||
|
) : (
|
||||||
|
<MarketLogos market={loser?.market} />
|
||||||
|
)}
|
||||||
|
<p className="ml-3 font-body text-th-fgd-2">
|
||||||
|
{bank?.name || loser?.market?.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="mr-3 flex flex-col items-end">
|
||||||
|
<span className="font-mono">
|
||||||
|
<FormatNumericValue value={price} isUsd />
|
||||||
|
</span>
|
||||||
|
<Change change={loser.change} suffix="%" />
|
||||||
|
</div>
|
||||||
|
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full flex-col items-center justify-center">
|
||||||
|
<RocketLaunchIcon className="mb-1.5 h-5 w-5" />
|
||||||
|
<p>{t('explore:no-losers')}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<CalloutTilesLoader />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecentGainersLosers
|
||||||
|
|
||||||
|
const CalloutTilesLoader = () => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-1">
|
||||||
|
{[...Array(3)].map((x, i) => (
|
||||||
|
<SheenLoader className="flex flex-1" key={i}>
|
||||||
|
<div className="h-16 w-full rounded-md bg-th-bkg-2" />
|
||||||
|
</SheenLoader>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,46 +1,28 @@
|
||||||
import Change from '@components/shared/Change'
|
|
||||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
|
||||||
import TokenLogo from '@components/shared/TokenLogo'
|
|
||||||
import useListedMarketsWithMarketData, {
|
import useListedMarketsWithMarketData, {
|
||||||
SerumMarketWithMarketData,
|
SerumMarketWithMarketData,
|
||||||
} from 'hooks/useListedMarketsWithMarketData'
|
} from 'hooks/useListedMarketsWithMarketData'
|
||||||
import useMangoGroup from 'hooks/useMangoGroup'
|
import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import { ChangeEvent, useMemo, useState } from 'react'
|
import { ChangeEvent, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import SpotTable from './SpotTable'
|
import SpotTable from './SpotTable'
|
||||||
import { goToTokenPage } from '@components/stats/tokens/TokenOverviewTable'
|
|
||||||
import {
|
import {
|
||||||
BoltIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
FaceFrownIcon,
|
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
RocketLaunchIcon,
|
|
||||||
Squares2X2Icon,
|
Squares2X2Icon,
|
||||||
TableCellsIcon,
|
TableCellsIcon,
|
||||||
} from '@heroicons/react/20/solid'
|
} from '@heroicons/react/20/solid'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
|
||||||
import { AllowedKeys } from 'utils/markets'
|
import { AllowedKeys } from 'utils/markets'
|
||||||
import ButtonGroup from '@components/forms/ButtonGroup'
|
import ButtonGroup from '@components/forms/ButtonGroup'
|
||||||
import SpotCards from './SpotCards'
|
import SpotCards from './SpotCards'
|
||||||
import Input from '@components/forms/Input'
|
import Input from '@components/forms/Input'
|
||||||
import EmptyState from '@components/nftMarket/EmptyState'
|
import EmptyState from '@components/nftMarket/EmptyState'
|
||||||
import { Bank } from '@blockworks-foundation/mango-v4'
|
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||||
import Link from 'next/link'
|
|
||||||
import useBanks from 'hooks/useBanks'
|
import useBanks from 'hooks/useBanks'
|
||||||
import SheenLoader from '@components/shared/SheenLoader'
|
|
||||||
import mangoStore from '@store/mangoStore'
|
|
||||||
dayjs.extend(relativeTime)
|
|
||||||
|
|
||||||
export type BankWithMarketData = {
|
export type BankWithMarketData = {
|
||||||
bank: Bank
|
bank: Bank
|
||||||
market: SerumMarketWithMarketData | undefined
|
market: SerumMarketWithMarketData | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const CALLOUT_TILES_WRAPPER_CLASSES =
|
|
||||||
'col-span-12 flex flex-col rounded-lg border border-th-bkg-3 p-6 lg:col-span-4'
|
|
||||||
|
|
||||||
const generateSearchTerm = (item: BankWithMarketData, searchValue: string) => {
|
const generateSearchTerm = (item: BankWithMarketData, searchValue: string) => {
|
||||||
const normalizedSearchValue = searchValue.toLowerCase()
|
const normalizedSearchValue = searchValue.toLowerCase()
|
||||||
const value = item.bank.name.toLowerCase()
|
const value = item.bank.name.toLowerCase()
|
||||||
|
@ -107,12 +89,9 @@ const sortTokens = (tokens: BankWithMarketData[], sortByKey: AllowedKeys) => {
|
||||||
|
|
||||||
const Spot = () => {
|
const Spot = () => {
|
||||||
const { t } = useTranslation(['common', 'explore', 'trade'])
|
const { t } = useTranslation(['common', 'explore', 'trade'])
|
||||||
const router = useRouter()
|
|
||||||
const { group } = useMangoGroup()
|
const { group } = useMangoGroup()
|
||||||
const { banks } = useBanks()
|
const { banks } = useBanks()
|
||||||
const { serumMarketsWithData, isLoading: loadingSerumMarkets } =
|
const { serumMarketsWithData } = useListedMarketsWithMarketData()
|
||||||
useListedMarketsWithMarketData()
|
|
||||||
const groupLoaded = mangoStore((s) => s.groupLoaded)
|
|
||||||
const [sortByKey, setSortByKey] = useState<AllowedKeys>('quote_volume_24h')
|
const [sortByKey, setSortByKey] = useState<AllowedKeys>('quote_volume_24h')
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [showTableView, setShowTableView] = useState(true)
|
const [showTableView, setShowTableView] = useState(true)
|
||||||
|
@ -136,81 +115,6 @@ const Spot = () => {
|
||||||
return banksWithMarketData
|
return banksWithMarketData
|
||||||
}, [banks, group, serumMarketsWithData])
|
}, [banks, group, serumMarketsWithData])
|
||||||
|
|
||||||
const newlyListedMintInfo = useMemo(() => {
|
|
||||||
if (!group) return []
|
|
||||||
const mintInfos = Array.from(group.mintInfosMapByTokenIndex).map(
|
|
||||||
([, mintInfo]) => mintInfo,
|
|
||||||
)
|
|
||||||
const sortByRegistrationTime = mintInfos
|
|
||||||
.sort((a, b) => {
|
|
||||||
return b.registrationTime.toNumber() - a.registrationTime.toNumber()
|
|
||||||
})
|
|
||||||
.slice(0, 3)
|
|
||||||
return sortByRegistrationTime
|
|
||||||
}, [group])
|
|
||||||
|
|
||||||
const newlyListed = useMemo(() => {
|
|
||||||
if (!newlyListedMintInfo.length || !banks.length) return []
|
|
||||||
const newlyListed = []
|
|
||||||
for (const listing of newlyListedMintInfo) {
|
|
||||||
const bank = banks.find((bank) => bank.tokenIndex === listing.tokenIndex)
|
|
||||||
if (bank) {
|
|
||||||
newlyListed.push(bank)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newlyListed
|
|
||||||
}, [newlyListedMintInfo, banks])
|
|
||||||
|
|
||||||
const [gainers, losers] = useMemo(() => {
|
|
||||||
if (!banksWithMarketData.length) return [[], []]
|
|
||||||
const sortByChange = banksWithMarketData
|
|
||||||
.filter((token) => {
|
|
||||||
const volume = token.market?.marketData?.quote_volume_24h || 0
|
|
||||||
return token.market?.quoteTokenIndex === 0 && volume > 0
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
const aPrice = a?.bank?.uiPrice || 0
|
|
||||||
const bPrice = b?.bank?.uiPrice || 0
|
|
||||||
const aPastPrice = a.market?.marketData?.price_24h
|
|
||||||
const bPastPrice = b.market?.marketData?.price_24h
|
|
||||||
const aValue = aPastPrice
|
|
||||||
? ((aPrice - aPastPrice) / aPastPrice) * 100
|
|
||||||
: undefined
|
|
||||||
const bValue = bPastPrice
|
|
||||||
? ((bPrice - bPastPrice) / bPastPrice) * 100
|
|
||||||
: undefined
|
|
||||||
if (typeof aValue === 'undefined' && typeof bValue === 'undefined') {
|
|
||||||
return 0 // Consider them equal
|
|
||||||
}
|
|
||||||
if (typeof aValue === 'undefined') {
|
|
||||||
return 1 // b should come before a
|
|
||||||
}
|
|
||||||
if (typeof bValue === 'undefined') {
|
|
||||||
return -1 // a should come before b
|
|
||||||
}
|
|
||||||
|
|
||||||
return bValue - aValue
|
|
||||||
})
|
|
||||||
const gainers = sortByChange.slice(0, 3).filter((item) => {
|
|
||||||
const pastPrice = item.market?.marketData?.price_24h
|
|
||||||
const change = pastPrice
|
|
||||||
? ((item.bank.uiPrice - pastPrice) / pastPrice) * 100
|
|
||||||
: 0
|
|
||||||
return change > 0
|
|
||||||
})
|
|
||||||
const losers = sortByChange
|
|
||||||
.slice(-3)
|
|
||||||
.reverse()
|
|
||||||
.filter((item) => {
|
|
||||||
const pastPrice = item.market?.marketData?.price_24h
|
|
||||||
const change = pastPrice
|
|
||||||
? ((item.bank.uiPrice - pastPrice) / pastPrice) * 100
|
|
||||||
: 0
|
|
||||||
return change < 0
|
|
||||||
})
|
|
||||||
return [gainers, losers]
|
|
||||||
}, [banksWithMarketData])
|
|
||||||
|
|
||||||
const sortedTokensToShow = useMemo(() => {
|
const sortedTokensToShow = useMemo(() => {
|
||||||
if (!banksWithMarketData.length) return []
|
if (!banksWithMarketData.length) return []
|
||||||
return search
|
return search
|
||||||
|
@ -223,239 +127,63 @@ const Spot = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="md:-mt-10">
|
||||||
<div className="grid grid-cols-12 gap-4 px-4 pb-8 md:px-6 2xl:px-12">
|
<div className="flex flex-col px-4 sm:flex-row sm:items-center sm:justify-end md:px-6 2xl:px-12">
|
||||||
<div className={CALLOUT_TILES_WRAPPER_CLASSES}>
|
<div className="flex w-full flex-col sm:flex-row sm:space-x-3 md:w-auto">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="relative mb-3 w-full sm:mb-0 md:w-40">
|
||||||
<div className="flex items-center space-x-2">
|
<Input
|
||||||
<BoltIcon className="h-5 w-5" />
|
heightClass="h-10 pl-8"
|
||||||
<h2 className="text-base">{t('explore:recently-listed')}</h2>
|
type="text"
|
||||||
</div>
|
value={search}
|
||||||
<Link href="/governance/list" shallow>
|
onChange={handleUpdateSearch}
|
||||||
<span className="default-transition font-bold text-th-active md:hover:text-th-active-dark">
|
/>
|
||||||
{t('governance:list-token')}
|
<MagnifyingGlassIcon className="absolute left-2 top-3 h-4 w-4" />
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
{groupLoaded ? (
|
<div className="flex space-x-3">
|
||||||
<div className="border-t border-th-bkg-3">
|
<div className="w-full sm:w-48">
|
||||||
{newlyListed.map((token) => {
|
<ButtonGroup
|
||||||
const mintInfo = newlyListedMintInfo.find(
|
activeValue={sortByKey}
|
||||||
(info) => info.tokenIndex === token.tokenIndex,
|
onChange={(v) => setSortByKey(v)}
|
||||||
)
|
names={[t('trade:24h-volume'), t('rolling-change')]}
|
||||||
let timeSinceListing = ''
|
values={['quote_volume_24h', 'change_24h']}
|
||||||
if (mintInfo) {
|
|
||||||
timeSinceListing = dayjs().to(
|
|
||||||
mintInfo.registrationTime.toNumber() * 1000,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="default-transition flex h-16 cursor-pointer items-center justify-between border-b border-th-bkg-3 px-4 md:hover:bg-th-bkg-2"
|
|
||||||
key={token.tokenIndex}
|
|
||||||
onClick={() =>
|
|
||||||
goToTokenPage(token.name.split(' ')[0], router)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<TokenLogo bank={token} />
|
|
||||||
<p className="ml-3 font-body text-th-fgd-2">
|
|
||||||
{token.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="mr-3">
|
|
||||||
<span className="text-th-fgd-3">
|
|
||||||
{timeSinceListing}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<CalloutTilesLoader />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={CALLOUT_TILES_WRAPPER_CLASSES}>
|
|
||||||
<div className="mb-4 flex items-center space-x-2">
|
|
||||||
<RocketLaunchIcon className="h-5 w-5" />
|
|
||||||
<h2 className="text-base">{t('explore:gainers')}</h2>
|
|
||||||
</div>
|
|
||||||
{!loadingSerumMarkets && groupLoaded ? (
|
|
||||||
<div className="h-full border-t border-th-bkg-3">
|
|
||||||
{gainers.length ? (
|
|
||||||
gainers.map((gainer) => {
|
|
||||||
const bank = gainer.bank
|
|
||||||
const pastPrice = gainer.market?.marketData?.price_24h
|
|
||||||
const change = pastPrice
|
|
||||||
? ((bank.uiPrice - pastPrice) / pastPrice) * 100
|
|
||||||
: 0
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="default-transition flex h-16 cursor-pointer items-center justify-between border-b border-th-bkg-3 px-4 md:hover:bg-th-bkg-2"
|
|
||||||
key={bank.tokenIndex}
|
|
||||||
onClick={() =>
|
|
||||||
goToTokenPage(bank.name.split(' ')[0], router)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<TokenLogo bank={bank} />
|
|
||||||
<p className="ml-3 font-body text-th-fgd-2">
|
|
||||||
{bank.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="mr-3 flex flex-col items-end">
|
|
||||||
<span className="font-mono">
|
|
||||||
<FormatNumericValue value={bank.uiPrice} isUsd />
|
|
||||||
</span>
|
|
||||||
<Change change={change} suffix="%" />
|
|
||||||
</div>
|
|
||||||
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div className="flex h-full flex-col items-center justify-center">
|
|
||||||
<FaceFrownIcon className="mb-1.5 h-5 w-5" />
|
|
||||||
<p>{t('explore:no-gainers')}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<CalloutTilesLoader />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={CALLOUT_TILES_WRAPPER_CLASSES}>
|
|
||||||
<div className="mb-4 flex items-center space-x-2">
|
|
||||||
<FaceFrownIcon className="h-5 w-5" />
|
|
||||||
<h2 className="text-base">{t('explore:losers')}</h2>
|
|
||||||
</div>
|
|
||||||
{!loadingSerumMarkets && groupLoaded ? (
|
|
||||||
<div className="h-full border-t border-th-bkg-3">
|
|
||||||
{losers.length ? (
|
|
||||||
losers.map((loser) => {
|
|
||||||
const bank = loser.bank
|
|
||||||
const pastPrice = loser.market?.marketData?.price_24h
|
|
||||||
const change = pastPrice
|
|
||||||
? ((bank.uiPrice - pastPrice) / pastPrice) * 100
|
|
||||||
: 0
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="default-transition flex h-16 cursor-pointer items-center justify-between border-b border-th-bkg-3 px-4 md:hover:bg-th-bkg-2"
|
|
||||||
key={bank.tokenIndex}
|
|
||||||
onClick={() =>
|
|
||||||
goToTokenPage(bank.name.split(' ')[0], router)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<TokenLogo bank={bank} />
|
|
||||||
<p className="ml-3 font-body text-th-fgd-2">
|
|
||||||
{bank.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="mr-3 flex flex-col items-end">
|
|
||||||
<span className="font-mono">
|
|
||||||
<FormatNumericValue value={bank.uiPrice} isUsd />
|
|
||||||
</span>
|
|
||||||
<Change change={change} suffix="%" />
|
|
||||||
</div>
|
|
||||||
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div className="flex h-full flex-col items-center justify-center">
|
|
||||||
<RocketLaunchIcon className="mb-1.5 h-5 w-5" />
|
|
||||||
<p>{t('explore:no-losers')}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<CalloutTilesLoader />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t border-th-bkg-3 pt-4">
|
|
||||||
<div className="flex flex-col px-4 sm:flex-row sm:items-center sm:justify-between md:px-6 2xl:px-12">
|
|
||||||
<h2 className="mb-4 text-base sm:mb-0">{t('tokens')}</h2>
|
|
||||||
<div className="flex flex-col sm:flex-row sm:space-x-3">
|
|
||||||
<div className="relative mb-3 w-full sm:mb-0 sm:w-40">
|
|
||||||
<Input
|
|
||||||
heightClass="h-10 pl-8"
|
|
||||||
type="text"
|
|
||||||
value={search}
|
|
||||||
onChange={handleUpdateSearch}
|
|
||||||
/>
|
/>
|
||||||
<MagnifyingGlassIcon className="absolute left-2 top-3 h-4 w-4" />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-3">
|
<div className="flex">
|
||||||
<div className="w-full sm:w-48">
|
<button
|
||||||
<ButtonGroup
|
className={`flex w-10 items-center justify-center rounded-l-md border border-th-bkg-3 focus:outline-none md:hover:bg-th-bkg-3 ${
|
||||||
activeValue={sortByKey}
|
showTableView ? 'bg-th-bkg-3 text-th-active' : ''
|
||||||
onChange={(v) => setSortByKey(v)}
|
}`}
|
||||||
names={[t('trade:24h-volume'), t('rolling-change')]}
|
onClick={() => setShowTableView(!showTableView)}
|
||||||
values={['quote_volume_24h', 'change_24h']}
|
>
|
||||||
/>
|
<TableCellsIcon className="h-5 w-5" />
|
||||||
</div>
|
</button>
|
||||||
<div className="flex">
|
<button
|
||||||
<button
|
className={`flex w-10 items-center justify-center rounded-r-md border border-th-bkg-3 focus:outline-none md:hover:bg-th-bkg-3 ${
|
||||||
className={`flex w-10 items-center justify-center rounded-l-md border border-th-bkg-3 focus:outline-none md:hover:bg-th-bkg-3 ${
|
!showTableView ? 'bg-th-bkg-3 text-th-active' : ''
|
||||||
showTableView ? 'bg-th-bkg-3 text-th-active' : ''
|
}`}
|
||||||
}`}
|
onClick={() => setShowTableView(!showTableView)}
|
||||||
onClick={() => setShowTableView(!showTableView)}
|
>
|
||||||
>
|
<Squares2X2Icon className="h-5 w-5" />
|
||||||
<TableCellsIcon className="h-5 w-5" />
|
</button>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`flex w-10 items-center justify-center rounded-r-md border border-th-bkg-3 focus:outline-none md:hover:bg-th-bkg-3 ${
|
|
||||||
!showTableView ? 'bg-th-bkg-3 text-th-active' : ''
|
|
||||||
}`}
|
|
||||||
onClick={() => setShowTableView(!showTableView)}
|
|
||||||
>
|
|
||||||
<Squares2X2Icon className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{sortedTokensToShow.length ? (
|
|
||||||
showTableView ? (
|
|
||||||
<div className="mt-6 border-t border-th-bkg-3">
|
|
||||||
<SpotTable tokens={sortedTokensToShow} />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<SpotCards tokens={sortedTokensToShow} />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div className="px-4 pt-2 md:px-6 2xl:px-12">
|
|
||||||
<EmptyState text="No results found..." />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
{sortedTokensToShow.length ? (
|
||||||
|
showTableView ? (
|
||||||
|
<div className="mt-6 border-t border-th-bkg-3">
|
||||||
|
<SpotTable tokens={sortedTokensToShow} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<SpotCards tokens={sortedTokensToShow} />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="px-4 pt-2 md:px-6 2xl:px-12">
|
||||||
|
<EmptyState text="No results found..." />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Spot
|
export default Spot
|
||||||
|
|
||||||
const CalloutTilesLoader = () => {
|
|
||||||
return (
|
|
||||||
<div className="space-y-1">
|
|
||||||
{[...Array(3)].map((x, i) => (
|
|
||||||
<SheenLoader className="flex flex-1" key={i}>
|
|
||||||
<div className="h-16 w-full rounded-md bg-th-bkg-2" />
|
|
||||||
</SheenLoader>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ import {
|
||||||
NewspaperIcon,
|
NewspaperIcon,
|
||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
DocumentTextIcon,
|
DocumentTextIcon,
|
||||||
SparklesIcon,
|
|
||||||
ArrowTopRightOnSquareIcon,
|
ArrowTopRightOnSquareIcon,
|
||||||
} from '@heroicons/react/20/solid'
|
} from '@heroicons/react/20/solid'
|
||||||
import SolanaTps from '@components/SolanaTps'
|
import SolanaTps from '@components/SolanaTps'
|
||||||
|
@ -80,9 +79,9 @@ const BottomBar = () => {
|
||||||
<ArrowTrendingUpIcon className="mb-1 h-4 w-4" />
|
<ArrowTrendingUpIcon className="mb-1 h-4 w-4" />
|
||||||
<StyledBarItemLabel>{t('trade')}</StyledBarItemLabel>
|
<StyledBarItemLabel>{t('trade')}</StyledBarItemLabel>
|
||||||
</BottomBarLink>
|
</BottomBarLink>
|
||||||
<BottomBarLink isActive={asPath === '/explore'} pathName="/explore">
|
<BottomBarLink isActive={asPath === '/borrow'} pathName="/borrow">
|
||||||
<SparklesIcon className="mb-1 h-4 w-4" />
|
<BanknotesIcon className="mb-1 h-4 w-4" />
|
||||||
<StyledBarItemLabel>{t('explore')}</StyledBarItemLabel>
|
<StyledBarItemLabel>{t('borrow')}</StyledBarItemLabel>
|
||||||
</BottomBarLink>
|
</BottomBarLink>
|
||||||
<button
|
<button
|
||||||
className={`${
|
className={`${
|
||||||
|
@ -125,11 +124,6 @@ const MoreMenuPanel = ({
|
||||||
className="border-b border-th-bkg-4"
|
className="border-b border-th-bkg-4"
|
||||||
onClick={() => setShowPanel(false)}
|
onClick={() => setShowPanel(false)}
|
||||||
>
|
>
|
||||||
<MoreMenuItem
|
|
||||||
title={t('borrow')}
|
|
||||||
path="/borrow"
|
|
||||||
icon={<BanknotesIcon className="h-5 w-5" />}
|
|
||||||
/>
|
|
||||||
<MoreMenuItem
|
<MoreMenuItem
|
||||||
title={t('stats')}
|
title={t('stats')}
|
||||||
path="/stats"
|
path="/stats"
|
||||||
|
|
|
@ -12,7 +12,7 @@ const Change = ({
|
||||||
change: number | typeof NaN
|
change: number | typeof NaN
|
||||||
decimals?: number
|
decimals?: number
|
||||||
prefix?: string
|
prefix?: string
|
||||||
size?: 'small'
|
size?: 'small' | 'large'
|
||||||
suffix?: string
|
suffix?: string
|
||||||
}) => {
|
}) => {
|
||||||
return !isNaN(change) ? (
|
return !isNaN(change) ? (
|
||||||
|
@ -34,7 +34,11 @@ const Change = ({
|
||||||
)}
|
)}
|
||||||
<p
|
<p
|
||||||
className={`font-mono font-normal ${
|
className={`font-mono font-normal ${
|
||||||
size === 'small' ? 'text-xs' : 'text-sm'
|
size === 'small'
|
||||||
|
? 'text-xs'
|
||||||
|
: size === 'large'
|
||||||
|
? 'text-base'
|
||||||
|
: 'text-sm'
|
||||||
} ${
|
} ${
|
||||||
change > 0
|
change > 0
|
||||||
? 'text-th-up'
|
? 'text-th-up'
|
||||||
|
|
|
@ -39,12 +39,13 @@ dayjs.extend(relativeTime)
|
||||||
interface DetailedAreaOrBarChartProps {
|
interface DetailedAreaOrBarChartProps {
|
||||||
chartType?: 'area' | 'bar'
|
chartType?: 'area' | 'bar'
|
||||||
customTooltip?: ContentType<number, string>
|
customTooltip?: ContentType<number, string>
|
||||||
data: any[]
|
data: any[] | undefined
|
||||||
daysToShow?: string
|
daysToShow?: string
|
||||||
domain?: AxisDomain
|
domain?: AxisDomain
|
||||||
heightClass?: string
|
heightClass?: string
|
||||||
hideChange?: boolean
|
hideChange?: boolean
|
||||||
hideChart?: () => void
|
hideChart?: () => void
|
||||||
|
hideAxis?: boolean
|
||||||
loaderHeightClass?: string
|
loaderHeightClass?: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
prefix?: string
|
prefix?: string
|
||||||
|
@ -80,6 +81,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
|
||||||
heightClass,
|
heightClass,
|
||||||
hideChange,
|
hideChange,
|
||||||
hideChart,
|
hideChart,
|
||||||
|
hideAxis,
|
||||||
loaderHeightClass,
|
loaderHeightClass,
|
||||||
loading,
|
loading,
|
||||||
prefix = '',
|
prefix = '',
|
||||||
|
@ -114,12 +116,12 @@ const DetailedAreaOrBarChart: FunctionComponent<
|
||||||
}
|
}
|
||||||
|
|
||||||
const flipGradientCoords = useMemo(() => {
|
const flipGradientCoords = useMemo(() => {
|
||||||
if (!data.length) return
|
if (!data || !data.length) return
|
||||||
return data[0][yKey] <= 0 && data[data.length - 1][yKey] < 0
|
return data[0][yKey] <= 0 && data[data.length - 1][yKey] < 0
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
if (!data.length) return []
|
if (!data || !data.length) return []
|
||||||
const start = Number(daysToShow) * DAILY_MILLISECONDS
|
const start = Number(daysToShow) * DAILY_MILLISECONDS
|
||||||
const filtered = data.filter((d: any) => {
|
const filtered = data.filter((d: any) => {
|
||||||
const dataTime = new Date(d[xKey]).getTime()
|
const dataTime = new Date(d[xKey]).getTime()
|
||||||
|
@ -285,7 +287,9 @@ const DetailedAreaOrBarChart: FunctionComponent<
|
||||||
: ''}
|
: ''}
|
||||||
{prefix}
|
{prefix}
|
||||||
<FormatNumericValue
|
<FormatNumericValue
|
||||||
value={Math.abs(data[data.length - 1][yKey])}
|
value={
|
||||||
|
data ? Math.abs(data[data.length - 1][yKey]) : 0
|
||||||
|
}
|
||||||
decimals={yDecimals}
|
decimals={yDecimals}
|
||||||
/>
|
/>
|
||||||
{suffix}
|
{suffix}
|
||||||
|
@ -386,6 +390,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
|
||||||
<XAxis
|
<XAxis
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
dataKey={xKey}
|
dataKey={xKey}
|
||||||
|
hide={hideAxis}
|
||||||
minTickGap={20}
|
minTickGap={20}
|
||||||
padding={{ left: 20, right: 20 }}
|
padding={{ left: 20, right: 20 }}
|
||||||
tick={{
|
tick={{
|
||||||
|
@ -400,6 +405,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
|
||||||
<YAxis
|
<YAxis
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
dataKey={yKey}
|
dataKey={yKey}
|
||||||
|
hide={hideAxis}
|
||||||
minTickGap={20}
|
minTickGap={20}
|
||||||
type="number"
|
type="number"
|
||||||
domain={
|
domain={
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const UpTriangle = ({ size }: { size?: 'small' }) => (
|
export const UpTriangle = ({ size }: { size?: 'small' | 'large' }) => (
|
||||||
<div
|
<div
|
||||||
className={`h-0 w-0 ${
|
className={`h-0 w-0 ${
|
||||||
size === 'small'
|
size === 'small'
|
||||||
|
@ -8,7 +8,7 @@ export const UpTriangle = ({ size }: { size?: 'small' }) => (
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const DownTriangle = ({ size }: { size?: 'small' }) => (
|
export const DownTriangle = ({ size }: { size?: 'small' | 'large' }) => (
|
||||||
<div
|
<div
|
||||||
className={`h-0 w-0 ${
|
className={`h-0 w-0 ${
|
||||||
size === 'small'
|
size === 'small'
|
||||||
|
|
|
@ -25,7 +25,7 @@ const TabButtons = <T extends Values>({
|
||||||
{values.map(([label, count], i) => (
|
{values.map(([label, count], i) => (
|
||||||
<div className={fillWidth ? 'flex-1' : ''} key={`${label}` + i}>
|
<div className={fillWidth ? 'flex-1' : ''} key={`${label}` + i}>
|
||||||
<button
|
<button
|
||||||
className={`flex h-12 w-full items-center justify-center px-4 font-normal focus-visible:bg-th-bkg-2 focus-visible:text-th-fgd-1 md:px-6 ${
|
className={`flex h-12 w-full items-center justify-center px-4 focus-visible:bg-th-bkg-2 focus-visible:text-th-fgd-1 md:px-6 ${
|
||||||
rounded ? 'rounded-md' : 'rounded-none'
|
rounded ? 'rounded-md' : 'rounded-none'
|
||||||
} ${
|
} ${
|
||||||
showBorders
|
showBorders
|
||||||
|
@ -49,7 +49,7 @@ const TabButtons = <T extends Values>({
|
||||||
className={`${
|
className={`${
|
||||||
label === 'buy' || label === 'sell'
|
label === 'buy' || label === 'sell'
|
||||||
? 'font-display'
|
? 'font-display'
|
||||||
: 'font-medium'
|
: 'font-bold'
|
||||||
} whitespace-nowrap`}
|
} whitespace-nowrap`}
|
||||||
>
|
>
|
||||||
{t(label)}
|
{t(label)}
|
||||||
|
|
|
@ -16,14 +16,15 @@ import { LinkButton } from '@components/shared/Button'
|
||||||
import SoonBadge from '@components/shared/SoonBadge'
|
import SoonBadge from '@components/shared/SoonBadge'
|
||||||
import { useViewport } from 'hooks/useViewport'
|
import { useViewport } from 'hooks/useViewport'
|
||||||
import { breakpoints } from 'utils/theme'
|
import { breakpoints } from 'utils/theme'
|
||||||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
|
||||||
|
|
||||||
export const goToPerpMarketDetails = (
|
export const goToPerpMarketDetails = (
|
||||||
market: PerpMarket,
|
market: string | undefined,
|
||||||
router: NextRouter,
|
router: NextRouter,
|
||||||
) => {
|
) => {
|
||||||
const query = { ...router.query, ['market']: market.name }
|
if (market) {
|
||||||
router.push({ pathname: router.pathname, query })
|
const query = { ...router.query, ['market']: market }
|
||||||
|
router.push({ pathname: router.pathname, query })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PerpMarketDetailsTable = () => {
|
const PerpMarketDetailsTable = () => {
|
||||||
|
@ -95,7 +96,7 @@ const PerpMarketDetailsTable = () => {
|
||||||
<TrBody
|
<TrBody
|
||||||
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
||||||
key={publicKey.toString()}
|
key={publicKey.toString()}
|
||||||
onClick={() => goToPerpMarketDetails(market, router)}
|
onClick={() => goToPerpMarketDetails(market.name, router)}
|
||||||
>
|
>
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
@ -323,7 +324,7 @@ const PerpMarketDetailsTable = () => {
|
||||||
<LinkButton
|
<LinkButton
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
goToPerpMarketDetails(market, router)
|
goToPerpMarketDetails(market.name, router)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t('stats:perp-details', { market: market.name })}
|
{t('stats:perp-details', { market: market.name })}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useEffect, useMemo } from 'react'
|
||||||
import MarketLogos from '@components/trade/MarketLogos'
|
import MarketLogos from '@components/trade/MarketLogos'
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
import PerpMarketDetails from './PerpMarketDetails'
|
import PerpMarketDetails from './PerpMarketDetails'
|
||||||
|
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
|
||||||
|
|
||||||
const PerpStatsPage = () => {
|
const PerpStatsPage = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -33,12 +34,20 @@ const PerpStatsPage = () => {
|
||||||
|
|
||||||
return marketDetails ? (
|
return marketDetails ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col border-b border-th-bkg-3 px-6 py-5 md:flex-row md:items-center md:justify-between">
|
<div className="flex h-14 items-center space-x-4 border-b border-th-bkg-3">
|
||||||
<div>
|
<button
|
||||||
<div className="flex items-center">
|
className="flex h-14 w-14 flex-shrink-0 items-center justify-center border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
|
||||||
<MarketLogos market={marketDetails} size="large" />
|
onClick={() =>
|
||||||
<h1 className="text-xl">{marketDetails.name}</h1>
|
router.push(router.pathname, undefined, { shallow: true })
|
||||||
</div>
|
}
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<MarketLogos market={marketDetails} size="large" />
|
||||||
|
<span className="text-lg font-bold text-th-fgd-1">
|
||||||
|
{marketDetails.name}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PerpMarketDetails marketStats={marketStats} perpMarket={marketDetails} />
|
<PerpMarketDetails marketStats={marketStats} perpMarket={marketDetails} />
|
||||||
|
|
|
@ -471,12 +471,14 @@ const SwapHistoryTable = () => {
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : mangoAccountAddress || connected ? (
|
) : mangoAccountAddress || connected ? (
|
||||||
<div className="flex flex-col items-center p-8">
|
<div className="flex flex-1 flex-col items-center justify-center">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<div className="flex flex-col items-center p-8">
|
||||||
<p>{t('swap:no-history')}</p>
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
<p>{t('swap:no-history')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-8">
|
<div className="flex flex-1 flex-col items-center justify-center p-8">
|
||||||
<ConnectEmptyState text={t('swap:connect-swap')} />
|
<ConnectEmptyState text={t('swap:connect-swap')} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -516,13 +516,15 @@ const SwapOrders = () => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : mangoAccountAddress || connected ? (
|
) : mangoAccountAddress || connected ? (
|
||||||
<div className="flex flex-col items-center p-8">
|
<div className="flex flex-1 flex-col items-center justify-center">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<div className="flex flex-col items-center p-8">
|
||||||
<p>{t('trade:no-orders')}</p>
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
<p>{t('trade:no-trigger-orders')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-8">
|
<div className="flex flex-1 flex-col items-center justify-center p-8">
|
||||||
<ConnectEmptyState text={t('connect-orders')} />
|
<ConnectEmptyState text={t('trade:connect-trigger-orders')} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import TopTokenAccounts from './TopTokenAccounts'
|
||||||
import TokenParams from './TokenParams'
|
import TokenParams from './TokenParams'
|
||||||
import { formatTokenSymbol } from 'utils/tokens'
|
import { formatTokenSymbol } from 'utils/tokens'
|
||||||
import TokenLogo from '@components/shared/TokenLogo'
|
import TokenLogo from '@components/shared/TokenLogo'
|
||||||
|
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
|
||||||
|
|
||||||
const DEFAULT_COINGECKO_VALUES = {
|
const DEFAULT_COINGECKO_VALUES = {
|
||||||
ath: 0,
|
ath: 0,
|
||||||
|
@ -121,6 +122,21 @@ const TokenPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="flex h-14 items-center space-x-4 border-b border-th-bkg-3">
|
||||||
|
<button
|
||||||
|
className="flex h-14 w-14 flex-shrink-0 items-center justify-center border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(router.pathname, undefined, { shallow: true })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
{bank ? (
|
||||||
|
<span className="text-lg font-bold text-th-fgd-1">
|
||||||
|
{formatTokenSymbol(bank.name)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
{bank && bankName ? (
|
{bank && bankName ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col border-b border-th-bkg-3 px-6 py-5 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col border-b border-th-bkg-3 px-6 py-5 md:flex-row md:items-center md:justify-between">
|
||||||
|
@ -129,10 +145,7 @@ const TokenPage = () => {
|
||||||
<TokenLogo bank={bank} />
|
<TokenLogo bank={bank} />
|
||||||
{coingeckoTokenInfo ? (
|
{coingeckoTokenInfo ? (
|
||||||
<h1 className="text-base font-normal">
|
<h1 className="text-base font-normal">
|
||||||
{coingeckoTokenInfo.name}{' '}
|
{coingeckoTokenInfo.name}
|
||||||
<span className="text-th-fgd-4">
|
|
||||||
{formatTokenSymbol(bank.name)}
|
|
||||||
</span>
|
|
||||||
</h1>
|
</h1>
|
||||||
) : (
|
) : (
|
||||||
<h1 className="text-base font-normal">{bank.name}</h1>
|
<h1 className="text-base font-normal">{bank.name}</h1>
|
||||||
|
|
|
@ -78,7 +78,7 @@ const MarketLogos = ({
|
||||||
: size === 'small'
|
: size === 'small'
|
||||||
? 'mr-1.5 h-4'
|
? 'mr-1.5 h-4'
|
||||||
: size === 'large'
|
: size === 'large'
|
||||||
? 'mr-2.5 h-6'
|
? 'mr-3 h-6'
|
||||||
: 'mr-2 h-5'
|
: 'mr-2 h-5'
|
||||||
} ${
|
} ${
|
||||||
market instanceof Serum3Market
|
market instanceof Serum3Market
|
||||||
|
|
|
@ -624,12 +624,14 @@ const OpenOrders = () => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : mangoAccountAddress || connected ? (
|
) : mangoAccountAddress || connected ? (
|
||||||
<div className="flex flex-col items-center p-8">
|
<div className="flex flex-1 flex-col items-center justify-center">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<div className="flex flex-col items-center p-8">
|
||||||
<p>{t('trade:no-orders')}</p>
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
<p>{t('trade:no-orders')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-8">
|
<div className="flex flex-1 flex-col items-center justify-center p-8">
|
||||||
<ConnectEmptyState text={t('trade:connect-orders')} />
|
<ConnectEmptyState text={t('trade:connect-orders')} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -730,12 +730,14 @@ const PerpPositions = () => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : mangoAccount || connected ? (
|
) : mangoAccount || connected ? (
|
||||||
<div className="flex flex-col items-center p-8">
|
<div className="flex flex-1 flex-col items-center justify-center">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<div className="flex flex-col items-center p-8">
|
||||||
<p>{t('trade:no-positions')}</p>
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
<p>{t('trade:no-positions')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-8">
|
<div className="flex flex-1 flex-col items-center justify-center p-8">
|
||||||
<ConnectEmptyState text={t('trade:connect-positions')} />
|
<ConnectEmptyState text={t('trade:connect-positions')} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -351,12 +351,14 @@ const TradeHistory = () => {
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : mangoAccountAddress || connected ? (
|
) : mangoAccountAddress || connected ? (
|
||||||
<div className="flex flex-col items-center p-8">
|
<div className="flex flex-1 flex-col items-center justify-center">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<div className="flex flex-col items-center p-8">
|
||||||
<p>No trade history</p>
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
<p>No trade history</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-8">
|
<div className="flex flex-1 flex-col items-center justify-center p-8">
|
||||||
<ConnectEmptyState text={t('trade:connect-trade-history')} />
|
<ConnectEmptyState text={t('trade:connect-trade-history')} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -326,12 +326,14 @@ const UnsettledTrades = ({
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : mangoAccountAddress || connected ? (
|
) : mangoAccountAddress || connected ? (
|
||||||
<div className="flex flex-col items-center p-8">
|
<div className="flex flex-1 flex-col items-center justify-center">
|
||||||
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<div className="flex flex-col items-center p-8">
|
||||||
<p>{t('trade:no-unsettled')}</p>
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
<p>{t('trade:no-unsettled')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-8">
|
<div className="flex flex-1 flex-col items-center justify-center p-8">
|
||||||
<ConnectEmptyState text={t('trade:connect-unsettled')} />
|
<ConnectEmptyState text={t('trade:connect-unsettled')} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,8 +10,8 @@ export default function useAccountPerformanceData() {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: performanceData,
|
data: performanceData,
|
||||||
isLoading: loadingPerformanceData,
|
|
||||||
isFetching: fetchingPerformanceData,
|
isFetching: fetchingPerformanceData,
|
||||||
|
isInitialLoading: loadingPerformanceData,
|
||||||
} = useQuery(
|
} = useQuery(
|
||||||
['performance', mangoAccountAddress],
|
['performance', mangoAccountAddress],
|
||||||
() => fetchAccountPerformance(mangoAccountAddress, 31),
|
() => fetchAccountPerformance(mangoAccountAddress, 31),
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import ExplorePage from '@components/explore/ExplorePage'
|
|
||||||
import type { NextPage } from 'next'
|
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
|
||||||
|
|
||||||
export async function getStaticProps({ locale }: { locale: string }) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
...(await serverSideTranslations(locale, [
|
|
||||||
'activity',
|
|
||||||
'common',
|
|
||||||
'explore',
|
|
||||||
'governance',
|
|
||||||
'notifications',
|
|
||||||
'onboarding',
|
|
||||||
'profile',
|
|
||||||
'search',
|
|
||||||
'settings',
|
|
||||||
'stats',
|
|
||||||
'token',
|
|
||||||
'trade',
|
|
||||||
])),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Explore: NextPage = () => {
|
|
||||||
return <ExplorePage />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Explore
|
|
|
@ -9,6 +9,8 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
||||||
'account',
|
'account',
|
||||||
'activity',
|
'activity',
|
||||||
'common',
|
'common',
|
||||||
|
'explore',
|
||||||
|
'governance',
|
||||||
'notifications',
|
'notifications',
|
||||||
'onboarding',
|
'onboarding',
|
||||||
'onboarding-tours',
|
'onboarding-tours',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"account-stats": "Account Stats",
|
||||||
"assets": "Assets",
|
"assets": "Assets",
|
||||||
"assets-liabilities": "Assets & Liabilities",
|
"assets-liabilities": "Assets & Liabilities",
|
||||||
"collateral-value": "Collateral Value",
|
"collateral-value": "Collateral Value",
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
"lifetime-volume": "Lifetime Trade Volume",
|
"lifetime-volume": "Lifetime Trade Volume",
|
||||||
"maint-health-contribution": "Maint Health Contribution",
|
"maint-health-contribution": "Maint Health Contribution",
|
||||||
"maint-health-contributions": "Maint Health Contributions",
|
"maint-health-contributions": "Maint Health Contributions",
|
||||||
|
"more-account-stats": "More Account Stats",
|
||||||
"no-data": "No data to display",
|
"no-data": "No data to display",
|
||||||
"no-pnl-history": "No PnL History",
|
"no-pnl-history": "No PnL History",
|
||||||
"pnl-chart": "PnL Chart",
|
"pnl-chart": "PnL Chart",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"connect-orders": "Connect to view your open orders",
|
"connect-orders": "Connect to view your open orders",
|
||||||
"connect-positions": "Connect to view your perp positions",
|
"connect-positions": "Connect to view your perp positions",
|
||||||
"connect-trade-history": "Connect to view your trade history",
|
"connect-trade-history": "Connect to view your trade history",
|
||||||
|
"connect-trigger-orders": "Connect to view your trigger orders",
|
||||||
"connect-unsettled": "Connect to view your unsettled funds",
|
"connect-unsettled": "Connect to view your unsettled funds",
|
||||||
"copy-and-share": "Copy Image to Clipboard",
|
"copy-and-share": "Copy Image to Clipboard",
|
||||||
"current-price": "Current Price",
|
"current-price": "Current Price",
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
"no-markets-found": "No markets found...",
|
"no-markets-found": "No markets found...",
|
||||||
"no-orders": "No open orders",
|
"no-orders": "No open orders",
|
||||||
"no-positions": "No perp positions",
|
"no-positions": "No perp positions",
|
||||||
|
"no-trigger-orders": "No trigger orders",
|
||||||
"no-unsettled": "No unsettled funds",
|
"no-unsettled": "No unsettled funds",
|
||||||
"notional": "Notional",
|
"notional": "Notional",
|
||||||
"notional-volume": "Notional Volume ($)",
|
"notional-volume": "Notional Volume ($)",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"account-stats": "Account Stats",
|
||||||
"assets": "Assets",
|
"assets": "Assets",
|
||||||
"assets-liabilities": "Assets & Liabilities",
|
"assets-liabilities": "Assets & Liabilities",
|
||||||
"collateral-value": "Collateral Value",
|
"collateral-value": "Collateral Value",
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
"lifetime-volume": "Lifetime Trade Volume",
|
"lifetime-volume": "Lifetime Trade Volume",
|
||||||
"maint-health-contribution": "Maint Health Contribution",
|
"maint-health-contribution": "Maint Health Contribution",
|
||||||
"maint-health-contributions": "Maint Health Contributions",
|
"maint-health-contributions": "Maint Health Contributions",
|
||||||
|
"more-account-stats": "More Account Stats",
|
||||||
"no-data": "No data to display",
|
"no-data": "No data to display",
|
||||||
"no-pnl-history": "No PnL History",
|
"no-pnl-history": "No PnL History",
|
||||||
"pnl-chart": "PnL Chart",
|
"pnl-chart": "PnL Chart",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"connect-orders": "Connect to view your open orders",
|
"connect-orders": "Connect to view your open orders",
|
||||||
"connect-positions": "Connect to view your perp positions",
|
"connect-positions": "Connect to view your perp positions",
|
||||||
"connect-trade-history": "Connect to view your trade history",
|
"connect-trade-history": "Connect to view your trade history",
|
||||||
|
"connect-trigger-orders": "Connect to view your trigger orders",
|
||||||
"connect-unsettled": "Connect to view your unsettled funds",
|
"connect-unsettled": "Connect to view your unsettled funds",
|
||||||
"copy-and-share": "Copy Image to Clipboard",
|
"copy-and-share": "Copy Image to Clipboard",
|
||||||
"current-price": "Current Price",
|
"current-price": "Current Price",
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
"no-markets-found": "No markets found...",
|
"no-markets-found": "No markets found...",
|
||||||
"no-orders": "No open orders",
|
"no-orders": "No open orders",
|
||||||
"no-positions": "No perp positions",
|
"no-positions": "No perp positions",
|
||||||
|
"no-trigger-orders": "No trigger orders",
|
||||||
"no-unsettled": "No unsettled funds",
|
"no-unsettled": "No unsettled funds",
|
||||||
"notional": "Notional",
|
"notional": "Notional",
|
||||||
"notional-volume": "Notional Volume ($)",
|
"notional-volume": "Notional Volume ($)",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"account-stats": "Account Stats",
|
||||||
"assets": "Assets",
|
"assets": "Assets",
|
||||||
"assets-liabilities": "Assets & Liabilities",
|
"assets-liabilities": "Assets & Liabilities",
|
||||||
"collateral-value": "Collateral Value",
|
"collateral-value": "Collateral Value",
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
"lifetime-volume": "Lifetime Trade Volume",
|
"lifetime-volume": "Lifetime Trade Volume",
|
||||||
"maint-health-contribution": "Maint Health Contribution",
|
"maint-health-contribution": "Maint Health Contribution",
|
||||||
"maint-health-contributions": "Maint Health Contributions",
|
"maint-health-contributions": "Maint Health Contributions",
|
||||||
|
"more-account-stats": "More Account Stats",
|
||||||
"no-data": "No data to display",
|
"no-data": "No data to display",
|
||||||
"no-pnl-history": "No PnL History",
|
"no-pnl-history": "No PnL History",
|
||||||
"pnl-chart": "PnL Chart",
|
"pnl-chart": "PnL Chart",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"connect-orders": "Connect to view your open orders",
|
"connect-orders": "Connect to view your open orders",
|
||||||
"connect-positions": "Connect to view your perp positions",
|
"connect-positions": "Connect to view your perp positions",
|
||||||
"connect-trade-history": "Connect to view your trade history",
|
"connect-trade-history": "Connect to view your trade history",
|
||||||
|
"connect-trigger-orders": "Connect to view your trigger orders",
|
||||||
"connect-unsettled": "Connect to view your unsettled funds",
|
"connect-unsettled": "Connect to view your unsettled funds",
|
||||||
"copy-and-share": "Copy Image to Clipboard",
|
"copy-and-share": "Copy Image to Clipboard",
|
||||||
"current-price": "Current Price",
|
"current-price": "Current Price",
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
"no-markets-found": "No markets found...",
|
"no-markets-found": "No markets found...",
|
||||||
"no-orders": "No open orders",
|
"no-orders": "No open orders",
|
||||||
"no-positions": "No perp positions",
|
"no-positions": "No perp positions",
|
||||||
|
"no-trigger-orders": "No trigger orders",
|
||||||
"no-unsettled": "No unsettled funds",
|
"no-unsettled": "No unsettled funds",
|
||||||
"notional": "Notional",
|
"notional": "Notional",
|
||||||
"notional-volume": "Notional Volume ($)",
|
"notional-volume": "Notional Volume ($)",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"account-stats": "Account Stats",
|
||||||
"assets": "资产",
|
"assets": "资产",
|
||||||
"assets-liabilities": "资产和债务",
|
"assets-liabilities": "资产和债务",
|
||||||
"collateral-value": "质押品价值",
|
"collateral-value": "质押品价值",
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
"maint-health": "维持健康度",
|
"maint-health": "维持健康度",
|
||||||
"maint-health-contribution": "维持健康贡献",
|
"maint-health-contribution": "维持健康贡献",
|
||||||
"maint-health-contributions": "维持健康度",
|
"maint-health-contributions": "维持健康度",
|
||||||
|
"more-account-stats": "More Account Stats",
|
||||||
"no-data": "无数据可显示",
|
"no-data": "无数据可显示",
|
||||||
"no-pnl-history": "无盈亏历史",
|
"no-pnl-history": "无盈亏历史",
|
||||||
"pnl-chart": "盈亏图表",
|
"pnl-chart": "盈亏图表",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"connect-orders": "连接以查看未结订单",
|
"connect-orders": "连接以查看未结订单",
|
||||||
"connect-positions": "连接以查看持仓",
|
"connect-positions": "连接以查看持仓",
|
||||||
"connect-trade-history": "连接以查看交易历史",
|
"connect-trade-history": "连接以查看交易历史",
|
||||||
|
"connect-trigger-orders": "Connect to view your trigger orders",
|
||||||
"connect-unsettled": "连接以查看未结清余额",
|
"connect-unsettled": "连接以查看未结清余额",
|
||||||
"copy-and-share": "将图片复制到剪贴版",
|
"copy-and-share": "将图片复制到剪贴版",
|
||||||
"current-price": "目前价格",
|
"current-price": "目前价格",
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
"no-markets-found": "No markets found...",
|
"no-markets-found": "No markets found...",
|
||||||
"no-orders": "无订单",
|
"no-orders": "无订单",
|
||||||
"no-positions": "无持仓",
|
"no-positions": "无持仓",
|
||||||
|
"no-trigger-orders": "No trigger orders",
|
||||||
"no-unsettled": "无未结清余额",
|
"no-unsettled": "无未结清余额",
|
||||||
"notional": "名义",
|
"notional": "名义",
|
||||||
"notional-volume": "名义交易量($)",
|
"notional-volume": "名义交易量($)",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"account-stats": "Account Stats",
|
||||||
"assets": "資產",
|
"assets": "資產",
|
||||||
"assets-liabilities": "資產和債務",
|
"assets-liabilities": "資產和債務",
|
||||||
"collateral-value": "質押品價值",
|
"collateral-value": "質押品價值",
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
"maint-health": "維持健康度",
|
"maint-health": "維持健康度",
|
||||||
"maint-health-contribution": "維持健康貢獻",
|
"maint-health-contribution": "維持健康貢獻",
|
||||||
"maint-health-contributions": "維持健康度",
|
"maint-health-contributions": "維持健康度",
|
||||||
|
"more-account-stats": "More Account Stats",
|
||||||
"no-data": "無數據可顯示",
|
"no-data": "無數據可顯示",
|
||||||
"no-pnl-history": "無盈虧歷史",
|
"no-pnl-history": "無盈虧歷史",
|
||||||
"pnl-chart": "盈虧圖表",
|
"pnl-chart": "盈虧圖表",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"connect-orders": "連接以查看未結訂單",
|
"connect-orders": "連接以查看未結訂單",
|
||||||
"connect-positions": "連接以查看持倉",
|
"connect-positions": "連接以查看持倉",
|
||||||
"connect-trade-history": "連接以查看交易歷史",
|
"connect-trade-history": "連接以查看交易歷史",
|
||||||
|
"connect-trigger-orders": "Connect to view your trigger orders",
|
||||||
"connect-unsettled": "連接以查看未結清餘額",
|
"connect-unsettled": "連接以查看未結清餘額",
|
||||||
"copy-and-share": "將圖片複製到剪貼版",
|
"copy-and-share": "將圖片複製到剪貼版",
|
||||||
"current-price": "目前價格",
|
"current-price": "目前價格",
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
"no-markets-found": "No markets found...",
|
"no-markets-found": "No markets found...",
|
||||||
"no-orders": "無訂單",
|
"no-orders": "無訂單",
|
||||||
"no-positions": "無持倉",
|
"no-positions": "無持倉",
|
||||||
|
"no-trigger-orders": "No trigger orders",
|
||||||
"no-unsettled": "無未結清餘額",
|
"no-unsettled": "無未結清餘額",
|
||||||
"notional": "名義",
|
"notional": "名義",
|
||||||
"notional-volume": "名義交易量($)",
|
"notional-volume": "名義交易量($)",
|
||||||
|
|
Loading…
Reference in New Issue