mrege main
This commit is contained in:
commit
bc55ef9204
|
@ -35,7 +35,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
}, [width])
|
||||
|
||||
useEffect(() => {
|
||||
const animationFrames = 5
|
||||
const animationFrames = 15
|
||||
|
||||
for (let x = 1; x <= animationFrames; x++) {
|
||||
setTimeout(() => {
|
||||
|
@ -55,7 +55,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
<BounceLoader />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex-grow bg-th-bkg-1 text-th-fgd-1 transition-all">
|
||||
<div className="flex-grow bg-th-bkg-1 text-th-fgd-2 transition-all">
|
||||
<div className="flex">
|
||||
<div className="fixed bottom-0 left-0 z-20 w-full md:hidden">
|
||||
<BottomBar />
|
||||
|
|
|
@ -79,7 +79,7 @@ const MangoAccountsList = ({
|
|||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Popover.Panel className="absolute top-[13.5px] -right-5 z-10 mr-4 w-56 rounded-md rounded-t-none border border-th-bkg-3 bg-th-bkg-1 p-4">
|
||||
<Popover.Panel className="absolute top-[13.5px] -right-5 z-10 mr-4 w-56 rounded-md rounded-t-none border border-th-bkg-2 bg-th-bkg-3 p-4 text-th-fgd-3">
|
||||
{loading ? (
|
||||
<Loading />
|
||||
) : mangoAccounts.length ? (
|
||||
|
@ -87,7 +87,7 @@ const MangoAccountsList = ({
|
|||
<div key={acc.publicKey.toString()}>
|
||||
<button
|
||||
onClick={() => handleSelectMangoAccount(acc)}
|
||||
className="mb-3 flex w-full items-center justify-between border-b border-th-bkg-3 pb-3"
|
||||
className="mb-3 flex w-full items-center justify-between border-b border-th-bkg-4 pb-3 hover:text-th-fgd-1"
|
||||
>
|
||||
{acc.name}
|
||||
{acc.publicKey.toString() ===
|
||||
|
|
|
@ -28,7 +28,7 @@ const OnboardingTour = () => {
|
|||
onClick={handleClose}
|
||||
className={`absolute right-4 top-4 z-50 text-th-bkg-3 focus:outline-none md:right-2 md:top-2 md:hover:text-th-primary`}
|
||||
>
|
||||
<XMarkIcon className={`h-5 w-5`} />
|
||||
<XMarkIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
<h3 className="text-th-bkg-1">{title}</h3>
|
||||
<p className="text-sm text-th-bkg-1">{description}</p>
|
||||
|
|
|
@ -42,7 +42,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`transition-all duration-500 ${
|
||||
className={`transition-all duration-300 ${
|
||||
collapsed ? 'w-[64px]' : 'w-44 lg:w-48 xl:w-52'
|
||||
} border-r border-th-bkg-3 bg-th-bkg-1`}
|
||||
>
|
||||
|
@ -50,7 +50,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
<div className="my-2">
|
||||
<Link href={'/'} shallow={true} passHref>
|
||||
<div
|
||||
className={`h-14 items-center transition-all duration-500 ease-in-out ${
|
||||
className={`h-14 items-center transition-all duration-300 ease-in-out ${
|
||||
collapsed ? '' : 'justify-start'
|
||||
} pb-1 pt-2 pl-4`}
|
||||
>
|
||||
|
@ -323,26 +323,16 @@ export const ExpandableMenuItem = ({
|
|||
{icon}
|
||||
</div>
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
show={showMenu}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-300"
|
||||
enterFrom="opacity-0 scale-90"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="transition ease-out duration-300"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
|
||||
<Popover.Panel
|
||||
className={`absolute z-20 w-56 rounded-md rounded-l-none border border-th-bkg-3 bg-th-bkg-1 py-2 ${
|
||||
alignBottom
|
||||
? 'bottom-0 left-[63px] rounded-b-none border-b-0 p-0'
|
||||
: 'top-1/2 left-[63px] -translate-y-1/2'
|
||||
}`}
|
||||
>
|
||||
<Popover.Panel
|
||||
className={`absolute z-20 w-56 rounded-md rounded-l-none border border-th-bkg-3 bg-th-bkg-1 py-2 ${
|
||||
alignBottom
|
||||
? 'bottom-0 left-[63px] rounded-b-none border-b-0 p-0'
|
||||
: 'top-1/2 left-[63px] -translate-y-1/2'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
{children}
|
||||
</Popover.Panel>
|
||||
</div>
|
||||
</Popover>
|
||||
) : (
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import TabButtons from '../shared/TabButtons'
|
||||
import TokenList from '../TokenList'
|
||||
import SwapHistoryTable from '../swap/SwapHistoryTable'
|
||||
import { useRouter } from 'next/router'
|
||||
import ActivityFeed from './ActivityFeed'
|
||||
|
||||
const TABS = ['balances', 'activity:activity', 'swap:swap-history']
|
||||
|
||||
const AccountTabs = () => {
|
||||
const [activeTab, setActiveTab] = useState('balances')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const { pathname } = useRouter()
|
||||
|
||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||
return TABS.map((t) => [t, 0])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount) {
|
||||
|
@ -23,7 +27,7 @@ const AccountTabs = () => {
|
|||
<TabButtons
|
||||
activeValue={activeTab}
|
||||
onChange={(v) => setActiveTab(v)}
|
||||
values={['balances', 'activity:activity', 'swap:swap-history']}
|
||||
values={tabsWithCount}
|
||||
showBorders
|
||||
/>
|
||||
<TabContent activeTab={activeTab} />
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
ChevronRightIcon,
|
||||
CurrencyDollarIcon as FeesIcon,
|
||||
LightBulbIcon,
|
||||
ArrowsRightLeftIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
|
||||
const StyledBarItemLabel = ({
|
||||
|
@ -58,18 +59,18 @@ const BottomBar = () => {
|
|||
asPath === '/swap' ? 'text-th-primary' : 'text-th-fgd-3'
|
||||
} col-span-1 flex cursor-pointer flex-col items-center`}
|
||||
>
|
||||
<TradeIcon className="mb-1 h-4 w-4" />
|
||||
<StyledBarItemLabel>{t('trade')}</StyledBarItemLabel>
|
||||
<ArrowsRightLeftIcon className="mb-1 h-4 w-4" />
|
||||
<StyledBarItemLabel>{t('swap')}</StyledBarItemLabel>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/stats" shallow={true}>
|
||||
<Link href="/trade" shallow={true}>
|
||||
<a
|
||||
className={`${
|
||||
asPath === '/stats' ? 'text-th-primary' : 'text-th-fgd-3'
|
||||
asPath === '/trade' ? 'text-th-primary' : 'text-th-fgd-3'
|
||||
} col-span-1 flex cursor-pointer flex-col items-center`}
|
||||
>
|
||||
<ChartBarIcon className="mb-1 h-4 w-4" />
|
||||
<StyledBarItemLabel>{t('stats')}</StyledBarItemLabel>
|
||||
<TradeIcon className="mb-1 h-4 w-4" />
|
||||
<StyledBarItemLabel>{t('trade')}</StyledBarItemLabel>
|
||||
</a>
|
||||
</Link>
|
||||
<a
|
||||
|
@ -99,19 +100,24 @@ const MoreMenuPanel = ({
|
|||
const { t } = useTranslation('common')
|
||||
return (
|
||||
<div
|
||||
className={`fixed bottom-0 z-30 h-96 w-full overflow-hidden bg-th-bkg-2 px-4 transition duration-500 ease-in-out ${
|
||||
className={`fixed bottom-0 z-30 h-96 w-full overflow-hidden rounded-t-3xl bg-th-bkg-2 px-4 transition duration-300 ease-in-out ${
|
||||
showPanel ? 'translate-y-0' : 'translate-y-full'
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-end py-4">
|
||||
<IconButton onClick={() => setShowPanel(false)} hideBg>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
<XMarkIcon className="h-6 w-6" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div
|
||||
className="border-b border-th-bkg-4"
|
||||
onClick={() => setShowPanel(false)}
|
||||
>
|
||||
<MoreMenuItem
|
||||
title={t('stats')}
|
||||
path="/stats"
|
||||
icon={<ChartBarIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
title={t('fees')}
|
||||
path="/fees"
|
||||
|
@ -140,7 +146,7 @@ const MoreMenuItem = ({
|
|||
isExternal?: boolean
|
||||
}) => {
|
||||
const classNames =
|
||||
'default-transition flex w-full items-center justify-between border-t border-th-bkg-4 px-2 py-3 text-th-fgd-2 hover:text-th-fgd-1'
|
||||
'default-transition flex w-full items-center justify-between border-t border-th-bkg-4 px-2 py-4 text-th-fgd-2 hover:text-th-fgd-1'
|
||||
return isExternal ? (
|
||||
<a
|
||||
className={classNames}
|
||||
|
|
|
@ -49,15 +49,15 @@ const IconDropMenu = ({
|
|||
}`}
|
||||
disabled={disabled}
|
||||
>
|
||||
{open ? <XMarkIcon className="h-5 w-5" /> : icon}
|
||||
{open ? <XMarkIcon className="h-6 w-6" /> : icon}
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
appear={true}
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-100"
|
||||
enterFrom="opacity-0 scale-90"
|
||||
enterTo="opacity-100 scale-100"
|
||||
enterFrom="scale-90"
|
||||
enterTo="scale-100"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
|
|
|
@ -26,7 +26,7 @@ function Modal({
|
|||
>
|
||||
<div className="min-h-screen px-4 text-center">
|
||||
<Dialog.Overlay
|
||||
className={`fixed inset-0 bg-black opacity-50 ${
|
||||
className={`fixed inset-0 backdrop-blur-sm ${
|
||||
disableOutsideClose ? 'pointer-events-none' : ''
|
||||
}`}
|
||||
/>
|
||||
|
@ -39,7 +39,7 @@ function Modal({
|
|||
onClick={onClose}
|
||||
className={`absolute right-4 top-4 z-50 text-th-fgd-4 focus:outline-none md:right-2 md:top-2 md:hover:text-th-primary`}
|
||||
>
|
||||
<XMarkIcon className={`h-5 w-5`} />
|
||||
<XMarkIcon className={`h-6 w-6`} />
|
||||
</button>
|
||||
) : null}
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
|
|
|
@ -106,8 +106,10 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
hideNotification()
|
||||
}
|
||||
},
|
||||
parsedTitle || type === 'confirm' || type === 'error'
|
||||
parsedTitle || type === 'confirm'
|
||||
? CLIENT_TX_TIMEOUT
|
||||
: type === 'error'
|
||||
? 30000
|
||||
: 8000
|
||||
)
|
||||
|
||||
|
@ -151,7 +153,9 @@ const Notification = ({ notification }: { notification: Notification }) => {
|
|||
{parsedTitle || title}
|
||||
</p>
|
||||
{description ? (
|
||||
<p className={`mb-0 mt-0.5 leading-tight text-th-fgd-3`}>
|
||||
<p
|
||||
className={`mb-0 mt-0.5 break-all text-sm leading-tight text-th-fgd-3`}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { FunctionComponent } from 'react'
|
|||
interface TabButtonsProps {
|
||||
activeValue: string
|
||||
onChange: (x: any) => void
|
||||
values: Array<any>
|
||||
values: [string, number][]
|
||||
showBorders?: boolean
|
||||
rounded?: boolean
|
||||
fillWidth?: boolean
|
||||
|
@ -26,20 +26,29 @@ const TabButtons: FunctionComponent<TabButtonsProps> = ({
|
|||
showBorders ? 'border-b border-th-bkg-3' : ''
|
||||
}`}
|
||||
>
|
||||
{values.map((v, i) => (
|
||||
<div className={fillWidth ? 'flex-1' : ''} key={v + i}>
|
||||
{values.map(([label, count], i) => (
|
||||
<div className={fillWidth ? 'flex-1' : ''} key={label + i}>
|
||||
<button
|
||||
className={`default-transition h-12 w-full px-6 font-bold ${
|
||||
className={`default-transition flex h-12 w-full items-center justify-center px-6 font-bold ${
|
||||
rounded ? 'rounded-md' : 'rounded-none'
|
||||
} ${showBorders ? 'border-r border-th-bkg-3' : ''} ${
|
||||
v === activeValue
|
||||
label === activeValue
|
||||
? 'bg-th-bkg-3 text-th-primary'
|
||||
: 'hover:cursor-pointer hover:text-th-fgd-2'
|
||||
}`}
|
||||
key={`${v}${i}`}
|
||||
onClick={() => onChange(v)}
|
||||
key={`${label}${i}`}
|
||||
onClick={() => onChange(label)}
|
||||
>
|
||||
{t(v)}
|
||||
<span className="">{t(label)} </span>
|
||||
{count ? (
|
||||
<div
|
||||
className={`ml-1.5 rounded ${
|
||||
label === activeValue ? 'bg-th-bkg-4' : 'bg-th-bkg-3'
|
||||
} px-1.5 font-mono text-xxs`}
|
||||
>
|
||||
{count}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Transition } from '@headlessui/react'
|
||||
import { CSSProperties, ReactNode } from 'react'
|
||||
|
||||
const transitionEnterStyle = 'transition-all ease-in duration-300'
|
||||
const transitionExitStyle = 'transition-all ease-out duration-300'
|
||||
const transitionEnterStyle = 'transition-all ease-out duration-500'
|
||||
const transitionExitStyle = 'transition-all ease-in duration-300'
|
||||
|
||||
export const EnterRightExitLeft = ({
|
||||
children,
|
||||
|
|
|
@ -287,7 +287,7 @@ const SwapForm = () => {
|
|||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<div className="-mb-2 flex justify-center">
|
||||
<button
|
||||
className="rounded-full border border-th-bkg-4 p-1.5 text-th-fgd-3 md:hover:text-th-primary"
|
||||
onClick={handleSwitchTokens}
|
||||
|
|
|
@ -106,19 +106,19 @@ const SwapFormTokenList = ({
|
|||
const [search, setSearch] = useState('')
|
||||
const tokens = mangoStore.getState().jupiterTokens
|
||||
const walletTokens = mangoStore((s) => s.wallet.tokens)
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
// const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
const inputBank = mangoStore((s) => s.swap.inputBank)
|
||||
const outputBank = mangoStore((s) => s.swap.outputBank)
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const group = mangoStore((s) => s.group)
|
||||
|
||||
const popularTokens = useMemo(() => {
|
||||
return tokens.filter((token) => {
|
||||
return !token?.name || !token?.symbol
|
||||
? false
|
||||
: popularTokenSymbols.includes(token.symbol)
|
||||
})
|
||||
}, [tokens])
|
||||
// const popularTokens = useMemo(() => {
|
||||
// return tokens.filter((token) => {
|
||||
// return !token?.name || !token?.symbol
|
||||
// ? false
|
||||
// : popularTokenSymbols.includes(token.symbol)
|
||||
// })
|
||||
// }, [tokens])
|
||||
|
||||
useEffect(() => {
|
||||
function onEscape(e: any) {
|
||||
|
@ -170,11 +170,12 @@ const SwapFormTokenList = ({
|
|||
}
|
||||
}, [tokens, walletTokens, inputBank, outputBank, mangoAccount, group])
|
||||
|
||||
const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value)
|
||||
}
|
||||
// const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
// setSearch(e.target.value)
|
||||
// }
|
||||
|
||||
const sortedTokens = search ? startSearch(tokenInfos, search) : tokenInfos
|
||||
// const sortedTokens = search ? startSearch(tokenInfos, search) : tokenInfos
|
||||
const sortedTokens = tokenInfos
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -184,7 +185,7 @@ const SwapFormTokenList = ({
|
|||
: `${t('swap')} ${t('swap:to')}`}
|
||||
</p>
|
||||
<IconButton className="absolute top-2 right-2" onClick={onClose} hideBg>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
<XMarkIcon className="h-6 w-6" />
|
||||
</IconButton>
|
||||
{/* No need for search/popular tokens until we have more tokens */}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ const SwapSettings = ({ onClose }: { onClose: () => void }) => {
|
|||
<>
|
||||
<h3 className="mb-3">{t('settings')}</h3>
|
||||
<IconButton className="absolute top-2 right-2" onClick={onClose} hideBg>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
<XMarkIcon className="h-6 w-6" />
|
||||
</IconButton>
|
||||
|
||||
<div className="mt-4">
|
||||
|
|
|
@ -52,7 +52,7 @@ const MarketSelectDropdown = () => {
|
|||
<div className="relative flex flex-col overflow-visible">
|
||||
<Popover.Button className="default-transition flex w-full items-center justify-between hover:text-th-primary">
|
||||
<MarketLogos baseURI={baseLogoURI} quoteURI={quoteLogoURI} />
|
||||
<div className="text-xl font-bold md:text-base">
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-base">
|
||||
{selectedMarket?.name || DEFAULT_MARKET_NAME}
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
|
@ -122,7 +122,7 @@ const OraclePrice = () => {
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="font-mono text-xs text-th-fgd-1">
|
||||
<div className="font-mono text-xs text-th-fgd-2">
|
||||
$
|
||||
{baseTokenBank.uiPrice
|
||||
? formatFixedDecimals(baseTokenBank.uiPrice)
|
||||
|
|
|
@ -19,6 +19,11 @@ import { notify } from 'utils/notifications'
|
|||
import SpotSlider from './SpotSlider'
|
||||
import { calculateMarketPrice } from 'utils/tradeForm'
|
||||
|
||||
const TABS: [string, number][] = [
|
||||
['Limit', 0],
|
||||
['Market', 0],
|
||||
]
|
||||
|
||||
const AdvancedTradeForm = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const set = mangoStore.getState().set
|
||||
|
@ -201,7 +206,7 @@ const AdvancedTradeForm = () => {
|
|||
<TabButtons
|
||||
activeValue={tradeForm.tradeType}
|
||||
onChange={(tab: 'Limit' | 'Market') => setTradeType(tab)}
|
||||
values={['Limit', 'Market']}
|
||||
values={TABS}
|
||||
fillWidth
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,18 +1,8 @@
|
|||
import { useCallback, useEffect, useState } from 'react'
|
||||
// import { useTranslation } from 'next-i18next'
|
||||
import dynamic from 'next/dynamic'
|
||||
import ReactGridLayout, { Responsive, WidthProvider } from 'react-grid-layout'
|
||||
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { GRID_LAYOUT_KEY } from 'utils/constants'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import Orderbook from './Orderbook'
|
||||
import AdvancedMarketHeader from './AdvancedMarketHeader'
|
||||
import AdvancedTradeForm from './AdvancedTradeForm'
|
||||
import BalanceAndOpenOrders from './BalanceAndOpenOrders'
|
||||
import TradingViewChart from './TradingViewChart'
|
||||
import TradeInfoTabs from './TradeInfoTabs'
|
||||
|
||||
const MobileTradeAdvancedPage = () => {
|
||||
return (
|
||||
|
@ -30,7 +20,7 @@ const MobileTradeAdvancedPage = () => {
|
|||
<Orderbook />
|
||||
</div>
|
||||
<div className="col-span-2 border-t border-th-bkg-3 sm:col-span-3">
|
||||
<BalanceAndOpenOrders />
|
||||
<TradeInfoTabs />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,140 +1,18 @@
|
|||
import { Serum3Side } from '@blockworks-foundation/mango-v4'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
import TabButtons from '@components/shared/TabButtons'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import {
|
||||
LinkIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
TrashIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { LinkIcon, TrashIcon } from '@heroicons/react/20/solid'
|
||||
import { Order } from '@project-serum/serum/lib/market'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/image'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
import MarketLogos from './MarketLogos'
|
||||
|
||||
const TABS = ['Balances', 'Orders']
|
||||
|
||||
const BalanceAndOpenOrders = () => {
|
||||
const [selectedTab, setSelectedTab] = useState('Balances')
|
||||
|
||||
return (
|
||||
<div className="hide-scroll h-full overflow-y-scroll">
|
||||
<div className="sticky top-0 z-10">
|
||||
<TabButtons
|
||||
activeValue={selectedTab}
|
||||
onChange={(tab: string) => setSelectedTab(tab)}
|
||||
values={TABS}
|
||||
showBorders
|
||||
/>
|
||||
</div>
|
||||
{selectedTab === 'Balances' ? <Balances /> : null}
|
||||
{selectedTab === 'Orders' ? <OpenOrders /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Balances = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
|
||||
const group = mangoStore((s) => s.group)
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
const sortedBanks = mangoAccount
|
||||
? rawBanks.sort(
|
||||
(a, b) =>
|
||||
Math.abs(
|
||||
mangoAccount?.getTokenBalanceUi(b.value[0]) *
|
||||
b.value[0].uiPrice!
|
||||
) -
|
||||
Math.abs(
|
||||
mangoAccount?.getTokenBalanceUi(a.value[0]) *
|
||||
a.value[0].uiPrice!
|
||||
)
|
||||
)
|
||||
: rawBanks
|
||||
|
||||
return mangoAccount
|
||||
? sortedBanks.filter(
|
||||
(b) => mangoAccount?.getTokenBalanceUi(b.value[0]) !== 0
|
||||
)
|
||||
: sortedBanks
|
||||
}
|
||||
return []
|
||||
}, [group, mangoAccount])
|
||||
|
||||
return (
|
||||
<table className="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="bg-th-bkg-1 text-left">{t('token')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('balance')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('in-orders')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('unsettled')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
|
||||
let logoURI
|
||||
if (jupiterTokens.length) {
|
||||
logoURI = jupiterTokens.find(
|
||||
(t) => t.address === bank.mint.toString()
|
||||
)!.logoURI
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={key} className="text-sm">
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
{logoURI ? (
|
||||
<Image alt="" width="20" height="20" src={logoURI} />
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-7 w-7 text-th-fgd-3" />
|
||||
)}
|
||||
</div>
|
||||
<span>{bank.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="pt-4 text-right font-mono">
|
||||
<div>
|
||||
{mangoAccount
|
||||
? formatDecimal(
|
||||
mangoAccount.getTokenBalanceUi(bank),
|
||||
bank.mintDecimals
|
||||
)
|
||||
: 0}
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-right font-mono">
|
||||
{spotBalances[bank.mint.toString()]?.inOrders || 0.0}
|
||||
</td>
|
||||
<td className="text-right font-mono">
|
||||
{spotBalances[bank.mint.toString()]?.unsettled || 0.0}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
const OpenOrders = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { connected } = useWallet()
|
||||
|
@ -271,4 +149,4 @@ const OpenOrders = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export default BalanceAndOpenOrders
|
||||
export default OpenOrders
|
|
@ -72,6 +72,7 @@ type cumOrderbookSide = {
|
|||
cumulativeSize: number
|
||||
sizePercent: number
|
||||
maxSizePercent: number
|
||||
cumulativeSizePercent: number
|
||||
}
|
||||
|
||||
const getCumulativeOrderbookSide = (
|
||||
|
@ -79,7 +80,7 @@ const getCumulativeOrderbookSide = (
|
|||
totalSize: number,
|
||||
maxSize: number,
|
||||
depth: number,
|
||||
backwards = false
|
||||
isBids = true
|
||||
): cumOrderbookSide[] => {
|
||||
let cumulative = orders
|
||||
.slice(0, depth)
|
||||
|
@ -90,13 +91,15 @@ const getCumulativeOrderbookSide = (
|
|||
size,
|
||||
cumulativeSize,
|
||||
sizePercent: Math.round((cumulativeSize / (totalSize || 1)) * 100),
|
||||
cumulativeSizePercent: Math.round((size / (cumulativeSize || 1)) * 100),
|
||||
maxSizePercent: Math.round((size / (maxSize || 1)) * 100),
|
||||
})
|
||||
return cumulative
|
||||
}, [])
|
||||
if (backwards) {
|
||||
cumulative = cumulative.reverse()
|
||||
if (!isBids) {
|
||||
console.log('cumulative', cumulative)
|
||||
}
|
||||
|
||||
return cumulative
|
||||
}
|
||||
|
||||
|
@ -245,30 +248,30 @@ const Orderbook = () => {
|
|||
|
||||
const sum = (total: number, [, size]: number[], index: number) =>
|
||||
index < depth ? total + size : total
|
||||
const totalBidSize = bids.reduce(sum, 0)
|
||||
const totalAskSize = asks.reduce(sum, 0)
|
||||
const maxBidSize = Math.max(
|
||||
...bids.map((b: number[]) => {
|
||||
return b[1]
|
||||
})
|
||||
)
|
||||
const maxAskSize = Math.max(
|
||||
...asks.map((a: number[]) => {
|
||||
return a[1]
|
||||
})
|
||||
)
|
||||
const totalSize = bids.reduce(sum, 0) + asks.reduce(sum, 0)
|
||||
|
||||
const maxSize =
|
||||
Math.max(
|
||||
...bids.map((b: number[]) => {
|
||||
return b[1]
|
||||
})
|
||||
) +
|
||||
Math.max(
|
||||
...asks.map((a: number[]) => {
|
||||
return a[1]
|
||||
})
|
||||
)
|
||||
|
||||
const bidsToDisplay = getCumulativeOrderbookSide(
|
||||
bids,
|
||||
totalBidSize,
|
||||
maxBidSize,
|
||||
depth,
|
||||
false
|
||||
totalSize,
|
||||
maxSize,
|
||||
depth
|
||||
)
|
||||
const asksToDisplay = getCumulativeOrderbookSide(
|
||||
asks,
|
||||
totalAskSize,
|
||||
maxAskSize,
|
||||
totalSize,
|
||||
maxSize,
|
||||
depth,
|
||||
false
|
||||
)
|
||||
|
@ -444,6 +447,9 @@ const Orderbook = () => {
|
|||
size={orderbookData?.asks[index].size}
|
||||
side="sell"
|
||||
sizePercent={orderbookData?.asks[index].sizePercent}
|
||||
cumulativeSizePercent={
|
||||
orderbookData?.asks[index].cumulativeSizePercent
|
||||
}
|
||||
grouping={grouping}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -480,6 +486,9 @@ const Orderbook = () => {
|
|||
size={orderbookData?.bids[index].size}
|
||||
side="buy"
|
||||
sizePercent={orderbookData?.bids[index].sizePercent}
|
||||
cumulativeSizePercent={
|
||||
orderbookData?.bids[index].cumulativeSizePercent
|
||||
}
|
||||
grouping={grouping}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -499,6 +508,7 @@ const OrderbookRow = ({
|
|||
// invert,
|
||||
// hasOpenOrder,
|
||||
minOrderSize,
|
||||
cumulativeSizePercent,
|
||||
tickSize,
|
||||
grouping,
|
||||
}: {
|
||||
|
@ -506,6 +516,7 @@ const OrderbookRow = ({
|
|||
price: number
|
||||
size: number
|
||||
sizePercent: number
|
||||
cumulativeSizePercent: number
|
||||
// hasOpenOrder: boolean
|
||||
// invert: boolean
|
||||
grouping: number
|
||||
|
@ -573,11 +584,11 @@ const OrderbookRow = ({
|
|||
onClick={handlePriceClick}
|
||||
>
|
||||
<>
|
||||
<div className="flex w-full items-center justify-between hover:bg-th-bkg-2">
|
||||
<div className="flex w-full items-center justify-between text-th-fgd-3 hover:bg-th-bkg-2">
|
||||
<div className="flex w-full justify-start pl-2">
|
||||
<div
|
||||
style={{ fontFeatureSettings: 'zero 1' }}
|
||||
className={`z-10 w-full text-right font-mono text-xs leading-5 md:leading-6 ${
|
||||
className={`z-10 w-full text-right font-mono text-xs ${
|
||||
/*hasOpenOrder*/ false ? 'text-th-primary' : ''
|
||||
}`}
|
||||
// onClick={handleSizeClick}
|
||||
|
@ -585,18 +596,24 @@ const OrderbookRow = ({
|
|||
{formattedSize.toFixed(minOrderSizeDecimals)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`z-10 w-full pr-4 text-right font-mono text-xs leading-5 md:leading-6`}
|
||||
>
|
||||
<div className={`z-10 w-full pr-4 text-right font-mono text-xs`}>
|
||||
{formattedPrice.toFixed(groupingDecimalCount)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Line
|
||||
className={`absolute left-0 opacity-90 ${
|
||||
className={`absolute left-0 opacity-40 brightness-125 ${
|
||||
side === 'buy' ? `bg-th-green-muted` : `bg-th-red-muted`
|
||||
}`}
|
||||
data-width={sizePercent + '%'}
|
||||
data-width={Math.max(sizePercent, 0.5) + '%'}
|
||||
/>
|
||||
<Line
|
||||
className={`absolute left-0 opacity-70 ${
|
||||
side === 'buy' ? `bg-th-green` : `bg-th-red`
|
||||
}`}
|
||||
data-width={
|
||||
Math.max((cumulativeSizePercent / 100) * sizePercent, 0.1) + '%'
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,11 @@ import { useState } from 'react'
|
|||
import Orderbook from './Orderbook'
|
||||
import RecentTrades from './RecentTrades'
|
||||
|
||||
const TABS: [string, number][] = [
|
||||
['book', 0],
|
||||
['trades', 0],
|
||||
]
|
||||
|
||||
const OrderbookAndTrades = () => {
|
||||
const [activeTab, setActiveTab] = useState('book')
|
||||
return (
|
||||
|
@ -11,7 +16,7 @@ const OrderbookAndTrades = () => {
|
|||
<TabButtons
|
||||
activeValue={activeTab}
|
||||
onChange={(tab: string) => setActiveTab(tab)}
|
||||
values={['book', 'trades']}
|
||||
values={TABS}
|
||||
fillWidth
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -30,17 +30,21 @@ const RecentTrades = () => {
|
|||
const fetchTradesForChart = useCallback(async () => {
|
||||
if (!selectedMarketPk) return
|
||||
|
||||
const response = await fetch(
|
||||
`https://event-history-api-candles.herokuapp.com/trades/address/${selectedMarketPk}`
|
||||
)
|
||||
const parsedResp = await response.json()
|
||||
const newTrades = parsedResp.data
|
||||
if (!newTrades) return null
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://event-history-api-candles.herokuapp.com/trades/address/${selectedMarketPk}`
|
||||
)
|
||||
const parsedResp = await response.json()
|
||||
const newTrades = parsedResp.data
|
||||
if (!newTrades) return null
|
||||
|
||||
if (newTrades.length && trades.length === 0) {
|
||||
setTrades(newTrades)
|
||||
} else if (newTrades?.length && !isEqual(newTrades[0], trades[0])) {
|
||||
setTrades(newTrades)
|
||||
if (newTrades.length && trades.length === 0) {
|
||||
setTrades(newTrades)
|
||||
} else if (newTrades?.length && !isEqual(newTrades[0], trades[0])) {
|
||||
setTrades(newTrades)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Unable to fetch recent trades', e)
|
||||
}
|
||||
}, [selectedMarketPk, trades])
|
||||
|
||||
|
@ -92,7 +96,7 @@ const RecentTrades = () => {
|
|||
<div className={`text-right text-th-fgd-2`}>
|
||||
{formattedSize.toFixed()}
|
||||
</div>
|
||||
<div className={`text-right text-th-fgd-4`}>
|
||||
<div className={`text-right tracking-tighter text-th-fgd-4`}>
|
||||
{trade.time && new Date(trade.time).toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
// import { useTranslation } from 'next-i18next'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import ReactGridLayout, { Responsive, WidthProvider } from 'react-grid-layout'
|
||||
|
||||
|
@ -8,10 +7,9 @@ import { GRID_LAYOUT_KEY } from 'utils/constants'
|
|||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import Orderbook from './Orderbook'
|
||||
import AdvancedMarketHeader from './AdvancedMarketHeader'
|
||||
import AdvancedTradeForm from './AdvancedTradeForm'
|
||||
import BalanceAndOpenOrders from './BalanceAndOpenOrders'
|
||||
import TradeInfoTabs from './TradeInfoTabs'
|
||||
import MobileTradeAdvancedPage from './MobileTradeAdvancedPage'
|
||||
import OrderbookAndTrades from './OrderbookAndTrades'
|
||||
|
||||
|
@ -60,13 +58,13 @@ const TradeAdvancedPage = () => {
|
|||
return {
|
||||
xxxl: [
|
||||
{ i: 'market-header', x: 0, y: 0, w: 16, h: marketHeaderHeight },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 16, h: 676 },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 16, h: 640 },
|
||||
{
|
||||
i: 'balances',
|
||||
x: 0,
|
||||
y: 2,
|
||||
w: 16,
|
||||
h: getHeight(innerHeight, 300, 676 + marketHeaderHeight),
|
||||
h: getHeight(innerHeight, 300, 640 + marketHeaderHeight),
|
||||
},
|
||||
{
|
||||
i: 'orderbook',
|
||||
|
@ -79,13 +77,13 @@ const TradeAdvancedPage = () => {
|
|||
],
|
||||
xxl: [
|
||||
{ i: 'market-header', x: 0, y: 0, w: 15, h: marketHeaderHeight },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 15, h: 576 },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 15, h: 536 },
|
||||
{
|
||||
i: 'balances',
|
||||
x: 0,
|
||||
y: 2,
|
||||
w: 15,
|
||||
h: getHeight(innerHeight, 300, 576 + marketHeaderHeight),
|
||||
h: getHeight(innerHeight, 300, 536 + marketHeaderHeight),
|
||||
},
|
||||
{
|
||||
i: 'orderbook',
|
||||
|
@ -98,13 +96,13 @@ const TradeAdvancedPage = () => {
|
|||
],
|
||||
xl: [
|
||||
{ i: 'market-header', x: 0, y: 0, w: 14, h: marketHeaderHeight },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 14, h: 520 },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 14, h: 488 },
|
||||
{
|
||||
i: 'balances',
|
||||
x: 0,
|
||||
y: 2,
|
||||
w: 14,
|
||||
h: getHeight(innerHeight, 300, 520 + marketHeaderHeight),
|
||||
h: getHeight(innerHeight, 300, 488 + marketHeaderHeight),
|
||||
},
|
||||
{
|
||||
i: 'orderbook',
|
||||
|
@ -117,13 +115,13 @@ const TradeAdvancedPage = () => {
|
|||
],
|
||||
lg: [
|
||||
{ i: 'market-header', x: 0, y: 0, w: 14, h: marketHeaderHeight },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 14, h: 520 },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 14, h: 488 },
|
||||
{
|
||||
i: 'balances',
|
||||
x: 0,
|
||||
y: 2,
|
||||
w: 14,
|
||||
h: getHeight(innerHeight, 300, 520 + marketHeaderHeight),
|
||||
h: getHeight(innerHeight, 300, 488 + marketHeaderHeight),
|
||||
},
|
||||
{
|
||||
i: 'orderbook',
|
||||
|
@ -136,10 +134,10 @@ const TradeAdvancedPage = () => {
|
|||
],
|
||||
md: [
|
||||
{ i: 'market-header', x: 0, y: 0, w: 18, h: marketHeaderHeight },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 18, h: 520 },
|
||||
{ i: 'tv-chart', x: 0, y: 1, w: 18, h: 488 },
|
||||
{ i: 'balances', x: 0, y: 2, w: 18, h: 488 },
|
||||
{ i: 'orderbook', x: 18, y: 2, w: 6, h: 489 },
|
||||
{ i: 'trade-form', x: 18, y: 1, w: 6, h: 568 },
|
||||
{ i: 'orderbook', x: 18, y: 2, w: 6, h: 488 },
|
||||
{ i: 'trade-form', x: 18, y: 1, w: 6, h: 488 },
|
||||
],
|
||||
}
|
||||
}, [height])
|
||||
|
@ -156,6 +154,8 @@ const TradeAdvancedPage = () => {
|
|||
}, [])
|
||||
|
||||
const onBreakpointChange = useCallback((newBreakpoint: string) => {
|
||||
console.log('newBreakpoints', newBreakpoint)
|
||||
|
||||
setCurrentBreakpoint(newBreakpoint)
|
||||
}, [])
|
||||
|
||||
|
@ -193,7 +193,7 @@ const TradeAdvancedPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div key="balances">
|
||||
<BalanceAndOpenOrders />
|
||||
<TradeInfoTabs />
|
||||
</div>
|
||||
<div
|
||||
key="trade-form"
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/image'
|
||||
import { useMemo } from 'react'
|
||||
import { formatDecimal } from 'utils/numbers'
|
||||
|
||||
const Balances = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
|
||||
const group = mangoStore((s) => s.group)
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
const sortedBanks = mangoAccount
|
||||
? rawBanks.sort(
|
||||
(a, b) =>
|
||||
Math.abs(
|
||||
mangoAccount?.getTokenBalanceUi(b.value[0]) *
|
||||
b.value[0].uiPrice!
|
||||
) -
|
||||
Math.abs(
|
||||
mangoAccount?.getTokenBalanceUi(a.value[0]) *
|
||||
a.value[0].uiPrice!
|
||||
)
|
||||
)
|
||||
: rawBanks
|
||||
|
||||
return mangoAccount
|
||||
? sortedBanks.filter(
|
||||
(b) => mangoAccount?.getTokenBalanceUi(b.value[0]) !== 0
|
||||
)
|
||||
: sortedBanks
|
||||
}
|
||||
return []
|
||||
}, [group, mangoAccount])
|
||||
|
||||
return (
|
||||
<table className="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="bg-th-bkg-1 text-left">{t('token')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('balance')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('in-orders')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('unsettled')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
|
||||
let logoURI
|
||||
if (jupiterTokens.length) {
|
||||
logoURI = jupiterTokens.find(
|
||||
(t) => t.address === bank.mint.toString()
|
||||
)!.logoURI
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={key} className="text-sm">
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<div className="mr-2.5 flex flex-shrink-0 items-center">
|
||||
{logoURI ? (
|
||||
<Image alt="" width="20" height="20" src={logoURI} />
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-7 w-7 text-th-fgd-3" />
|
||||
)}
|
||||
</div>
|
||||
<span>{bank.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="pt-4 text-right font-mono">
|
||||
<div>
|
||||
{mangoAccount
|
||||
? formatDecimal(
|
||||
mangoAccount.getTokenBalanceUi(bank),
|
||||
bank.mintDecimals
|
||||
)
|
||||
: 0}
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-right font-mono">
|
||||
{spotBalances[bank.mint.toString()]?.inOrders || 0.0}
|
||||
</td>
|
||||
<td className="text-right font-mono">
|
||||
{spotBalances[bank.mint.toString()]?.unsettled || 0.0}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
export default Balances
|
|
@ -0,0 +1,37 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
import TabButtons from '@components/shared/TabButtons'
|
||||
import OpenOrders from './OpenOrders'
|
||||
import Balances from './TradeBalances'
|
||||
import UnsettledTrades from './UnsettledTrades'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
|
||||
const TradeInfoTabs = () => {
|
||||
const [selectedTab, setSelectedTab] = useState('Balances')
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
|
||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||
return [
|
||||
['Balances', 0],
|
||||
['Orders', Object.values(openOrders).flat().length],
|
||||
['Unsettled', 0],
|
||||
]
|
||||
}, [openOrders])
|
||||
|
||||
return (
|
||||
<div className="hide-scroll h-full overflow-y-scroll">
|
||||
<div className="sticky top-0 z-10">
|
||||
<TabButtons
|
||||
activeValue={selectedTab}
|
||||
onChange={(tab: string) => setSelectedTab(tab)}
|
||||
values={tabsWithCount}
|
||||
showBorders
|
||||
/>
|
||||
</div>
|
||||
{selectedTab === 'Balances' ? <Balances /> : null}
|
||||
{selectedTab === 'Orders' ? <OpenOrders /> : null}
|
||||
{selectedTab === 'Unsettled' ? <UnsettledTrades /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TradeInfoTabs
|
|
@ -138,6 +138,7 @@ const TradingViewChart = () => {
|
|||
'header_interval_dialog_button',
|
||||
'show_interval_dialog_on_key_press',
|
||||
'header_symbol_search',
|
||||
'popup_hints',
|
||||
],
|
||||
fullscreen: defaultProps.fullscreen,
|
||||
autosize: defaultProps.autosize,
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
|
||||
import Button from '@components/shared/Button'
|
||||
import { notify } from 'utils/notifications'
|
||||
|
||||
const UnsettledTrades = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const openOrdersAccounts =
|
||||
mangoStore.getState().mangoAccount.openOrderAccounts
|
||||
const group = mangoStore((s) => s.group)
|
||||
// const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
|
||||
const unsettledSpotBalances = useMemo(() => {
|
||||
if (!group || !mangoAccount) return {}
|
||||
const unsettledBalances: Record<string, { base: number; quote: number }> =
|
||||
{}
|
||||
mangoAccount.serum3Active().forEach((serumMarket) => {
|
||||
const market = group.getSerum3MarketByIndex(serumMarket.marketIndex)!
|
||||
const openOrdersAccForMkt = openOrdersAccounts.find((oo) =>
|
||||
oo.market.equals(market.serumMarketExternal)
|
||||
)
|
||||
const baseTokenUnsettled = toUiDecimals(
|
||||
openOrdersAccForMkt!.baseTokenFree.toNumber(),
|
||||
group.getFirstBankByTokenIndex(serumMarket.baseTokenIndex).mintDecimals
|
||||
)
|
||||
const quoteTokenUnsettled = toUiDecimals(
|
||||
openOrdersAccForMkt!.quoteTokenFree
|
||||
// @ts-ignore
|
||||
.add(openOrdersAccForMkt['referrerRebatesAccrued'])
|
||||
.toNumber(),
|
||||
group.getFirstBankByTokenIndex(serumMarket.quoteTokenIndex).mintDecimals
|
||||
)
|
||||
unsettledBalances[market.serumMarketExternal.toString()] = {
|
||||
base: baseTokenUnsettled,
|
||||
quote: quoteTokenUnsettled,
|
||||
}
|
||||
})
|
||||
|
||||
const filtered = Object.entries(unsettledBalances).filter(
|
||||
([_mkt, balance]) => balance.base > 0 || balance.quote > 0
|
||||
)
|
||||
return Object.fromEntries(filtered)!
|
||||
}, [mangoAccount, group, openOrdersAccounts])
|
||||
|
||||
const handleSettleFunds = useCallback(async (mktAddress: string) => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const actions = mangoStore.getState().actions
|
||||
|
||||
if (!group || !mangoAccount) return
|
||||
|
||||
try {
|
||||
const txid = await client.serum3SettleFunds(
|
||||
group,
|
||||
mangoAccount,
|
||||
new PublicKey(mktAddress)
|
||||
)
|
||||
actions.fetchSerumOpenOrders()
|
||||
actions.reloadMangoAccount()
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Successfully settled funds',
|
||||
txid,
|
||||
})
|
||||
} catch (e: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: 'Settle transaction failed',
|
||||
description: e?.message,
|
||||
txid: e?.txid,
|
||||
})
|
||||
console.error('Settle funds error:', e)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!group) return null
|
||||
|
||||
// console.log('unsettledSpotBalances', unsettledSpotBalances)
|
||||
|
||||
return (
|
||||
<table className="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="bg-th-bkg-1 text-left">Market</th>
|
||||
<th className="bg-th-bkg-1 text-right">Base</th>
|
||||
<th className="bg-th-bkg-1 text-right">Quote</th>
|
||||
<th className="bg-th-bkg-1 text-right"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(unsettledSpotBalances).map(([mktAddress, balance]) => {
|
||||
const market = group.getSerum3MarketByPk(new PublicKey(mktAddress))
|
||||
console.log('market', mktAddress)
|
||||
const base = market?.name.split('/')[0]
|
||||
const quote = market?.name.split('/')[1]
|
||||
|
||||
return (
|
||||
<tr key={mktAddress} className="text-sm">
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>{market ? market.name : ''}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-right font-mono">
|
||||
{unsettledSpotBalances[mktAddress].base || 0.0} {base}
|
||||
</td>
|
||||
<td className="text-right font-mono">
|
||||
{unsettledSpotBalances[mktAddress].quote || 0.0} {quote}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<Button
|
||||
onClick={() => handleSettleFunds(mktAddress)}
|
||||
className={`text-white`}
|
||||
disabled={false}
|
||||
size="small"
|
||||
>
|
||||
<span>Settle</span>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
export default UnsettledTrades
|
|
@ -62,7 +62,7 @@ const Settings: NextPage = () => {
|
|||
EXPLORERS[0]
|
||||
)
|
||||
const themes = useMemo(() => {
|
||||
return [t('settings:light'), t('settings:dark'), t('settings:mango')]
|
||||
return [t('settings:light'), t('settings:mango'), t('settings:dark')]
|
||||
}, [t])
|
||||
|
||||
const handleLangChange = useCallback(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export const COLORS: any = {
|
||||
BKG1: { Mango: '#18181C', Dark: '#17171B', Light: '#FDFDFD' },
|
||||
GREEN: { Mango: '#AFD803', Dark: '#5EBF4d', Light: '#5EBF4d' },
|
||||
GREEN: { Mango: '#A6CD03', Dark: '#5EBF4d', Light: '#5EBF4d' },
|
||||
PRIMARY: { Mango: '#F2C94C', Dark: '#F2C94C', Light: '#FF9C24' },
|
||||
RED: { Mango: '#F84638', Dark: '#CC2929', Light: '#CC2929' },
|
||||
}
|
||||
|
|
|
@ -266,13 +266,12 @@ table th {
|
|||
}
|
||||
|
||||
table p {
|
||||
@apply font-mono text-sm tracking-tight text-th-fgd-1;
|
||||
@apply font-mono text-sm tracking-tight text-th-fgd-2;
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
|
||||
.font-mono {
|
||||
@apply tracking-tight;
|
||||
-webkit-font-feature-settings: 'zero' 1;
|
||||
font-feature-settings: 'zero' 1;
|
||||
}
|
||||
|
|
|
@ -64,12 +64,12 @@ module.exports = {
|
|||
dark: '#E4AF11',
|
||||
},
|
||||
red: { DEFAULT: '#F84638', dark: '#C7251A', muted: '#6d2832' },
|
||||
green: { DEFAULT: '#AFD803', dark: '#91B503', muted: '#49601b' },
|
||||
green: { DEFAULT: '#A6CD03', dark: '#84A21C', muted: '#49601b' },
|
||||
orange: { DEFAULT: '#FF9C24' },
|
||||
'bkg-1': '#141026',
|
||||
'bkg-2': '#1D1832',
|
||||
'bkg-3': '#2A2440',
|
||||
'bkg-4': '#37324D',
|
||||
'bkg-1': '#1C1924', // '#141026',
|
||||
'bkg-2': '#252232', // '#1D1832',
|
||||
'bkg-3': '#302B40', // '#2A2440',
|
||||
'bkg-4': '#383544', // '#37324D',
|
||||
'fgd-1': '#E5E3EC',
|
||||
'fgd-2': '#D2CEDE',
|
||||
'fgd-3': '#C1BED3',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export const LAST_ACCOUNT_KEY = 'mangoAccount-0.1'
|
||||
|
||||
export const CLIENT_TX_TIMEOUT = 60000
|
||||
export const CLIENT_TX_TIMEOUT = 90000
|
||||
|
||||
export const INPUT_TOKEN_DEFAULT = 'USDC'
|
||||
|
||||
|
|
Loading…
Reference in New Issue