Merge pull request #7 from blockworks-foundation/account-page
Account page
This commit is contained in:
commit
15db74feb6
|
@ -81,7 +81,7 @@ const BalancesTable = () => {
|
|||
>
|
||||
<Table className={`min-w-full divide-y divide-th-bkg-2`}>
|
||||
<Thead>
|
||||
<Tr className="text-th-fgd-3">
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th
|
||||
scope="col"
|
||||
className={`px-6 py-2 text-left font-normal`}
|
||||
|
@ -134,7 +134,7 @@ const BalancesTable = () => {
|
|||
`}
|
||||
>
|
||||
<Td
|
||||
className={`flex items-center px-4 py-2.5 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`flex items-center px-4 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
|
@ -147,27 +147,27 @@ const BalancesTable = () => {
|
|||
{balance.symbol}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-4 py-2.5 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-4 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{balance.marginDeposits.toFixed(tokenConfig.decimals)}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-4 py-2.5 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-4 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{balance.borrows.toFixed(tokenConfig.decimals)}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-4 py-2.5 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-4 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{balance.orders}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-4 py-2.5 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-4 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{balance.unsettled}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-4 py-2.5 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-4 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{balance.net.toFixed(tokenConfig.decimals)}
|
||||
</Td>
|
||||
|
|
|
@ -17,8 +17,8 @@ const Button: FunctionComponent<ButtonProps> = ({
|
|||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`${className} px-6 py-2 border border-th-fgd-4 bg-th-bkg-2 rounded-md text-th-fgd-1
|
||||
active:border-mango-yellow hover:bg-th-bkg-3 focus:outline-none disabled:bg-th-bkg-2
|
||||
className={`${className} px-6 py-2 bg-th-bkg-4 rounded-full text-th-fgd-1
|
||||
hover:brightness-[1.15] focus:outline-none disabled:bg-th-bkg-4
|
||||
disabled:text-th-fgd-4 disabled:cursor-not-allowed`}
|
||||
{...props}
|
||||
>
|
||||
|
@ -47,3 +47,22 @@ export const LinkButton: FunctionComponent<ButtonProps> = ({
|
|||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export const IconButton: FunctionComponent<ButtonProps> = ({
|
||||
children,
|
||||
onClick,
|
||||
disabled = false,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`${className} bg-th-bkg-4 flex items-center justify-center rounded-full w-8 h-8 text-th-fgd-1 focus:outline-none hover:text-th-primary`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ const ConnectWalletButton = () => {
|
|||
{connected && wallet?.publicKey ? (
|
||||
<Menu>
|
||||
<div className="relative h-full">
|
||||
<Menu.Button className="bg-th-fgd-4 flex items-center justify-center rounded-full w-10 h-10 text-th-fgd-2 focus:outline-none hover:bg-th-bkg-3 hover:text-th-fgd-3">
|
||||
<ProfileIcon className="fill-current h-6 w-6" />
|
||||
<Menu.Button className="bg-th-bkg-4 flex items-center justify-center rounded-full w-10 h-10 text-white focus:outline-none hover:bg-th-bkg-3 hover:text-th-fgd-3">
|
||||
<ProfileIcon className="h-6 w-6" />
|
||||
</Menu.Button>
|
||||
<Menu.Items className="bg-th-bkg-1 mt-2 p-1 absolute right-0 shadow-lg outline-none rounded-md w-48 z-20">
|
||||
<Menu.Item>
|
||||
|
|
|
@ -8,7 +8,7 @@ import useMangoStore from '../stores/useMangoStore'
|
|||
import DepositModal from './DepositModal'
|
||||
import WithdrawModal from './WithdrawModal'
|
||||
// import BorrowModal from './BorrowModal'
|
||||
import Button from './Button'
|
||||
import Button, { IconButton } from './Button'
|
||||
import AccountsModal from './AccountsModal'
|
||||
|
||||
export default function MarginBalances() {
|
||||
|
@ -48,11 +48,10 @@ export default function MarginBalances() {
|
|||
<ElementTitle noMarignBottom>Mango Account</ElementTitle>
|
||||
<div className="absolute right-0 pr-4">
|
||||
<Menu>
|
||||
<Menu.Button
|
||||
className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={!connected}
|
||||
>
|
||||
<DotsHorizontalIcon className="w-5 h-5" />
|
||||
<Menu.Button disabled={!connected}>
|
||||
<IconButton>
|
||||
<DotsHorizontalIcon className="w-5 h-5" />
|
||||
</IconButton>
|
||||
</Menu.Button>
|
||||
<Menu.Items className="bg-th-bkg-1 mt-2 p-1 absolute right-0 shadow-lg outline-none rounded-md w-48 z-20">
|
||||
<Menu.Item>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { useState } from 'react'
|
|||
import { RefreshClockwiseIcon } from './icons'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import Tooltip from './Tooltip'
|
||||
import { IconButton } from './Button'
|
||||
|
||||
const ManualRefresh = ({ className = '' }) => {
|
||||
const [spin, setSpin] = useState(false)
|
||||
|
@ -18,14 +19,11 @@ const ManualRefresh = ({ className = '' }) => {
|
|||
return (
|
||||
<div className={`inline-flex relative ${className}`}>
|
||||
<Tooltip content="Refresh Data" className="text-xs py-1">
|
||||
<button
|
||||
onClick={() => handleRefreshData()}
|
||||
className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
|
||||
>
|
||||
<IconButton onClick={handleRefreshData}>
|
||||
<RefreshClockwiseIcon
|
||||
className={`w-4 h-4 ${spin ? 'animate-spin' : null}`}
|
||||
/>
|
||||
</button>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -106,11 +106,14 @@ export default function MarginInfo() {
|
|||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="border border-th-bkg-3 mt-4 p-4 rounded">
|
||||
<div className="border border-th-bkg-4 mt-4 p-4 rounded">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<HeartIcon className="h-5 w-5" aria-hidden="true" />
|
||||
<HeartIcon
|
||||
className="h-5 w-5 text-th-primary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="ml-2">Health Ratio</span>
|
||||
</div>
|
||||
<div className="text-right">{maintHealth.toFixed(2)}%</div>
|
||||
|
@ -121,7 +124,13 @@ export default function MarginInfo() {
|
|||
style={{
|
||||
width: `${maintHealth}%`,
|
||||
}}
|
||||
className="flex rounded bg-th-primary"
|
||||
className={`flex rounded ${
|
||||
maintHealth > 50
|
||||
? 'bg-th-green'
|
||||
: maintHealth <= 50 && maintHealth > 24
|
||||
? 'bg-th-orange'
|
||||
: 'bg-th-red'
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -80,7 +80,7 @@ export default function MarketMenuItem({ menuTitle = '', linksArray = [] }) {
|
|||
className="flex flex-col h-10"
|
||||
>
|
||||
<Popover.Button
|
||||
className="flex items-center px-2.5 h-10 text-th-fgd-3 hover:text-th-primary focus:outline-none"
|
||||
className="flex items-center px-3 h-10 text-th-fgd-3 hover:text-th-primary focus:outline-none"
|
||||
ref={buttonRef}
|
||||
onClick={() => handleClick(open)}
|
||||
>
|
||||
|
|
|
@ -15,16 +15,16 @@ const StyledMarketSelectWrapper = styled.div`
|
|||
}
|
||||
`
|
||||
|
||||
const StyledMarketTypeToggleWrapper = styled.div`
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
`
|
||||
// const StyledMarketTypeToggleWrapper = styled.div`
|
||||
// background: rgba(255, 255, 255, 0.12);
|
||||
// `
|
||||
|
||||
const StyledArrow = styled.div`
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 20px solid transparent;
|
||||
border-bottom: 20px solid transparent;
|
||||
border-left: 20px solid rgba(255, 255, 255, 0.12);
|
||||
// border-left: 20px solid rgba(255, 255, 255, 0.12);
|
||||
padding-right: 0.5rem;
|
||||
`
|
||||
|
||||
|
@ -54,15 +54,15 @@ const MarketSelect = () => {
|
|||
return (
|
||||
<>
|
||||
<StyledMarketSelectWrapper className="bg-th-bkg-3 flex h-10">
|
||||
<StyledMarketTypeToggleWrapper className="flex items-center pl-6 md:pl-9 pr-1">
|
||||
<div className="bg-th-bkg-4 flex items-center pl-6 md:pl-9 pr-1">
|
||||
<LinkButton
|
||||
className="font-normal text-th-fgd-2 text-xs"
|
||||
onClick={() => setShowMarketsModal(true)}
|
||||
>
|
||||
MARKETS
|
||||
</LinkButton>
|
||||
</StyledMarketTypeToggleWrapper>
|
||||
<StyledArrow />
|
||||
</div>
|
||||
<StyledArrow className="border-l-[20px] border-th-bkg-4" />
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center">
|
||||
{sortedMarkets
|
||||
|
|
|
@ -12,6 +12,7 @@ import SideBadge from './SideBadge'
|
|||
import { useSortableData } from '../hooks/useSortableData'
|
||||
import { Order, Market } from '@project-serum/serum/lib/market'
|
||||
import { PerpOrder, PerpMarket } from '@blockworks-foundation/mango-client'
|
||||
import { usdFormatter } from '../utils'
|
||||
|
||||
const OpenOrdersTable = () => {
|
||||
const { asPath } = useRouter()
|
||||
|
@ -76,9 +77,9 @@ const OpenOrdersTable = () => {
|
|||
<Table className={`min-w-full divide-y divide-th-bkg-2`}>
|
||||
<Thead>
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th scope="col" className={`px-6 py-2 text-left`}>
|
||||
<Th scope="col" className={`px-6 py-2`}>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline font-normal text-sm"
|
||||
className="flex items-center no-underline font-normal"
|
||||
onClick={() => requestSort('marketName')}
|
||||
>
|
||||
Market
|
||||
|
@ -93,9 +94,9 @@ const OpenOrdersTable = () => {
|
|||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-2 text-left`}>
|
||||
<Th scope="col" className={`px-6 py-2`}>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline font-normal text-sm"
|
||||
className="flex items-center no-underline font-normal"
|
||||
onClick={() => requestSort('side')}
|
||||
>
|
||||
Side
|
||||
|
@ -110,9 +111,9 @@ const OpenOrdersTable = () => {
|
|||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-2 text-left`}>
|
||||
<Th scope="col" className={`px-6 py-2`}>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline font-normal text-sm"
|
||||
className="flex items-center no-underline font-normal"
|
||||
onClick={() => requestSort('size')}
|
||||
>
|
||||
Size
|
||||
|
@ -127,9 +128,9 @@ const OpenOrdersTable = () => {
|
|||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-2 text-left`}>
|
||||
<Th scope="col" className={`px-6 py-2`}>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline font-normal text-sm"
|
||||
className="flex items-center no-underline font-normal"
|
||||
onClick={() => requestSort('price')}
|
||||
>
|
||||
Price
|
||||
|
@ -158,7 +159,7 @@ const OpenOrdersTable = () => {
|
|||
`}
|
||||
>
|
||||
<Td
|
||||
className={`px-4 py-1 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-6 py-2 whitespace-nowrap text-th-fgd-1`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
|
@ -172,21 +173,21 @@ const OpenOrdersTable = () => {
|
|||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-4 py-1 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-6 py-2 whitespace-nowrap text-th-fgd-1`}
|
||||
>
|
||||
<SideBadge side={order.side} />
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-4 py-1 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-6 py-2 whitespace-nowrap text-th-fgd-1`}
|
||||
>
|
||||
{order.size}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-4 py-1 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
className={`px-6 py-2 whitespace-nowrap text-th-fgd-1`}
|
||||
>
|
||||
{order.price}
|
||||
{usdFormatter.format(order.price)}
|
||||
</Td>
|
||||
<Td className={`px-4 py-1 whitespace-nowrap text-left`}>
|
||||
<Td className={`px-6 py-2 whitespace-nowrap`}>
|
||||
<div className={`flex justify-end`}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
|
|
|
@ -15,6 +15,7 @@ import BN from 'bn.js'
|
|||
import SideBadge from './SideBadge'
|
||||
import { useState } from 'react'
|
||||
import Loading from './Loading'
|
||||
import { usdFormatter } from '../utils'
|
||||
|
||||
const PositionsTable = () => {
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
|
@ -95,9 +96,9 @@ const PositionsTable = () => {
|
|||
<div className="overflow-hidden border-b border-th-bkg-2 sm:rounded-m">
|
||||
<Table className="min-w-full divide-y divide-th-bkg-2">
|
||||
<Thead>
|
||||
<Tr className="text-th-fgd-3">
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th scope="col" className="px-6 py-2 text-left font-normal">
|
||||
Perp Market
|
||||
Market
|
||||
</Th>
|
||||
<Th scope="col" className="px-2 py-2 text-left font-normal">
|
||||
Side
|
||||
|
@ -144,7 +145,7 @@ const PositionsTable = () => {
|
|||
${index % 2 === 0 ? `bg-th-bkg-3` : `bg-th-bkg-2`}
|
||||
`}
|
||||
>
|
||||
<Td className="px-6 py-1 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
<Td className="px-6 py-2 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
|
@ -156,7 +157,7 @@ const PositionsTable = () => {
|
|||
<div>{marketConfig.name}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td className="px-2 py-1 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
<SideBadge
|
||||
side={
|
||||
perpAccount.basePosition.gt(new BN(0))
|
||||
|
@ -165,25 +166,28 @@ const PositionsTable = () => {
|
|||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="px-2 py-1 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
{perpMarket.baseLotsToNumber(
|
||||
perpAccount.basePosition
|
||||
)}
|
||||
</Td>
|
||||
<Td className="px-2 py-1 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
{nativeI80F48ToUi(
|
||||
perpAccount.quotePosition,
|
||||
marketConfig.quoteDecimals
|
||||
).toFixed()}
|
||||
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
{usdFormatter.format(
|
||||
+nativeI80F48ToUi(
|
||||
perpAccount.quotePosition,
|
||||
marketConfig.quoteDecimals
|
||||
)
|
||||
)}
|
||||
</Td>
|
||||
<Td className="px-2 py-1 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
$
|
||||
{nativeI80F48ToUi(
|
||||
perpAccount.getPnl(perpMarketInfo, price),
|
||||
marketConfig.quoteDecimals
|
||||
).toFixed()}
|
||||
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
{usdFormatter.format(
|
||||
+nativeI80F48ToUi(
|
||||
perpAccount.getPnl(perpMarketInfo, price),
|
||||
marketConfig.quoteDecimals
|
||||
)
|
||||
)}
|
||||
</Td>
|
||||
<Td className="px-2 py-1 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
{perpAccount
|
||||
.getHealth(
|
||||
perpMarketInfo,
|
||||
|
@ -195,7 +199,7 @@ const PositionsTable = () => {
|
|||
)
|
||||
.toFixed(3)}
|
||||
</Td>
|
||||
<Td className="px-6 py-1 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
<Td className="px-6 py-2 whitespace-nowrap text-sm text-th-fgd-1">
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
onClick={() =>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { TemplateIcon } from '@heroicons/react/outline'
|
|||
import { defaultLayouts, GRID_LAYOUT_KEY } from './TradePageGrid'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import Tooltip from './Tooltip'
|
||||
import { IconButton } from './Button'
|
||||
|
||||
const ResetLayout = ({ className = '' }) => {
|
||||
const [, setSavedLayouts] = useLocalStorageState(
|
||||
|
@ -16,12 +17,9 @@ const ResetLayout = ({ className = '' }) => {
|
|||
return (
|
||||
<div className={`inline-flex relative ${className}`}>
|
||||
<Tooltip content="Reset Layout" className="text-xs py-1">
|
||||
<button
|
||||
onClick={() => handleResetLayout()}
|
||||
className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
|
||||
>
|
||||
<IconButton onClick={handleResetLayout}>
|
||||
<TemplateIcon className="w-4 h-4" />
|
||||
</button>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ const Switch: FunctionComponent<SwitchProps> = ({
|
|||
<button
|
||||
type="button"
|
||||
className={`${
|
||||
checked ? 'bg-th-primary' : 'bg-th-fgd-4'
|
||||
checked ? 'bg-th-primary' : 'bg-th-bkg-4'
|
||||
} relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent
|
||||
rounded-full cursor-pointer transition-colors ease-in-out duration-200
|
||||
focus:outline-none`}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useTheme } from 'next-themes'
|
|||
import { MoonIcon, SunIcon } from '@heroicons/react/outline'
|
||||
import DropMenu from './DropMenu'
|
||||
import { MangoIcon } from './icons'
|
||||
import { IconButton } from './Button'
|
||||
|
||||
const THEMES = [
|
||||
{ name: 'Light', icon: <SunIcon className="h-4 w-4" /> },
|
||||
|
@ -20,9 +21,7 @@ const ThemeSwitch = () => {
|
|||
return mounted ? (
|
||||
<DropMenu
|
||||
button={
|
||||
<div className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8">
|
||||
{THEMES.find((t) => t.name === theme).icon}
|
||||
</div>
|
||||
<IconButton>{THEMES.find((t) => t.name === theme).icon}</IconButton>
|
||||
}
|
||||
buttonClassName="flex items-center justify-center hover:text-th-primary rounded-md focus:outline-none"
|
||||
value={theme}
|
||||
|
|
|
@ -58,15 +58,15 @@ const TopBar = () => {
|
|||
</div>
|
||||
{mangoAccount ? (
|
||||
<div className="pl-3">
|
||||
<Button
|
||||
className="pb-1 pt-1 pl-2 pr-2 text-xs"
|
||||
<button
|
||||
className="border border-th-bkg-4 py-1 px-2 rounded text-xs focus:outline-none hover:border-th-fgd-4"
|
||||
onClick={() => setShowAccountsModal(true)}
|
||||
>
|
||||
<div className="font-normal text-th-primary tiny-text">
|
||||
Account
|
||||
</div>
|
||||
{abbreviateAddress(mangoAccount.publicKey)}
|
||||
</Button>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex">
|
||||
|
|
|
@ -374,15 +374,15 @@ export default function TradeForm() {
|
|||
onClick={onSubmit}
|
||||
className={`${
|
||||
!disabledTradeButton
|
||||
? 'border-th-green hover:border-th-green-dark'
|
||||
: undefined
|
||||
? 'bg-th-bkg-2 border border-th-green hover:border-th-green-dark'
|
||||
: 'border border-th-bkg-4'
|
||||
} text-th-green hover:text-th-fgd-1 hover:bg-th-green-dark flex-grow`}
|
||||
>
|
||||
{`${
|
||||
baseSize > 0
|
||||
? 'Buy ' + baseSize
|
||||
: 'Set BUY bid >= ' + minOrderSize
|
||||
} ${marketConfig.baseSymbol}`}
|
||||
{`${baseSize > 0 ? 'Buy ' + baseSize : 'Buy '} ${
|
||||
marketConfig.name.includes('PERP')
|
||||
? marketConfig.name
|
||||
: marketConfig.baseSymbol
|
||||
}`}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
|
@ -390,15 +390,15 @@ export default function TradeForm() {
|
|||
onClick={onSubmit}
|
||||
className={`${
|
||||
!disabledTradeButton
|
||||
? 'border-th-red hover:border-th-red-dark'
|
||||
: undefined
|
||||
? 'bg-th-bkg-2 border border-th-red hover:border-th-red-dark'
|
||||
: 'border border-th-bkg-4'
|
||||
} text-th-red hover:text-th-fgd-1 hover:bg-th-red-dark flex-grow`}
|
||||
>
|
||||
{`${
|
||||
baseSize > 0
|
||||
? 'Sell ' + baseSize
|
||||
: 'Set SELL bid >= ' + minOrderSize
|
||||
} ${marketConfig.baseSymbol}`}
|
||||
{`${baseSize > 0 ? 'Sell ' + baseSize : 'Sell '} ${
|
||||
marketConfig.name.includes('PERP')
|
||||
? marketConfig.name
|
||||
: marketConfig.baseSymbol
|
||||
}`}
|
||||
</Button>
|
||||
)
|
||||
) : (
|
||||
|
|
|
@ -58,7 +58,10 @@ const TVChartContainer = () => {
|
|||
useEffect(() => {
|
||||
const widgetOptions: ChartingLibraryWidgetOptions = {
|
||||
// TODO: stop trading view from not crash looping on perp
|
||||
symbol: selectedMarketConfig.kind == "perp" ? "BTC/USDC" : selectedMarketConfig.name,
|
||||
symbol:
|
||||
selectedMarketConfig.kind == 'perp'
|
||||
? 'BTC/USDC'
|
||||
: selectedMarketConfig.name,
|
||||
// BEWARE: no trailing slash is expected in feed URL
|
||||
// tslint:disable-next-line:no-any
|
||||
datafeed: new (window as any).Datafeeds.UDFCompatibleDatafeed(
|
||||
|
@ -102,7 +105,7 @@ const TVChartContainer = () => {
|
|||
loading_screen: { backgroundColor: 'rgba(0,0,0,0.1)' },
|
||||
overrides: {
|
||||
'paneProperties.background':
|
||||
theme === 'Dark' ? '#2B2B2B' : theme === 'Light' ? '#fff' : '#1D1832',
|
||||
theme === 'Dark' ? '#1B1B1F' : theme === 'Light' ? '#fff' : '#1D1832',
|
||||
'mainSeriesProperties.candleStyle.upColor':
|
||||
theme === 'Mango' ? '#AFD803' : '#5EBF4D',
|
||||
'mainSeriesProperties.candleStyle.downColor':
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Transition } from '@headlessui/react'
|
|||
import useMangoStore from '../stores/useMangoStore'
|
||||
import ResetLayout from './ResetLayout'
|
||||
import Tooltip from './Tooltip'
|
||||
import { IconButton } from './Button'
|
||||
|
||||
const UiLock = ({ className = '' }) => {
|
||||
const set = useMangoStore((s) => s.set)
|
||||
|
@ -36,16 +37,13 @@ const UiLock = ({ className = '' }) => {
|
|||
content={uiLocked ? 'Unlock Layout' : 'Lock Layout'}
|
||||
className="text-xs py-1"
|
||||
>
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 default-transition hover:text-th-primary focus:outline-none"
|
||||
>
|
||||
<IconButton onClick={handleClick}>
|
||||
{uiLocked ? (
|
||||
<LockClosedIcon className="w-4 h-4" />
|
||||
) : (
|
||||
<LockOpenIcon className="w-4 h-4 animate-bounce" />
|
||||
)}
|
||||
</div>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -162,10 +162,10 @@ export default function AccountAssets() {
|
|||
{balances.map((bal, i) => {
|
||||
const token = getTokenBySymbol(groupConfig, bal.symbol)
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
console.log(
|
||||
'price cache',
|
||||
mangoCache.priceCache[tokenIndex]
|
||||
)
|
||||
// console.log(
|
||||
// 'price cache',
|
||||
// mangoCache.priceCache[tokenIndex]
|
||||
// )
|
||||
|
||||
return (
|
||||
<Tr
|
||||
|
|
|
@ -0,0 +1,493 @@
|
|||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
|
||||
import styled from '@emotion/styled'
|
||||
import { Menu } from '@headlessui/react'
|
||||
import {
|
||||
ArrowSmDownIcon,
|
||||
CurrencyDollarIcon,
|
||||
DotsHorizontalIcon,
|
||||
HeartIcon,
|
||||
XIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import {
|
||||
getTokenBySymbol,
|
||||
getMarketByPublicKey,
|
||||
I80F48,
|
||||
nativeI80F48ToUi,
|
||||
PerpMarket,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import { useBalances } from '../../hooks/useBalances'
|
||||
import { useSortableData } from '../../hooks/useSortableData'
|
||||
import { usdFormatter, tokenPrecision } from '../../utils'
|
||||
import SideBadge from '../SideBadge'
|
||||
import Button, { LinkButton } from '../Button'
|
||||
import Switch from '../Switch'
|
||||
import PositionsTable from '../PositionsTable'
|
||||
|
||||
const StyledAccountValue = styled.div`
|
||||
font-size: 1.8rem;
|
||||
line-height: 1.2;
|
||||
`
|
||||
|
||||
export default function AccountOverview() {
|
||||
const [spotPortfolio, setSpotPortfolio] = useState([])
|
||||
const [perpPositions, setPerpPositions] = useState([])
|
||||
const [filteredSpotPortfolio, setFilteredSpotPortfolio] = useState([])
|
||||
const [showZeroBalances, setShowZeroBalances] = useState(false)
|
||||
const allMarkets = useMangoStore((s) => s.selectedMangoGroup.markets)
|
||||
const balances = useBalances()
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const { items, requestSort, sortConfig } = useSortableData(
|
||||
filteredSpotPortfolio
|
||||
)
|
||||
|
||||
const perpMarkets = useMemo(
|
||||
() =>
|
||||
mangoGroup
|
||||
? groupConfig.perpMarkets.map(
|
||||
(m) => mangoGroup.perpMarkets[m.marketIndex]
|
||||
)
|
||||
: [],
|
||||
[mangoGroup]
|
||||
)
|
||||
|
||||
const perpAccounts = useMemo(
|
||||
() =>
|
||||
mangoAccount
|
||||
? groupConfig.perpMarkets.map(
|
||||
(m) => mangoAccount.perpAccounts[m.marketIndex]
|
||||
)
|
||||
: [],
|
||||
[mangoAccount]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let positions = []
|
||||
perpAccounts.forEach((acc, index) => {
|
||||
const market = perpMarkets[index]
|
||||
const marketConfig = getMarketByPublicKey(groupConfig, market.perpMarket)
|
||||
const perpMarket = allMarkets[
|
||||
marketConfig.publicKey.toString()
|
||||
] as PerpMarket
|
||||
if (
|
||||
+nativeI80F48ToUi(acc.quotePosition, marketConfig.quoteDecimals) !== 0
|
||||
) {
|
||||
positions.push({
|
||||
market: marketConfig.name,
|
||||
balance: perpMarket.baseLotsToNumber(acc.basePosition),
|
||||
price: mangoGroup.getPrice(marketConfig.marketIndex, mangoCache),
|
||||
symbol: marketConfig.baseSymbol,
|
||||
value: +nativeI80F48ToUi(
|
||||
acc.quotePosition,
|
||||
marketConfig.quoteDecimals
|
||||
),
|
||||
type:
|
||||
perpMarket.baseLotsToNumber(acc.basePosition) > 0
|
||||
? 'Long'
|
||||
: 'Short',
|
||||
})
|
||||
}
|
||||
})
|
||||
setPerpPositions(positions.sort((a, b) => b.value - a.value))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const spotPortfolio = []
|
||||
balances.forEach((b) => {
|
||||
const token = getTokenBySymbol(groupConfig, b.symbol)
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
if (+b.marginDeposits > 0) {
|
||||
spotPortfolio.push({
|
||||
market: b.symbol,
|
||||
balance: +b.marginDeposits + b.orders + b.unsettled,
|
||||
borrowRate: mangoGroup
|
||||
.getBorrowRate(tokenIndex)
|
||||
.mul(I80F48.fromNumber(100)),
|
||||
depositRate: mangoGroup
|
||||
.getDepositRate(tokenIndex)
|
||||
.mul(I80F48.fromNumber(100)),
|
||||
price: mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(),
|
||||
symbol: b.symbol,
|
||||
value:
|
||||
(+b.marginDeposits + b.orders + b.unsettled) *
|
||||
mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(),
|
||||
type: 'Deposit',
|
||||
})
|
||||
} else if (+b.borrows > 0) {
|
||||
spotPortfolio.push({
|
||||
market: b.symbol,
|
||||
balance: +b.borrows,
|
||||
borrowRate: mangoGroup
|
||||
.getBorrowRate(tokenIndex)
|
||||
.mul(I80F48.fromNumber(100)),
|
||||
depositRate: mangoGroup
|
||||
.getDepositRate(tokenIndex)
|
||||
.mul(I80F48.fromNumber(100)),
|
||||
price: mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(),
|
||||
symbol: b.symbol,
|
||||
value: b.borrows.mul(mangoGroup.getPrice(tokenIndex, mangoCache)),
|
||||
type: 'Borrow',
|
||||
})
|
||||
} else {
|
||||
spotPortfolio.push({
|
||||
market: b.symbol,
|
||||
balance: 0,
|
||||
borrowRate: mangoGroup
|
||||
.getBorrowRate(tokenIndex)
|
||||
.mul(I80F48.fromNumber(100)),
|
||||
depositRate: mangoGroup
|
||||
.getDepositRate(tokenIndex)
|
||||
.mul(I80F48.fromNumber(100)),
|
||||
price: mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(),
|
||||
symbol: b.symbol,
|
||||
value: 0,
|
||||
type: '–',
|
||||
})
|
||||
}
|
||||
})
|
||||
setSpotPortfolio(spotPortfolio.sort((a, b) => b.value - a.value))
|
||||
setFilteredSpotPortfolio(
|
||||
spotPortfolio
|
||||
.filter((pos) => pos.balance > 0)
|
||||
.sort((a, b) => b.value - a.value)
|
||||
)
|
||||
}, [])
|
||||
|
||||
const handleShowZeroBalances = (checked) => {
|
||||
if (checked) {
|
||||
setFilteredSpotPortfolio(spotPortfolio)
|
||||
} else {
|
||||
setFilteredSpotPortfolio(spotPortfolio.filter((pos) => pos.balance > 0))
|
||||
}
|
||||
setShowZeroBalances(checked)
|
||||
}
|
||||
|
||||
return mangoAccount ? (
|
||||
<>
|
||||
<div className="pb-6">
|
||||
<div className="pb-1 text-th-fgd-3">Account Value</div>
|
||||
<div className="flex items-center">
|
||||
<CurrencyDollarIcon className="flex-shrink-0 h-7 w-7 mr-1.5 text-th-primary" />
|
||||
<StyledAccountValue className="text-th-fgd-1">
|
||||
{usdFormatter.format(
|
||||
+mangoAccount.computeValue(mangoGroup, mangoCache).toFixed(2)
|
||||
)}
|
||||
</StyledAccountValue>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-1 grid-rows-4 sm:grid-cols-2 sm:grid-rows-2 md:grid-cols-4 md:grid-rows-1 gap-4 pb-10">
|
||||
<div className="border border-th-bkg-4 p-4 rounded-lg">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">Positions</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-lg text-th-fgd-1">
|
||||
{spotPortfolio.length > 0
|
||||
? usdFormatter.format(
|
||||
spotPortfolio.reduce(
|
||||
(acc, d) => d.market.includes('PERP') && acc + d.value,
|
||||
0
|
||||
)
|
||||
)
|
||||
: 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-th-bkg-4 p-4 rounded-lg">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">Deposits</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-lg text-th-fgd-1">
|
||||
{usdFormatter.format(
|
||||
+mangoAccount.getAssetsVal(mangoGroup, mangoCache)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-th-bkg-4 p-4 rounded-lg">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">Borrows</div>
|
||||
<div className="flex items-center">
|
||||
<div className="text-lg text-th-fgd-1">
|
||||
{usdFormatter.format(
|
||||
+mangoAccount.getLiabsVal(mangoGroup, mangoCache)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-th-bkg-4 p-4 rounded-lg">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">Health Ratio</div>
|
||||
<div className="flex items-center">
|
||||
<HeartIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-primary" />
|
||||
<div className="text-lg text-th-fgd-1">
|
||||
{mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-8">
|
||||
<div className="pb-4 text-th-fgd-1 text-lg">Perp Positions</div>
|
||||
<PositionsTable />
|
||||
</div>
|
||||
{spotPortfolio.length > 0 ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between pb-4">
|
||||
<div className="text-th-fgd-1 text-lg">Assets</div>
|
||||
<Switch
|
||||
checked={showZeroBalances}
|
||||
className="text-xs"
|
||||
onChange={handleShowZeroBalances}
|
||||
>
|
||||
Show zero balances
|
||||
</Switch>
|
||||
</div>
|
||||
<Table className="min-w-full divide-y divide-th-bkg-2">
|
||||
<Thead>
|
||||
<Tr className="text-th-fgd-3 text-xs">
|
||||
<Th scope="col" className={`px-6 py-2 text-left font-normal`}>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('market')}
|
||||
>
|
||||
Asset
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'market'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-2 text-left font-normal`}>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('type')}
|
||||
>
|
||||
Type
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'type'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-2 text-left font-normal`}>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('balance')}
|
||||
>
|
||||
Balance
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'balance'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className={`px-6 py-2 text-left font-normal`}>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('price')}
|
||||
>
|
||||
Price
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'price'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className="px-6 py-2 text-left font-normal">
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('value')}
|
||||
>
|
||||
Value
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'value'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className="px-6 py-2 text-left font-normal">
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('depositRate')}
|
||||
>
|
||||
Deposit Rate
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'depositRate'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th scope="col" className="px-6 py-2 text-left font-normal">
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('borrowRate')}
|
||||
>
|
||||
Borrow Rate
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition flex-shrink-0 h-4 w-4 ml-1 ${
|
||||
sortConfig?.key === 'borrowRate'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'transform rotate-180'
|
||||
: 'transform rotate-360'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{items.map((pos, i) => (
|
||||
<Tr
|
||||
key={i}
|
||||
className={`border-b border-th-bkg-3
|
||||
${i % 2 === 0 ? `bg-th-bkg-3` : `bg-th-bkg-2`}
|
||||
`}
|
||||
>
|
||||
<Td
|
||||
className={`px-6 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${pos.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<div>{pos.market}</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{pos.type === 'Long' || pos.type === 'Short' ? (
|
||||
<SideBadge side={pos.type} />
|
||||
) : (
|
||||
pos.type
|
||||
)}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{pos.balance > 0
|
||||
? pos.balance.toFixed(tokenPrecision[pos.symbol])
|
||||
: 0}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{usdFormatter.format(pos.price)}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-2 whitespace-nowrap text-sm text-th-fgd-1`}
|
||||
>
|
||||
{usdFormatter.format(pos.value)}
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-2 whitespace-nowrap text-sm text-th-green`}
|
||||
>
|
||||
{pos.depositRate.toFixed(2)}%
|
||||
</Td>
|
||||
<Td
|
||||
className={`px-6 py-2 whitespace-nowrap text-sm text-th-red`}
|
||||
>
|
||||
{pos.borrowRate.toFixed(2)}%
|
||||
</Td>
|
||||
<Td className={`px-6 py-2 flex justify-end`}>
|
||||
<Menu>
|
||||
{({ open }) => (
|
||||
<div className="relative h-full">
|
||||
<Menu.Button className="bg-th-bkg-4 flex items-center justify-center rounded-full w-8 h-8 text-th-fgd-1 focus:outline-none hover:text-th-primary">
|
||||
{open ? (
|
||||
<XIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<DotsHorizontalIcon className="h-5 w-5" />
|
||||
)}
|
||||
</Menu.Button>
|
||||
<Menu.Items className="bg-th-bkg-1 mt-2 p-1 absolute right-0 bottom-10 shadow-lg outline-none rounded-md w-32 z-20">
|
||||
<div className="border-b border-th-bkg-3 flex items-center p-2 text-th-fgd-3 text-xs">
|
||||
<img
|
||||
alt=""
|
||||
width="12"
|
||||
height="12"
|
||||
src={`/assets/icons/${pos.symbol.toLowerCase()}.svg`}
|
||||
className={`mr-1.5`}
|
||||
/>
|
||||
{pos.symbol}
|
||||
</div>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="font-normal rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => console.log('true')}
|
||||
>
|
||||
<div className="text-left">Trade</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="font-normal rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => console.log('true')}
|
||||
>
|
||||
<div className="text-left">Deposit</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="font-normal rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => console.log('true')}
|
||||
>
|
||||
<div className="text-left">Withdraw</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className="font-normal rounded-none w-full p-2 hover:bg-th-bkg-2 hover:cursor-pointer focus:outline-none"
|
||||
onClick={() => console.log('true')}
|
||||
>
|
||||
<div className="text-left">Borrow</div>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</div>
|
||||
)}
|
||||
</Menu>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : null
|
||||
}
|
|
@ -1,14 +1,19 @@
|
|||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
CurrencyDollarIcon,
|
||||
ChartBarIcon,
|
||||
DuplicateIcon,
|
||||
ExternalLinkIcon,
|
||||
ChartPieIcon,
|
||||
LinkIcon,
|
||||
PencilIcon,
|
||||
} from '@heroicons/react/outline'
|
||||
import {
|
||||
getTokenBySymbol,
|
||||
getMarketByPublicKey,
|
||||
nativeI80F48ToUi,
|
||||
PerpMarket,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import useMangoStore from '../stores/useMangoStore'
|
||||
import { useBalances } from '../hooks/useBalances'
|
||||
import { abbreviateAddress, copyToClipboard } from '../utils'
|
||||
import PageBodyContainer from '../components/PageBodyContainer'
|
||||
import TopBar from '../components/TopBar'
|
||||
|
@ -17,18 +22,20 @@ import AccountBorrows from '../components/account-page/AccountBorrows'
|
|||
import AccountOrders from '../components/account-page/AccountOrders'
|
||||
import AccountHistory from '../components/account-page/AccountHistory'
|
||||
import AccountsModal from '../components/AccountsModal'
|
||||
import AccountOverview from '../components/account-page/AccountOverview'
|
||||
import AccountNameModal from '../components/AccountNameModal'
|
||||
import Button from '../components/Button'
|
||||
import EmptyState from '../components/EmptyState'
|
||||
import { MangoAccount } from '@blockworks-foundation/mango-client'
|
||||
|
||||
const TABS = [
|
||||
'Overview',
|
||||
'Assets',
|
||||
'Borrows',
|
||||
// 'Stats',
|
||||
// 'Positions',
|
||||
'Orders',
|
||||
'History',
|
||||
'Activity',
|
||||
]
|
||||
|
||||
export function getMarginInfoString(marginAccount: MangoAccount) {
|
||||
|
@ -43,9 +50,13 @@ export function getMarginInfoString(marginAccount: MangoAccount) {
|
|||
export default function Account() {
|
||||
const [activeTab, setActiveTab] = useState(TABS[0])
|
||||
const [showAccountsModal, setShowAccountsModal] = useState(false)
|
||||
const [portfolio, setPortfolio] = useState([])
|
||||
const allMarkets = useMangoStore((s) => s.selectedMangoGroup.markets)
|
||||
const balances = useBalances()
|
||||
const [showNameModal, setShowNameModal] = useState(false)
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
|
@ -61,6 +72,77 @@ export default function Account() {
|
|||
setShowAccountsModal(false)
|
||||
}, [])
|
||||
|
||||
const perpMarkets = useMemo(
|
||||
() =>
|
||||
mangoGroup
|
||||
? groupConfig.perpMarkets.map(
|
||||
(m) => mangoGroup.perpMarkets[m.marketIndex]
|
||||
)
|
||||
: [],
|
||||
[mangoGroup]
|
||||
)
|
||||
|
||||
const perpAccounts = useMemo(
|
||||
() =>
|
||||
mangoAccount
|
||||
? groupConfig.perpMarkets.map(
|
||||
(m) => mangoAccount.perpAccounts[m.marketIndex]
|
||||
)
|
||||
: [],
|
||||
[mangoAccount]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const portfolio = []
|
||||
perpAccounts.forEach((acc, index) => {
|
||||
const market = perpMarkets[index]
|
||||
const marketConfig = getMarketByPublicKey(groupConfig, market.perpMarket)
|
||||
const perpMarket = allMarkets[
|
||||
marketConfig.publicKey.toString()
|
||||
] as PerpMarket
|
||||
if (
|
||||
+nativeI80F48ToUi(acc.quotePosition, marketConfig.quoteDecimals) > 0
|
||||
) {
|
||||
portfolio.push({
|
||||
market: marketConfig.name,
|
||||
balance: perpMarket.baseLotsToNumber(acc.basePosition),
|
||||
symbol: marketConfig.baseSymbol,
|
||||
value: +nativeI80F48ToUi(
|
||||
acc.quotePosition,
|
||||
marketConfig.quoteDecimals
|
||||
),
|
||||
type:
|
||||
perpMarket.baseLotsToNumber(acc.basePosition) > 0
|
||||
? 'Long'
|
||||
: 'Short',
|
||||
})
|
||||
}
|
||||
})
|
||||
balances.forEach((b) => {
|
||||
const token = getTokenBySymbol(groupConfig, b.symbol)
|
||||
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||
if (+b.marginDeposits > 0) {
|
||||
portfolio.push({
|
||||
market: b.symbol,
|
||||
balance: +b.marginDeposits + b.orders + b.unsettled,
|
||||
symbol: b.symbol,
|
||||
value:
|
||||
(+b.marginDeposits + b.orders + b.unsettled) *
|
||||
mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(),
|
||||
type: 'Deposits',
|
||||
})
|
||||
}
|
||||
if (+b.borrows > 0) {
|
||||
portfolio.push({
|
||||
market: b.symbol,
|
||||
balance: +b.borrows,
|
||||
value: b.borrows.mul(mangoGroup.getPrice(tokenIndex, mangoCache)),
|
||||
type: 'Borrows',
|
||||
})
|
||||
}
|
||||
})
|
||||
setPortfolio(portfolio.sort((a, b) => b.value - a.value))
|
||||
}, [perpAccounts])
|
||||
const handleCopyPublicKey = (code) => {
|
||||
setIsCopied(true)
|
||||
copyToClipboard(code)
|
||||
|
@ -113,7 +195,7 @@ export default function Account() {
|
|||
</div>
|
||||
</Button> */}
|
||||
<a
|
||||
className="border border-th-fgd-4 bg-th-bkg-2 default-transition flex flex-grow font-bold h-8 items-center justify-center pl-3 pr-3 rounded-md text-th-fgd-1 text-xs hover:bg-th-bkg-3 hover:text-th-fgd-1 focus:outline-none"
|
||||
className="bg-th-bkg-4 default-transition flex flex-grow font-bold h-8 items-center justify-center pl-3 pr-3 rounded-full text-th-fgd-1 text-xs hover:bg-th-bkg-3 hover:text-th-fgd-1 focus:outline-none"
|
||||
href={`https://explorer.solana.com/address/${mangoAccount?.publicKey}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
@ -137,65 +219,26 @@ export default function Account() {
|
|||
<div className="bg-th-bkg-2 overflow-none p-6 rounded-lg">
|
||||
{mangoAccount ? (
|
||||
<>
|
||||
<div className="pb-4 text-th-fgd-1 text-lg">Overview</div>
|
||||
<div className="grid grid-flow-col grid-cols-1 grid-rows-4 sm:grid-cols-2 sm:grid-rows-2 md:grid-cols-4 md:grid-rows-1 gap-4 pb-10">
|
||||
<div className="bg-th-bkg-3 p-3 rounded-md">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
Account Value
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<CurrencyDollarIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-primary" />
|
||||
<div className="text-lg text-th-fgd-1">
|
||||
$
|
||||
{mangoAccount
|
||||
.computeValue(mangoGroup, mangoCache)
|
||||
.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-th-bkg-3 p-3 rounded-md">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">Total PnL</div>
|
||||
<div className="flex items-center">
|
||||
<ChartBarIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-primary" />
|
||||
<div className="text-lg text-th-fgd-1">$0.00</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-th-bkg-3 p-3 rounded-md">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3">
|
||||
Health Ratio
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<ChartPieIcon className="flex-shrink-0 h-5 w-5 mr-2 text-th-primary" />
|
||||
<div className="text-lg text-th-fgd-1">
|
||||
{mangoAccount.getHealthRatio(
|
||||
mangoGroup,
|
||||
mangoCache,
|
||||
'Maint'
|
||||
)}
|
||||
%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b border-th-fgd-4 mb-4">
|
||||
<div className="border-b border-th-fgd-4 mb-8">
|
||||
<nav className={`-mb-px flex space-x-6`} aria-label="Tabs">
|
||||
{TABS.map((tabName) => (
|
||||
<a
|
||||
key={tabName}
|
||||
onClick={() => handleTabChange(tabName)}
|
||||
className={`whitespace-nowrap pb-4 px-1 border-b-2 font-semibold cursor-pointer default-transition hover:opacity-100
|
||||
${
|
||||
activeTab === tabName
|
||||
? `border-th-primary text-th-primary`
|
||||
: `border-transparent text-th-fgd-4 hover:text-th-primary`
|
||||
}
|
||||
`}
|
||||
${
|
||||
activeTab === tabName
|
||||
? `border-th-primary text-th-primary`
|
||||
: `border-transparent text-th-fgd-4 hover:text-th-primary`
|
||||
}
|
||||
`}
|
||||
>
|
||||
{tabName}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<TabContent activeTab={activeTab} />
|
||||
</>
|
||||
) : connected ? (
|
||||
|
@ -235,6 +278,8 @@ export default function Account() {
|
|||
|
||||
const TabContent = ({ activeTab }) => {
|
||||
switch (activeTab) {
|
||||
case 'Overview':
|
||||
return <AccountOverview />
|
||||
case 'Assets':
|
||||
return <AccountAssets />
|
||||
case 'Borrows':
|
||||
|
@ -248,6 +293,6 @@ const TabContent = ({ activeTab }) => {
|
|||
case 'History':
|
||||
return <AccountHistory />
|
||||
default:
|
||||
return <AccountAssets />
|
||||
return <AccountOverview />
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
--bkg-1: theme('colors.light-theme["bkg-1"]');
|
||||
--bkg-2: theme('colors.light-theme["bkg-2"]');
|
||||
--bkg-3: theme('colors.light-theme["bkg-3"]');
|
||||
--bkg-4: theme('colors.light-theme["bkg-4"]');
|
||||
--fgd-1: theme('colors.light-theme["fgd-1"]');
|
||||
--fgd-2: theme('colors.light-theme["fgd-2"]');
|
||||
--fgd-3: theme('colors.light-theme["fgd-3"]');
|
||||
|
@ -32,6 +33,7 @@
|
|||
--bkg-1: theme('colors.dark-theme["bkg-1"]');
|
||||
--bkg-2: theme('colors.dark-theme["bkg-2"]');
|
||||
--bkg-3: theme('colors.dark-theme["bkg-3"]');
|
||||
--bkg-4: theme('colors.dark-theme["bkg-4"]');
|
||||
--fgd-1: theme('colors.dark-theme["fgd-1"]');
|
||||
--fgd-2: theme('colors.dark-theme["fgd-2"]');
|
||||
--fgd-3: theme('colors.dark-theme["fgd-3"]');
|
||||
|
@ -49,6 +51,7 @@
|
|||
--bkg-1: theme('colors.mango-theme["bkg-1"]');
|
||||
--bkg-2: theme('colors.mango-theme["bkg-2"]');
|
||||
--bkg-3: theme('colors.mango-theme["bkg-3"]');
|
||||
--bkg-4: theme('colors.mango-theme["bkg-4"]');
|
||||
--fgd-1: theme('colors.mango-theme["fgd-1"]');
|
||||
--fgd-2: theme('colors.mango-theme["fgd-2"]');
|
||||
--fgd-3: theme('colors.mango-theme["fgd-3"]');
|
||||
|
|
|
@ -19,35 +19,35 @@ module.exports = {
|
|||
help: 'help',
|
||||
},
|
||||
colors: {
|
||||
'mango-orange': {
|
||||
DEFAULT: '#DFAB01',
|
||||
dark: '#CB9C01',
|
||||
},
|
||||
'mango-yellow': '#F2C94C',
|
||||
'mango-red': '#E54033',
|
||||
'mango-green': '#AFD803',
|
||||
'mango-dark': {
|
||||
lighter: '#332F46',
|
||||
light: '#262337',
|
||||
DEFAULT: '#141026',
|
||||
},
|
||||
'mango-med': {
|
||||
light: '#C2BDD9',
|
||||
DEFAULT: '#9490A6',
|
||||
dark: '#706C81',
|
||||
},
|
||||
'mango-light': {
|
||||
light: '#FCFCFF',
|
||||
DEFAULT: '#F0EDFF',
|
||||
dark: '#B9B5CE',
|
||||
},
|
||||
'mango-grey': {
|
||||
lighter: '#f7f7f7',
|
||||
light: '#e6e6e6',
|
||||
dark: '#092e34',
|
||||
darker: '#072428',
|
||||
darkest: '#061f23',
|
||||
},
|
||||
// 'mango-orange': {
|
||||
// DEFAULT: '#DFAB01',
|
||||
// dark: '#CB9C01',
|
||||
// },
|
||||
// 'mango-yellow': '#F2C94C',
|
||||
// 'mango-red': '#E54033',
|
||||
// 'mango-green': '#AFD803',
|
||||
// 'mango-dark': {
|
||||
// lighter: '#332F46',
|
||||
// light: '#262337',
|
||||
// DEFAULT: '#141026',
|
||||
// },
|
||||
// 'mango-med': {
|
||||
// light: '#C2BDD9',
|
||||
// DEFAULT: '#9490A6',
|
||||
// dark: '#706C81',
|
||||
// },
|
||||
// 'mango-light': {
|
||||
// light: '#FCFCFF',
|
||||
// DEFAULT: '#F0EDFF',
|
||||
// dark: '#B9B5CE',
|
||||
// },
|
||||
// 'mango-grey': {
|
||||
// lighter: '#f7f7f7',
|
||||
// light: '#e6e6e6',
|
||||
// dark: '#092e34',
|
||||
// darker: '#072428',
|
||||
// darkest: '#061f23',
|
||||
// },
|
||||
'light-theme': {
|
||||
orange: {
|
||||
DEFAULT: '#FF9C24',
|
||||
|
@ -57,7 +57,8 @@ module.exports = {
|
|||
green: { DEFAULT: '#5EBF4D', dark: '#4BA53B' },
|
||||
'bkg-1': '#f7f7f7',
|
||||
'bkg-2': '#FFFFFF',
|
||||
'bkg-3': '#EDEDED',
|
||||
'bkg-3': '#F0F0F0',
|
||||
'bkg-4': '#E6E6E6',
|
||||
'fgd-1': '#061f23',
|
||||
'fgd-2': '#0C3F45',
|
||||
'fgd-3': '#446065',
|
||||
|
@ -71,9 +72,10 @@ module.exports = {
|
|||
red: { DEFAULT: '#CC2929', dark: '#AA2222' },
|
||||
green: { DEFAULT: '#5EBF4D', dark: '#4BA53B' },
|
||||
orange: { DEFAULT: '#FF9C24' },
|
||||
'bkg-1': '#1C1C1C',
|
||||
'bkg-2': '#2B2B2B',
|
||||
'bkg-3': '#424242',
|
||||
'bkg-1': '#101012',
|
||||
'bkg-2': '#1B1B1F',
|
||||
'bkg-3': '#27272B',
|
||||
'bkg-4': '#38383D',
|
||||
'fgd-1': '#FFFFFF',
|
||||
'fgd-2': '#F7F7F7',
|
||||
'fgd-3': '#E7E7E7',
|
||||
|
@ -95,7 +97,8 @@ module.exports = {
|
|||
orange: { DEFAULT: '#FF9C24' },
|
||||
'bkg-1': '#141026',
|
||||
'bkg-2': '#1D1832',
|
||||
'bkg-3': '#322E47',
|
||||
'bkg-3': '#2A2440',
|
||||
'bkg-4': '#37324D',
|
||||
'fgd-1': '#F0EDFF',
|
||||
'fgd-2': '#FCFCFF',
|
||||
'fgd-3': '#B9B5CE',
|
||||
|
@ -104,6 +107,7 @@ module.exports = {
|
|||
'th-bkg-1': 'var(--bkg-1)',
|
||||
'th-bkg-2': 'var(--bkg-2)',
|
||||
'th-bkg-3': 'var(--bkg-3)',
|
||||
'th-bkg-4': 'var(--bkg-4)',
|
||||
'th-fgd-1': 'var(--fgd-1)',
|
||||
'th-fgd-2': 'var(--fgd-2)',
|
||||
'th-fgd-3': 'var(--fgd-3)',
|
||||
|
|
|
@ -298,3 +298,8 @@ export async function getOrderBookAccountInfos(
|
|||
|
||||
return await getMultipleAccounts(DEFAULT_CONNECTION, orderBookPks)
|
||||
}
|
||||
|
||||
export const usdFormatter = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue