Merge branch 'main' into jupiter-logos
This commit is contained in:
commit
5ef7217469
|
@ -8,12 +8,7 @@ import BottomBar from './mobile/BottomBar'
|
|||
import BounceLoader from './shared/BounceLoader'
|
||||
import TopBar from './TopBar'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import {
|
||||
IS_ONBOARDED_KEY,
|
||||
ONBOARDING_TOUR_KEY,
|
||||
SIDEBAR_COLLAPSE_KEY,
|
||||
} from '../utils/constants'
|
||||
import OnboardingTour from './OnboardingTour'
|
||||
import { SIDEBAR_COLLAPSE_KEY } from '../utils/constants'
|
||||
|
||||
const sideBarAnimationDuration = 500
|
||||
|
||||
|
@ -24,8 +19,6 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
SIDEBAR_COLLAPSE_KEY,
|
||||
false
|
||||
)
|
||||
const [showOnboardingTour] = useLocalStorageState(ONBOARDING_TOUR_KEY, false)
|
||||
const [isOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
|
||||
const { width } = useViewport()
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -94,9 +87,6 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showOnboardingTour && isOnboarded && connected ? (
|
||||
<OnboardingTour />
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { LAST_ACCOUNT_KEY } from '../utils/constants'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { retryFn } from '../utils'
|
||||
import Loading from './shared/Loading'
|
||||
import ActionTokenList from './account/ActionTokenList'
|
||||
|
||||
const MangoAccountsList = ({
|
||||
mangoAccount,
|
||||
|
@ -22,6 +23,7 @@ const MangoAccountsList = ({
|
|||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccounts = mangoStore((s) => s.mangoAccounts.accounts)
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const loading = mangoStore((s) => s.mangoAccount.initialLoad)
|
||||
const [showNewAccountModal, setShowNewAccountModal] = useState(false)
|
||||
const [, setLastAccountViewed] = useLocalStorageStringState(LAST_ACCOUNT_KEY)
|
||||
|
@ -41,13 +43,14 @@ const MangoAccountsList = ({
|
|||
s.mangoAccount.lastUpdatedAt = new Date().toISOString()
|
||||
})
|
||||
setLastAccountViewed(acc.publicKey.toString())
|
||||
actions.fetchSerumOpenOrders(acc)
|
||||
} catch (e) {
|
||||
console.warn('Error selecting account', e)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="step-one">
|
||||
<div id="account-step-two">
|
||||
<Popover>
|
||||
{({ open }) => (
|
||||
<>
|
||||
|
@ -76,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-2 bg-th-bkg-3 p-4 text-th-fgd-3">
|
||||
<Popover.Panel className="absolute top-[13.5px] -right-5 z-20 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 ? (
|
||||
|
@ -84,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-4 pb-3 hover:text-th-fgd-1"
|
||||
className="default-transition 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() ===
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { useRouter } from 'next/router'
|
||||
import {
|
||||
CardinalOrientation,
|
||||
MaskOptions,
|
||||
Walktour,
|
||||
WalktourLogic,
|
||||
} from 'walktour'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import { ONBOARDING_TOUR_KEY } from '../utils/constants'
|
||||
|
||||
const OnboardingTour = () => {
|
||||
const [, setShowOnboardingTour] = useLocalStorageState(ONBOARDING_TOUR_KEY)
|
||||
const router = useRouter()
|
||||
|
||||
const renderTooltip = (tourLogic: WalktourLogic | undefined) => {
|
||||
const { title, description } = tourLogic!.stepContent
|
||||
const { next, prev, close, allSteps, stepIndex } = tourLogic!
|
||||
|
||||
const handleClose = () => {
|
||||
setShowOnboardingTour(false)
|
||||
close()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative w-72 rounded-lg bg-gradient-to-b from-gradient-start via-gradient-mid to-gradient-end p-4">
|
||||
<button
|
||||
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-6 w-6`} />
|
||||
</button>
|
||||
<h3 className="text-th-bkg-1">{title}</h3>
|
||||
<p className="text-sm text-th-bkg-1">{description}</p>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
{stepIndex !== 0 ? (
|
||||
<button
|
||||
className="default-transition h-8 rounded-md border border-th-bkg-1 px-3 font-bold text-th-bkg-1 focus:outline-none md:hover:border-th-bkg-3 md:hover:text-th-bkg-3"
|
||||
onClick={() => prev()}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
) : (
|
||||
<div className="h-8 w-[58.25px]" />
|
||||
)}
|
||||
<div className="flex space-x-2">
|
||||
{allSteps.map((s, i) => (
|
||||
<div
|
||||
className={`h-1 w-1 rounded-full ${
|
||||
i === stepIndex ? 'bg-th-primary' : 'bg-[rgba(0,0,0,0.2)]'
|
||||
}`}
|
||||
key={s.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{stepIndex !== allSteps.length - 1 ? (
|
||||
<button
|
||||
className="default-transition h-8 rounded-md bg-th-bkg-1 px-3 font-bold text-th-fgd-1 focus:outline-none md:hover:bg-th-bkg-3"
|
||||
onClick={() => next()}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="default-transition h-8 rounded-md bg-th-bkg-1 px-3 font-bold text-th-fgd-1 focus:outline-none md:hover:bg-th-bkg-3"
|
||||
onClick={handleClose}
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const steps = [
|
||||
{
|
||||
selector: '#step-one',
|
||||
title: 'Your Accounts',
|
||||
description:
|
||||
'Switch between accounts and create new ones. Use multiple accounts to trade isolated margin and protect your capital from liquidation.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#step-two',
|
||||
title: 'Account Value',
|
||||
description:
|
||||
'The value of your assets (deposits) minus the value of your liabilities (borrows).',
|
||||
orientationPreferences: [CardinalOrientation.EASTNORTH],
|
||||
movingTarget: true,
|
||||
customNextFunc: (tourLogic: WalktourLogic) => {
|
||||
router.push('/')
|
||||
setTimeout(() => tourLogic.next(), 1000)
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '#step-three',
|
||||
title: 'Health',
|
||||
description:
|
||||
'If your account health reaches 0% your account will be liquidated. You can increase the health of your account by making a deposit.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#step-four',
|
||||
title: 'Free Collateral',
|
||||
description:
|
||||
"The amount of capital you have to trade or borrow against. When your free collateral reaches $0 you won't be able to make withdrawals.",
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#step-five',
|
||||
title: 'Total Interest Value',
|
||||
description:
|
||||
'The value of interest earned (deposits) minus interest paid (borrows).',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#step-six',
|
||||
title: 'Health Check',
|
||||
description:
|
||||
'Check the health of your account from any screen in the app. A green heart represents good health, orange okay and red poor.',
|
||||
orientationPreferences: [CardinalOrientation.EASTSOUTH],
|
||||
movingTarget: true,
|
||||
customNextFunc: (tourLogic: WalktourLogic) => {
|
||||
router.push('/swap')
|
||||
setTimeout(() => tourLogic.next(), 1000)
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '#step-seven',
|
||||
title: 'Swap',
|
||||
description:
|
||||
"You choose the quote token of your trades. This means you can easily trade tokens on their relative strength vs. another token. Let's say your thesis is BTC will see diminishing returns relative to SOL. You can sell BTC and buy SOL. Now you are long SOL/BTC",
|
||||
orientationPreferences: [CardinalOrientation.CENTER],
|
||||
customPrevFunc: (tourLogic: WalktourLogic) => {
|
||||
router.push('/swap')
|
||||
tourLogic.prev()
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '#step-eight',
|
||||
title: 'Trade Settings',
|
||||
description:
|
||||
'Edit your slippage settings and toggle margin on and off. When margin is off your trades will be limited by your balance for each token.',
|
||||
orientationPreferences: [CardinalOrientation.WESTNORTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#step-nine',
|
||||
title: 'Token to Sell',
|
||||
description:
|
||||
'Select the token you want to sell. If your sell size is above your token balance a loan will be opened to cover the shortfall.',
|
||||
orientationPreferences: [CardinalOrientation.WESTNORTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#step-ten',
|
||||
title: 'Health Impact',
|
||||
description:
|
||||
'Projects the health of your account before you make a trade.',
|
||||
orientationPreferences: [CardinalOrientation.WESTNORTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Walktour
|
||||
customTooltipRenderer={renderTooltip}
|
||||
steps={steps}
|
||||
updateInterval={200}
|
||||
disableCloseOnClick
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default OnboardingTour
|
|
@ -20,26 +20,16 @@ import MangoAccountSummary from './account/MangoAccountSummary'
|
|||
import Tooltip from './shared/Tooltip'
|
||||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import useLocalStorageState from '../hooks/useLocalStorageState'
|
||||
import { ONBOARDING_TOUR_KEY } from '../utils/constants'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import HealthHeart from './account/HealthHeart'
|
||||
|
||||
const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
||||
const [, setShowOnboardingTour] = useLocalStorageState(ONBOARDING_TOUR_KEY)
|
||||
const { t } = useTranslation('common')
|
||||
const { connected } = useWallet()
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const router = useRouter()
|
||||
const { pathname } = router
|
||||
|
||||
const handleTakeTour = () => {
|
||||
if (pathname !== '/') {
|
||||
router.push('/')
|
||||
}
|
||||
setShowOnboardingTour(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`transition-all duration-300 ${
|
||||
|
@ -144,15 +134,6 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
isExternal
|
||||
showTooltip={false}
|
||||
/>
|
||||
{connected ? (
|
||||
<button
|
||||
className="default-transition mt-1 flex items-center px-4 text-th-fgd-2 md:hover:text-th-primary"
|
||||
onClick={handleTakeTour}
|
||||
>
|
||||
<InformationCircleIcon className="mr-3 h-5 w-5" />
|
||||
<span className="text-base">Take UI Tour</span>
|
||||
</button>
|
||||
) : null}
|
||||
</ExpandableMenuItem>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -105,21 +105,17 @@ const TokenList = () => {
|
|||
</Tooltip>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="The sum of interest earned and interest paid for each token.">
|
||||
<span className="tooltip-underline">
|
||||
{t('interest-earned-paid')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<th className="text-right" id="account-step-eight">
|
||||
<Tooltip content="The sum of interest earned and interest paid for each token.">
|
||||
<span className="tooltip-underline">
|
||||
{t('interest-earned-paid')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content="The interest rates (per year) for depositing (green/left) and borrowing (red/right)">
|
||||
<span className="tooltip-underline">{t('rates')}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<th className="text-right" id="account-step-nine">
|
||||
<Tooltip content="The interest rates (per year) for depositing (green/left) and borrowing (red/right).">
|
||||
<span className="tooltip-underline">{t('rates')}</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th className="text-right">{t('price')}</th>
|
||||
<th className="hidden text-right lg:block"></th>
|
||||
|
@ -127,7 +123,7 @@ const TokenList = () => {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banks.map(({ key, value }) => {
|
||||
{banks.map(({ key, value }, i) => {
|
||||
const bank = value[0]
|
||||
const oraclePrice = bank.uiPrice
|
||||
|
||||
|
@ -256,7 +252,10 @@ const TokenList = () => {
|
|||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<div
|
||||
className="flex justify-end space-x-2"
|
||||
id={i === 0 ? 'account-step-ten' : ''}
|
||||
>
|
||||
<ActionsMenu bank={bank} mangoAccount={mangoAccount} />
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import {
|
||||
HealthType,
|
||||
I80F48,
|
||||
toUiDecimalsForQuote,
|
||||
ZERO_I80F48,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import AccountActions from './AccountActions'
|
||||
import DepositModal from '../modals/DepositModal'
|
||||
import WithdrawModal from '../modals/WithdrawModal'
|
||||
import mangoStore, { PerformanceDataItem } from '@store/mangoStore'
|
||||
import { formatDecimal, formatFixedDecimals } from '../../utils/numbers'
|
||||
import FlipNumbers from 'react-flip-numbers'
|
||||
import { DownTriangle, UpTriangle } from '../shared/DirectionTriangles'
|
||||
import SimpleAreaChart from '../shared/SimpleAreaChart'
|
||||
import { COLORS } from '../../styles/colors'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
@ -31,6 +28,10 @@ import { breakpoints } from '../../utils/theme'
|
|||
import useMangoAccount from '../shared/useMangoAccount'
|
||||
import PercentageChange from '../shared/PercentageChange'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { IS_ONBOARDED_KEY } from 'utils/constants'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import AccountOnboardingTour from '@components/tours/AccountOnboardingTour'
|
||||
|
||||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
return {
|
||||
|
@ -46,7 +47,8 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
|
||||
const AccountPage = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { mangoAccount, lastUpdatedAt } = useMangoAccount()
|
||||
const { connected } = useWallet()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const loadPerformanceData = mangoStore(
|
||||
(s) => s.mangoAccount.stats.performance.loading
|
||||
|
@ -69,6 +71,8 @@ const AccountPage = () => {
|
|||
const { theme } = useTheme()
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
const tourSettings = mangoStore((s) => s.settings.tours)
|
||||
const [isOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
|
||||
|
||||
useEffect(() => {
|
||||
if (mangoAccount) {
|
||||
|
@ -134,11 +138,24 @@ const AccountPage = () => {
|
|||
return mangoAccount ? mangoAccount.getHealthRatioUi(HealthType.maint) : 0
|
||||
}, [mangoAccount])
|
||||
|
||||
const handleChartToShow = (chartName: string) => {
|
||||
if (chartName === 'cumulative-interest-value') {
|
||||
if (interestTotalValue > 1 || interestTotalValue < -1) {
|
||||
setChartToShow(chartName)
|
||||
}
|
||||
}
|
||||
if (chartName === 'pnl') {
|
||||
if (performanceData.length > 4) {
|
||||
setChartToShow(chartName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !chartToShow ? (
|
||||
<>
|
||||
<div className="flex flex-wrap items-center justify-between border-b-0 border-th-bkg-3 px-6 pt-3 pb-0 md:border-b md:pb-3">
|
||||
<div className="flex items-center space-x-6">
|
||||
<div id="step-two">
|
||||
<div id="account-step-three">
|
||||
<Tooltip
|
||||
maxWidth="20rem"
|
||||
placement="bottom-start"
|
||||
|
@ -230,7 +247,7 @@ const AccountPage = () => {
|
|||
</div>
|
||||
<div className="grid grid-cols-4 border-b border-th-bkg-3">
|
||||
<div className="col-span-4 flex border-t border-th-bkg-3 py-3 pl-6 md:col-span-1 md:col-span-2 md:border-l md:border-t-0 lg:col-span-1">
|
||||
<div id="step-three">
|
||||
<div id="account-step-four">
|
||||
<Tooltip
|
||||
maxWidth="20rem"
|
||||
placement="bottom-start"
|
||||
|
@ -273,7 +290,7 @@ const AccountPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 flex border-t border-th-bkg-3 py-3 pl-6 md:col-span-1 md:col-span-2 md:border-l md:border-t-0 lg:col-span-1">
|
||||
<div id="step-four">
|
||||
<div id="account-step-five">
|
||||
<Tooltip
|
||||
content="The amount of capital you have to trade or borrow against. When your free collateral reaches $0 you won't be able to make withdrawals."
|
||||
maxWidth="20rem"
|
||||
|
@ -295,51 +312,59 @@ const AccountPage = () => {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 flex border-t border-th-bkg-3 py-3 px-6 md:col-span-2 md:col-span-2 md:border-l lg:col-span-1 lg:border-t-0">
|
||||
<div>
|
||||
<Tooltip
|
||||
content="The amount your account has made or lost."
|
||||
placement="bottom-start"
|
||||
>
|
||||
<p className="tooltip-underline text-th-fgd-3">{t('pnl')}</p>
|
||||
</Tooltip>
|
||||
<p className="mt-1 text-2xl font-bold text-th-fgd-1">
|
||||
{formatFixedDecimals(accountPnl, true)}
|
||||
</p>
|
||||
</div>
|
||||
{performanceData.length > 4 ? (
|
||||
<IconButton
|
||||
onClick={() => setChartToShow('pnl')}
|
||||
size={!isMobile ? 'small' : 'medium'}
|
||||
>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="col-span-4 flex items-center justify-between border-t border-th-bkg-3 py-3 pl-6 md:col-span-1 md:col-span-2 md:border-l lg:col-span-1 lg:border-t-0">
|
||||
<div id="step-five">
|
||||
<Tooltip
|
||||
content="The value of interest earned (deposits) minus interest paid (borrows)."
|
||||
maxWidth="20rem"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<p className="tooltip-underline text-th-fgd-3">
|
||||
{t('total-interest-value')}
|
||||
<button
|
||||
className={`col-span-4 border-t border-th-bkg-3 py-3 px-6 md:col-span-2 md:col-span-2 md:border-l lg:col-span-1 lg:border-t-0 ${
|
||||
performanceData.length > 4
|
||||
? 'default-transition cursor-pointer md:hover:bg-th-bkg-2'
|
||||
: 'cursor-default'
|
||||
}`}
|
||||
onClick={() => handleChartToShow('pnl')}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div id="account-step-six">
|
||||
<Tooltip
|
||||
content="The amount your account has made or lost."
|
||||
placement="bottom-start"
|
||||
>
|
||||
<p className="tooltip-underline text-th-fgd-3">{t('pnl')}</p>
|
||||
</Tooltip>
|
||||
<p className="mt-1 text-2xl font-bold text-th-fgd-1">
|
||||
{formatFixedDecimals(accountPnl, true)}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="mt-1 text-2xl font-bold text-th-fgd-1">
|
||||
{formatFixedDecimals(interestTotalValue, true)}
|
||||
</p>
|
||||
</div>
|
||||
{performanceData.length > 4 ? (
|
||||
<ChevronRightIcon className="h-6 w-6" />
|
||||
) : null}
|
||||
</div>
|
||||
{interestTotalValue > 1 || interestTotalValue < -1 ? (
|
||||
<IconButton
|
||||
onClick={() => setChartToShow('cumulative-interest-value')}
|
||||
size={!isMobile ? 'small' : 'medium'}
|
||||
>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
className={`col-span-4 border-t border-th-bkg-3 py-3 pl-6 text-left md:col-span-1 md:col-span-2 md:border-l lg:col-span-1 lg:border-t-0 ${
|
||||
interestTotalValue > 1 || interestTotalValue < -1
|
||||
? 'default-transition cursor-pointer md:hover:bg-th-bkg-2'
|
||||
: 'cursor-default'
|
||||
}`}
|
||||
onClick={() => handleChartToShow('cumulative-interest-value')}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div id="account-step-seven">
|
||||
<Tooltip
|
||||
content="The value of interest earned (deposits) minus interest paid (borrows)."
|
||||
maxWidth="20rem"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<p className="tooltip-underline text-th-fgd-3">
|
||||
{t('total-interest-value')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="mt-1 text-2xl font-bold text-th-fgd-1">
|
||||
{formatFixedDecimals(interestTotalValue, true)}
|
||||
</p>
|
||||
</div>
|
||||
{interestTotalValue > 1 || interestTotalValue < -1 ? (
|
||||
<ChevronRightIcon className="h-6 w-6" />
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<AccountTabs />
|
||||
{showDepositModal ? (
|
||||
|
@ -354,6 +379,9 @@ const AccountPage = () => {
|
|||
onClose={() => setShowWithdrawModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
{!tourSettings?.account_tour_seen && isOnboarded && connected ? (
|
||||
<AccountOnboardingTour />
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div className="p-6">
|
||||
|
|
|
@ -12,7 +12,7 @@ const HealthHeart = ({
|
|||
|
||||
return (
|
||||
<svg
|
||||
id="step-six"
|
||||
id="account-step-eleven"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={
|
||||
health
|
||||
|
|
|
@ -130,7 +130,7 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
})
|
||||
|
||||
await actions.reloadMangoAccount()
|
||||
setShowSetupStep(4)
|
||||
onClose()
|
||||
setSubmitDeposit(false)
|
||||
} catch (e: any) {
|
||||
notify({
|
||||
|
@ -379,7 +379,7 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
</Button>
|
||||
<LinkButton
|
||||
className="flex w-full justify-center"
|
||||
onClick={() => setShowSetupStep(4)}
|
||||
onClick={onClose}
|
||||
>
|
||||
<span className="default-transition text-th-fgd-4 underline md:hover:text-th-fgd-3 md:hover:no-underline">
|
||||
Skip for now
|
||||
|
@ -506,7 +506,7 @@ const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
Deposit
|
||||
</div>
|
||||
</Button>
|
||||
<LinkButton onClick={() => setShowSetupStep(4)}>
|
||||
<LinkButton onClick={onClose}>
|
||||
<span className="default-transition text-th-fgd-4 underline md:hover:text-th-fgd-3 md:hover:no-underline">
|
||||
Skip for now
|
||||
</span>
|
||||
|
|
|
@ -18,7 +18,7 @@ const TabButtons: FunctionComponent<TabButtonsProps> = ({
|
|||
rounded = false,
|
||||
fillWidth = false,
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'swap'])
|
||||
const { t } = useTranslation(['common', 'swap', 'trade'])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -29,7 +29,7 @@ const TabButtons: FunctionComponent<TabButtonsProps> = ({
|
|||
{values.map(([label, count], i) => (
|
||||
<div className={fillWidth ? 'flex-1' : ''} key={label + i}>
|
||||
<button
|
||||
className={`default-transition flex h-12 w-full items-center justify-center px-6 font-bold ${
|
||||
className={`default-transition flex h-12 w-full items-center justify-center px-4 font-bold md:px-6 ${
|
||||
rounded ? 'rounded-md' : 'rounded-none'
|
||||
} ${showBorders ? 'border-r border-th-bkg-3' : ''} ${
|
||||
label === activeValue
|
||||
|
@ -44,7 +44,7 @@ const TabButtons: FunctionComponent<TabButtonsProps> = ({
|
|||
<div
|
||||
className={`ml-1.5 rounded ${
|
||||
label === activeValue ? 'bg-th-bkg-4' : 'bg-th-bkg-3'
|
||||
} px-1.5 font-mono text-xxs`}
|
||||
} px-1.5 font-mono text-xxs text-th-fgd-2`}
|
||||
>
|
||||
{count}
|
||||
</div>
|
||||
|
|
|
@ -237,7 +237,7 @@ const SwapForm = () => {
|
|||
</EnterBottomExitBottom>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-base text-th-fgd-2">{t('swap')}</h2>
|
||||
<div id="step-eight">
|
||||
<div id="swap-step-one">
|
||||
<IconButton
|
||||
className="text-th-fgd-2"
|
||||
hideBg
|
||||
|
@ -248,7 +248,10 @@ const SwapForm = () => {
|
|||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div id="step-nine" className="mb-2 flex items-center justify-between">
|
||||
<div
|
||||
id="swap-step-two"
|
||||
className="mb-2 flex items-center justify-between"
|
||||
>
|
||||
<p className="text-th-fgd-3">{t('swap:from')}</p>
|
||||
<MaxSwapAmount
|
||||
useMargin={useMargin}
|
||||
|
@ -303,7 +306,7 @@ const SwapForm = () => {
|
|||
</button>
|
||||
</div>
|
||||
<p className="mb-2 text-th-fgd-3">{t('swap:to')}</p>
|
||||
<div className="mb-3 grid grid-cols-2">
|
||||
<div id="swap-step-three" className="mb-3 grid grid-cols-2">
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-bkg-4 bg-th-bkg-1">
|
||||
<TokenSelect
|
||||
tokenSymbol={outputTokenInfo?.symbol || OUTPUT_TOKEN_DEFAULT}
|
||||
|
@ -348,7 +351,7 @@ const SwapForm = () => {
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
id="step-ten"
|
||||
id="swap-step-four"
|
||||
className={`border-t border-th-bkg-3 px-6 transition-all ${
|
||||
showHealthImpact ? 'max-h-40 py-4 ' : 'h-0'
|
||||
}`}
|
||||
|
|
|
@ -2,26 +2,35 @@ import Swap from './SwapForm'
|
|||
import SwapTokenChart from './SwapTokenChart'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import AccountTabs from '../account/AccountTabs'
|
||||
import SwapOnboardingTour from '@components/tours/SwapOnboardingTour'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
|
||||
const SwapPage = () => {
|
||||
const inputTokenInfo = mangoStore((s) => s.swap.inputTokenInfo)
|
||||
const outputTokenInfo = mangoStore((s) => s.swap.outputTokenInfo)
|
||||
const { connected } = useWallet()
|
||||
const tourSettings = mangoStore((s) => s.settings.tours)
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-12 border-th-bkg-3 md:col-span-6 md:border-b lg:col-span-8">
|
||||
<SwapTokenChart
|
||||
inputTokenId={inputTokenInfo?.extensions?.coingeckoId}
|
||||
outputTokenId={outputTokenInfo?.extensions?.coingeckoId}
|
||||
/>
|
||||
<>
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-12 border-th-bkg-3 md:col-span-6 md:border-b lg:col-span-8">
|
||||
<SwapTokenChart
|
||||
inputTokenId={inputTokenInfo?.extensions?.coingeckoId}
|
||||
outputTokenId={outputTokenInfo?.extensions?.coingeckoId}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-12 mt-2 space-y-6 border-th-bkg-3 md:col-span-6 md:mt-0 md:border-b lg:col-span-4">
|
||||
<Swap />
|
||||
</div>
|
||||
<div className="col-span-12">
|
||||
<AccountTabs />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 mt-2 space-y-6 border-th-bkg-3 md:col-span-6 md:mt-0 md:border-b lg:col-span-4">
|
||||
<Swap />
|
||||
</div>
|
||||
<div className="col-span-12">
|
||||
<AccountTabs />
|
||||
</div>
|
||||
</div>
|
||||
{!tourSettings?.swap_tour_seen && connected ? (
|
||||
<SwapOnboardingTour />
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
import { CardinalOrientation, Walktour, WalktourLogic } from 'walktour'
|
||||
import CustomTooltip from './CustomTooltip'
|
||||
|
||||
const AccountOnboardingTour = () => {
|
||||
const renderTooltip = (tourLogic: WalktourLogic | undefined) => {
|
||||
return (
|
||||
<CustomTooltip hasSeenKey="account_tour_seen" tourLogic={tourLogic!} />
|
||||
)
|
||||
}
|
||||
|
||||
const steps = [
|
||||
{
|
||||
selector: '#account-step-zero',
|
||||
title: 'Your Account Dashboard',
|
||||
description:
|
||||
"Here you'll find the important information related to your account. Let us show you around. Click close to skip the tour at any time.",
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-one',
|
||||
title: 'Profile Menu',
|
||||
description:
|
||||
"If you haven't chosen a profile name yet, you'll see your assigned one here. You can edit it and change your profile picture from this menu.",
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-two',
|
||||
title: 'Your Accounts',
|
||||
description:
|
||||
'Switch between accounts and create new ones. Use multiple accounts to trade isolated margin and protect your capital from liquidation.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-three',
|
||||
title: 'Account Value',
|
||||
description:
|
||||
'The value of your assets (deposits) minus the value of your liabilities (borrows).',
|
||||
orientationPreferences: [CardinalOrientation.EASTNORTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-four',
|
||||
title: 'Health',
|
||||
description:
|
||||
'If your account health reaches 0% your account will be liquidated. You can increase the health of your account by making a deposit.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-five',
|
||||
title: 'Free Collateral',
|
||||
description:
|
||||
"The amount of capital you have to trade or borrow against. When your free collateral reaches $0 you won't be able to make withdrawals.",
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-six',
|
||||
title: 'PnL (Profit and Loss)',
|
||||
description: 'The amount your account has made or lost.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-seven',
|
||||
title: 'Total Interest Value',
|
||||
description:
|
||||
'The value of interest earned (deposits) minus interest paid (borrows).',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-eight',
|
||||
title: 'Interest Earned',
|
||||
description:
|
||||
'The sum of interest earned and interest paid for each token.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-nine',
|
||||
title: 'Rates',
|
||||
description:
|
||||
'The interest rates (per year) for depositing (green/left) and borrowing (red/right).',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-ten',
|
||||
title: 'Token Actions',
|
||||
description:
|
||||
'Deposit, withdraw, borrow, buy and sell buttons for each token.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#account-step-eleven',
|
||||
title: 'Health Check',
|
||||
description:
|
||||
'Check the health of your account from any screen in the app. A green heart represents good health, orange okay and red poor.',
|
||||
orientationPreferences: [CardinalOrientation.EASTSOUTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Walktour
|
||||
customTooltipRenderer={renderTooltip}
|
||||
steps={steps}
|
||||
updateInterval={200}
|
||||
disableCloseOnClick
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountOnboardingTour
|
|
@ -0,0 +1,115 @@
|
|||
import Loading from '@components/shared/Loading'
|
||||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useState } from 'react'
|
||||
import { WalktourLogic } from 'walktour'
|
||||
|
||||
const CustomTooltip = ({
|
||||
customOnClose,
|
||||
hasSeenKey,
|
||||
tourLogic,
|
||||
}: {
|
||||
customOnClose?: () => void
|
||||
hasSeenKey: 'account_tour_seen' | 'swap_tour_seen' | 'trade_tour_seen'
|
||||
tourLogic: WalktourLogic | undefined
|
||||
}) => {
|
||||
const { title, description } = tourLogic!.stepContent
|
||||
const { next, prev, close, allSteps, stepIndex } = tourLogic!
|
||||
const { publicKey } = useWallet()
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const tourSettings = mangoStore((s) => s.settings.tours)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const onClose = async () => {
|
||||
if (!publicKey || !tourSettings) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const settings = {
|
||||
...tourSettings,
|
||||
}
|
||||
settings[hasSeenKey] = true
|
||||
const message = JSON.stringify(settings)
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: message,
|
||||
}
|
||||
const response = await fetch(
|
||||
'https://mango-transaction-log.herokuapp.com/v4/user-data/settings-unsigned',
|
||||
requestOptions
|
||||
)
|
||||
if (response.status === 200) {
|
||||
await actions.fetchTourSettings(publicKey.toString())
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
if (customOnClose) {
|
||||
customOnClose()
|
||||
}
|
||||
setLoading(false)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative w-72 rounded-lg bg-gradient-to-b from-gradient-start via-gradient-mid to-gradient-end p-4">
|
||||
{!loading ? (
|
||||
<>
|
||||
<button
|
||||
onClick={onClose}
|
||||
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`} />
|
||||
</button>
|
||||
<h3 className="text-th-bkg-1">{title}</h3>
|
||||
<p className="text-sm text-th-bkg-1">{description}</p>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
{stepIndex !== 0 ? (
|
||||
<button
|
||||
className="default-transition h-8 rounded-md border border-th-bkg-1 px-3 font-bold text-th-bkg-1 focus:outline-none md:hover:border-th-bkg-3 md:hover:text-th-bkg-3"
|
||||
onClick={() => prev()}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
) : (
|
||||
<div className="h-8 w-[58.25px]" />
|
||||
)}
|
||||
<div className="flex space-x-1.5">
|
||||
{allSteps.map((s, i) => (
|
||||
<div
|
||||
className={`h-1 w-1 rounded-full ${
|
||||
i === stepIndex ? 'bg-th-primary' : 'bg-[rgba(0,0,0,0.2)]'
|
||||
}`}
|
||||
key={s.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{stepIndex !== allSteps.length - 1 ? (
|
||||
<button
|
||||
className="default-transition h-8 rounded-md bg-th-bkg-1 px-3 font-bold text-th-fgd-1 focus:outline-none md:hover:bg-th-bkg-3"
|
||||
onClick={() => next()}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="default-transition h-8 rounded-md bg-th-bkg-1 px-3 font-bold text-th-fgd-1 focus:outline-none md:hover:bg-th-bkg-3"
|
||||
onClick={onClose}
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center py-12">
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomTooltip
|
|
@ -0,0 +1,61 @@
|
|||
import { CardinalOrientation, Walktour, WalktourLogic } from 'walktour'
|
||||
import CustomTooltip from './CustomTooltip'
|
||||
|
||||
const SwapOnboardingTour = () => {
|
||||
const renderTooltip = (tourLogic: WalktourLogic | undefined) => {
|
||||
return <CustomTooltip tourLogic={tourLogic!} hasSeenKey="swap_tour_seen" />
|
||||
}
|
||||
|
||||
const steps = [
|
||||
{
|
||||
selector: '#swap-step-zero',
|
||||
title: "We've Juiced Swap",
|
||||
description:
|
||||
"The swap you know and love + leverage. Swap lets you trade tokens on their relative strength. Let's say your thesis is BTC will see diminishing returns relative to SOL. You can sell BTC and buy SOL. Now you are long SOL/BTC",
|
||||
orientationPreferences: [CardinalOrientation.CENTER],
|
||||
},
|
||||
{
|
||||
selector: '#swap-step-one',
|
||||
title: 'Swap Settings',
|
||||
description:
|
||||
'Edit your slippage settings and toggle margin on and off. When margin is off your swaps will be limited by your balance for each token.',
|
||||
orientationPreferences: [CardinalOrientation.WESTNORTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#swap-step-two',
|
||||
title: 'From Token',
|
||||
description:
|
||||
'Select the token you want to swap from (sell). If you have margin on and your size is above your token balance a loan will be opened to cover the shortfall. Check the borrow rate before making a margin swap.',
|
||||
orientationPreferences: [CardinalOrientation.WESTNORTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#swap-step-three',
|
||||
title: 'To Token',
|
||||
description:
|
||||
"The token you'll receive in your Mango Account after making a swap. You can think of this token as the one you're buying/longing.",
|
||||
orientationPreferences: [CardinalOrientation.WESTNORTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#swap-step-four',
|
||||
title: 'Health Impact',
|
||||
description:
|
||||
'Projects the health of your account before you make a swap. The first value is your current account health and the second, your projected account health.',
|
||||
orientationPreferences: [CardinalOrientation.WESTNORTH],
|
||||
movingTarget: true,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Walktour
|
||||
customTooltipRenderer={renderTooltip}
|
||||
steps={steps}
|
||||
updateInterval={200}
|
||||
disableCloseOnClick
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SwapOnboardingTour
|
|
@ -0,0 +1,108 @@
|
|||
import { CardinalOrientation, Walktour, WalktourLogic } from 'walktour'
|
||||
import CustomTooltip from './CustomTooltip'
|
||||
|
||||
const TradeOnboardingTour = () => {
|
||||
const renderTooltip = (tourLogic: WalktourLogic | undefined) => {
|
||||
return <CustomTooltip tourLogic={tourLogic!} hasSeenKey="trade_tour_seen" />
|
||||
}
|
||||
|
||||
const steps = [
|
||||
{
|
||||
selector: '#trade-step-zero',
|
||||
title: 'Trade 100s of Tokens...',
|
||||
description:
|
||||
'A refined interface without listing limits. The tokens you want to trade are now on Mango and no longer only quoted in USDC.',
|
||||
orientationPreferences: [CardinalOrientation.CENTER],
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-one',
|
||||
title: 'Market Selector',
|
||||
description: 'Chose the market you want to trade.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-two',
|
||||
title: 'Oracle Price',
|
||||
description:
|
||||
"The oracle price uses an average of price data from many sources. It's used to avoid price manipulation which could lead to liquidations.",
|
||||
orientationPreferences: [CardinalOrientation.SOUTHWEST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-three',
|
||||
title: 'Toggle Orderbook',
|
||||
description:
|
||||
'Use these buttons if you only want to see one side of the orderbook. Looking to bid/buy? Toggle off the buy orders to only see the sells and vice versa.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-four',
|
||||
title: 'Orderbook Grouping',
|
||||
description:
|
||||
'Adjust the price intervals to change how orders are grouped. Small intervals will show more small orders in the book',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-five',
|
||||
title: 'Recent Trades',
|
||||
description:
|
||||
'Shows the most recent orders for a market across all accounts.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-six',
|
||||
title: 'Post Only',
|
||||
description:
|
||||
"An order condition that will only allow your order to enter the orderbook as a maker order. If the condition can't be met the order will be cancelled.",
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-seven',
|
||||
title: 'Immediate or Cancel (IoC)',
|
||||
description:
|
||||
'An order condition that attempts to execute all or part of an order immediately and then cancels any unfilled portion.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-eight',
|
||||
title: 'Margin',
|
||||
description:
|
||||
"When margin is on you can trade with more size than your token balance. Using margin increases your risk of loss. If you're not an experienced trader, use it with caution.",
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-nine',
|
||||
title: 'Spread',
|
||||
description:
|
||||
'The difference between the prices quoted for an immediate sell (ask) and an immediate buy (bid). Or, in other words, the difference between the lowest sell price and the highest buy price.',
|
||||
orientationPreferences: [CardinalOrientation.SOUTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
{
|
||||
selector: '#trade-step-ten',
|
||||
title: 'Unsettled Balance',
|
||||
description:
|
||||
'When a limit order is filled, the funds are placed in your unsettled balances. When you have an unsettled balance you\'ll see a "Settle All" button above this table. Use it to move the funds to your account balance.',
|
||||
orientationPreferences: [CardinalOrientation.NORTHEAST],
|
||||
movingTarget: true,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Walktour
|
||||
customTooltipRenderer={renderTooltip}
|
||||
steps={steps}
|
||||
updateInterval={200}
|
||||
disableCloseOnClick
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default TradeOnboardingTour
|
|
@ -29,7 +29,10 @@ const MarketSelectDropdown = () => {
|
|||
return (
|
||||
<Popover>
|
||||
{({ close, open }) => (
|
||||
<div className="relative flex flex-col overflow-visible">
|
||||
<div
|
||||
className="relative flex flex-col overflow-visible"
|
||||
id="trade-step-one"
|
||||
>
|
||||
<Popover.Button className="default-transition flex w-full items-center justify-between hover:text-th-primary">
|
||||
<MarketLogos serumMarket={selectedMarket!} />
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-base">
|
||||
|
@ -93,7 +96,7 @@ const OraclePrice = () => {
|
|||
}
|
||||
|
||||
const AdvancedMarketHeader = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
|
||||
const coingeckoPrices = mangoStore((s) => s.coingeckoPrices.data)
|
||||
|
||||
|
@ -121,8 +124,8 @@ const AdvancedMarketHeader = () => {
|
|||
<MarketSelectDropdown />
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-6 flex-col">
|
||||
<div className="text-xs text-th-fgd-4">{t('oracle-price')}</div>
|
||||
<div id="trade-step-two" className="ml-6 flex-col">
|
||||
<div className="text-xs text-th-fgd-4">{t('trade:oracle-price')}</div>
|
||||
<OraclePrice />
|
||||
</div>
|
||||
<div className="ml-6 flex-col">
|
||||
|
|
|
@ -18,6 +18,9 @@ import NumberFormat, {
|
|||
import { notify } from 'utils/notifications'
|
||||
import SpotSlider from './SpotSlider'
|
||||
import { calculateMarketPrice } from 'utils/tradeForm'
|
||||
import Image from 'next/image'
|
||||
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
|
||||
import Loading from '@components/shared/Loading'
|
||||
|
||||
const TABS: [string, number][] = [
|
||||
['Limit', 0],
|
||||
|
@ -25,20 +28,40 @@ const TABS: [string, number][] = [
|
|||
]
|
||||
|
||||
const AdvancedTradeForm = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const set = mangoStore.getState().set
|
||||
const tradeForm = mangoStore((s) => s.tradeForm)
|
||||
const jupiterTokens = mangoStore((s) => s.jupiterTokens)
|
||||
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
|
||||
const [useMargin, setUseMargin] = useState(true)
|
||||
const [placingOrder, setPlacingOrder] = useState(false)
|
||||
|
||||
const baseSymbol = useMemo(() => {
|
||||
return selectedMarket?.name.split('/')[0]
|
||||
}, [selectedMarket])
|
||||
|
||||
const baseLogoURI = useMemo(() => {
|
||||
if (!baseSymbol || !jupiterTokens.length) return ''
|
||||
const token = jupiterTokens.find((t) => t.symbol === baseSymbol)
|
||||
if (token) {
|
||||
return token.logoURI
|
||||
}
|
||||
return ''
|
||||
}, [baseSymbol, jupiterTokens])
|
||||
|
||||
const quoteSymbol = useMemo(() => {
|
||||
return selectedMarket?.name.split('/')[1]
|
||||
}, [selectedMarket])
|
||||
|
||||
const quoteLogoURI = useMemo(() => {
|
||||
if (!quoteSymbol || !jupiterTokens.length) return ''
|
||||
const token = jupiterTokens.find((t) => t.symbol === quoteSymbol)
|
||||
if (token) {
|
||||
return token.logoURI
|
||||
}
|
||||
return ''
|
||||
}, [quoteSymbol, jupiterTokens])
|
||||
|
||||
const setTradeType = useCallback(
|
||||
(tradeType: 'Limit' | 'Market') => {
|
||||
set((s) => {
|
||||
|
@ -66,7 +89,6 @@ const AdvancedTradeForm = () => {
|
|||
const handleBaseSizeChange = useCallback(
|
||||
(e: NumberFormatValues, info: SourceInfo) => {
|
||||
if (info.source !== 'event') return
|
||||
console.log('ho')
|
||||
|
||||
set((s) => {
|
||||
s.tradeForm.baseSize = e.value
|
||||
|
@ -84,7 +106,6 @@ const AdvancedTradeForm = () => {
|
|||
const handleQuoteSizeChange = useCallback(
|
||||
(e: NumberFormatValues, info: SourceInfo) => {
|
||||
if (info.source !== 'event') return
|
||||
console.log('hi')
|
||||
|
||||
set((s) => {
|
||||
s.tradeForm.quoteSize = e.value
|
||||
|
@ -155,7 +176,7 @@ const AdvancedTradeForm = () => {
|
|||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
|
||||
if (!group || !mangoAccount) return
|
||||
|
||||
setPlacingOrder(true)
|
||||
try {
|
||||
const orderType = tradeForm.ioc
|
||||
? Serum3OrderType.immediateOrCancel
|
||||
|
@ -197,6 +218,8 @@ const AdvancedTradeForm = () => {
|
|||
type: 'error',
|
||||
})
|
||||
console.error('Place trade error:', e)
|
||||
} finally {
|
||||
setPlacingOrder(false)
|
||||
}
|
||||
}, [t])
|
||||
|
||||
|
@ -255,7 +278,7 @@ const AdvancedTradeForm = () => {
|
|||
{tradeForm.tradeType === 'Limit' ? (
|
||||
<>
|
||||
<div className="mb-2 mt-4 flex items-center justify-between">
|
||||
<p className="text-xs text-th-fgd-3">Limit Price</p>
|
||||
<p className="text-xs text-th-fgd-3">{t('trade:limit-price')}</p>
|
||||
</div>
|
||||
<div className="default-transition flex items-center rounded-md border border-th-bkg-4 bg-th-bkg-1 p-2 text-xs font-bold text-th-fgd-1 md:hover:border-th-fgd-4 md:hover:bg-th-bkg-2 lg:text-base">
|
||||
<NumberFormat
|
||||
|
@ -278,10 +301,21 @@ const AdvancedTradeForm = () => {
|
|||
</>
|
||||
) : null}
|
||||
<div className="my-2 flex items-center justify-between">
|
||||
<p className="text-xs text-th-fgd-3">{t('amount')}</p>
|
||||
<p className="text-xs text-th-fgd-3">{t('trade:amount')}</p>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="default-transition flex items-center rounded-md border border-th-bkg-4 bg-th-bkg-1 p-2 text-xs font-bold text-th-fgd-1 md:hover:border-th-fgd-4 md:hover:bg-th-bkg-2 lg:text-base">
|
||||
<div className="default-transition flex items-center rounded-md rounded-b-none border border-th-bkg-4 bg-th-bkg-1 p-2 text-xs font-bold text-th-fgd-1 md:hover:z-10 md:hover:border-th-fgd-4 md:hover:bg-th-bkg-2 lg:text-base">
|
||||
{baseLogoURI ? (
|
||||
<Image
|
||||
className="rounded-full"
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={baseLogoURI}
|
||||
/>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
)}
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
|
@ -290,7 +324,7 @@ const AdvancedTradeForm = () => {
|
|||
decimalScale={6}
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
className="w-full bg-transparent font-mono focus:outline-none"
|
||||
className="ml-2 w-full bg-transparent font-mono focus:outline-none"
|
||||
placeholder="0.00"
|
||||
value={tradeForm.baseSize}
|
||||
onValueChange={handleBaseSizeChange}
|
||||
|
@ -299,7 +333,18 @@ const AdvancedTradeForm = () => {
|
|||
{baseSymbol}
|
||||
</div>
|
||||
</div>
|
||||
<div className="default-transition mt-1 flex items-center rounded-md border border-th-bkg-4 bg-th-bkg-1 p-2 text-xs font-bold text-th-fgd-1 md:hover:border-th-fgd-4 md:hover:bg-th-bkg-2 lg:text-base">
|
||||
<div className="default-transition -mt-[1px] flex items-center rounded-md rounded-t-none border border-th-bkg-4 bg-th-bkg-1 p-2 text-xs font-bold text-th-fgd-1 md:hover:border-th-fgd-4 md:hover:bg-th-bkg-2 lg:text-base">
|
||||
{quoteLogoURI ? (
|
||||
<Image
|
||||
className="rounded-full"
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={quoteLogoURI}
|
||||
/>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
)}
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
|
@ -308,7 +353,7 @@ const AdvancedTradeForm = () => {
|
|||
decimalScale={6}
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
className="w-full bg-transparent font-mono focus:outline-none"
|
||||
className="ml-2 w-full bg-transparent font-mono focus:outline-none"
|
||||
placeholder="0.00"
|
||||
value={tradeForm.quoteSize}
|
||||
onValueChange={handleQuoteSizeChange}
|
||||
|
@ -325,27 +370,27 @@ const AdvancedTradeForm = () => {
|
|||
<div className="flex flex-wrap px-5">
|
||||
{tradeForm.tradeType === 'Limit' ? (
|
||||
<div className="flex">
|
||||
<div className="mr-4 mt-4">
|
||||
<div className="mr-4 mt-4" id="trade-step-six">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={250}
|
||||
placement="left"
|
||||
content={t('tooltip-post')}
|
||||
content={t('trade:tooltip-post')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={tradeForm.postOnly}
|
||||
onChange={(e) => handlePostOnlyChange(e.target.checked)}
|
||||
>
|
||||
Post
|
||||
{t('trade:post')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="mr-4 mt-4">
|
||||
<div className="mr-4 mt-4" id="trade-step-seven">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={250}
|
||||
placement="top"
|
||||
content={t('tooltip-ioc')}
|
||||
placement="left"
|
||||
content={t('trade:tooltip-ioc')}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
|
@ -359,17 +404,17 @@ const AdvancedTradeForm = () => {
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mt-4">
|
||||
<div className="mt-4" id="trade-step-eight">
|
||||
<Tooltip
|
||||
delay={250}
|
||||
placement="left"
|
||||
content={t('tooltip-enable-margin')}
|
||||
content={t('trade:tooltip-enable-margin')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={useMargin}
|
||||
onChange={(e) => setUseMargin(e.target.checked)}
|
||||
>
|
||||
{t('margin')}
|
||||
{t('trade:margin')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
@ -385,7 +430,16 @@ const AdvancedTradeForm = () => {
|
|||
disabled={false}
|
||||
size="large"
|
||||
>
|
||||
<span className="capitalize">Place {tradeForm.side} Order</span>
|
||||
{!placingOrder ? (
|
||||
<span className="capitalize">
|
||||
{t('trade:place-order', { side: tradeForm.side })}
|
||||
</span>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Loading />
|
||||
<span>{t('trade:placing-order')}</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Serum3Side } from '@blockworks-foundation/mango-v4'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { LinkIcon, TrashIcon } from '@heroicons/react/20/solid'
|
||||
|
@ -7,16 +8,21 @@ 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 { useViewport } from 'hooks/useViewport'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
import { formatFixedDecimals, getDecimalCount } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import MarketLogos from './MarketLogos'
|
||||
|
||||
const OpenOrders = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { connected } = useWallet()
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
const [cancelId, setCancelId] = useState<string>('')
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
const handleCancelOrder = useCallback(
|
||||
async (o: Order) => {
|
||||
|
@ -27,7 +33,7 @@ const OpenOrders = () => {
|
|||
const actions = mangoStore.getState().actions
|
||||
|
||||
if (!group || !mangoAccount) return
|
||||
|
||||
setCancelId(o.orderId.toString())
|
||||
try {
|
||||
const tx = await client.serum3CancelOrder(
|
||||
group,
|
||||
|
@ -45,11 +51,13 @@ const OpenOrders = () => {
|
|||
} catch (e: any) {
|
||||
console.error('Error canceling', e)
|
||||
notify({
|
||||
title: t('order-error'),
|
||||
title: t('trade:cancel-order-error'),
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
setCancelId('')
|
||||
}
|
||||
},
|
||||
[t]
|
||||
|
@ -61,8 +69,8 @@ const OpenOrders = () => {
|
|||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">{t('market')}</th>
|
||||
<th className="text-right">{t('side')}</th>
|
||||
<th className="text-right">{t('size')}</th>
|
||||
<th className="text-right">{t('trade:side')}</th>
|
||||
<th className="text-right">{t('trade:size')}</th>
|
||||
<th className="text-right">{t('price')}</th>
|
||||
<th className="text-right">{t('value')}</th>
|
||||
<th className="text-right"></th>
|
||||
|
@ -90,15 +98,21 @@ const OpenOrders = () => {
|
|||
<td className="text-right">
|
||||
<SideBadge side={o.side} />
|
||||
</td>
|
||||
<td className="text-right">{o.size}</td>
|
||||
<td className="text-right">
|
||||
<span>
|
||||
{o.price}{' '}
|
||||
<span className="text-th-fgd-4">
|
||||
{quoteTokenBank?.name}
|
||||
<td className="text-right font-mono">
|
||||
{o.size.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getDecimalCount(o.size),
|
||||
})}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="font-mono">
|
||||
{o.price.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getDecimalCount(o.price),
|
||||
})}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{quoteSymbol}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</td>
|
||||
<td className="text-right">
|
||||
{formatFixedDecimals(o.size * o.price, true)}
|
||||
</td>
|
||||
|
@ -106,10 +120,15 @@ const OpenOrders = () => {
|
|||
<div className="flex justify-end">
|
||||
<Tooltip content={t('cancel')}>
|
||||
<IconButton
|
||||
disabled={cancelId === o.orderId.toString()}
|
||||
onClick={() => handleCancelOrder(o)}
|
||||
size="small"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
{cancelId === o.orderId.toString() ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : (
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
@ -117,19 +136,92 @@ const OpenOrders = () => {
|
|||
</tr>
|
||||
)
|
||||
})
|
||||
.flat()}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="pb-20">
|
||||
{Object.entries(openOrders).map(([marketPk, orders]) => {
|
||||
return orders.map((o) => {
|
||||
const group = mangoStore.getState().group
|
||||
const market = group?.getSerum3MarketByPk(new PublicKey(marketPk))
|
||||
let baseLogoURI = ''
|
||||
let quoteLogoURI = ''
|
||||
const baseSymbol = group?.getFirstBankByTokenIndex(
|
||||
market!.baseTokenIndex
|
||||
).name
|
||||
const quoteSymbol = group?.getFirstBankByTokenIndex(
|
||||
market!.quoteTokenIndex
|
||||
).name
|
||||
if (jupiterTokens.length) {
|
||||
baseLogoURI = jupiterTokens.find(
|
||||
(t) => t.symbol === baseSymbol
|
||||
)!.logoURI
|
||||
quoteLogoURI = jupiterTokens.find(
|
||||
(t) => t.symbol === quoteSymbol
|
||||
)!.logoURI
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={`${o.side}${o.size}${o.price}`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos
|
||||
baseURI={baseLogoURI}
|
||||
quoteURI={quoteLogoURI}
|
||||
/>
|
||||
<div>
|
||||
<p className="text-sm text-th-fgd-1">{market?.name}</p>
|
||||
<span
|
||||
className={`capitalize ${
|
||||
o.side === 'buy' ? 'text-th-green' : 'text-th-red'
|
||||
}`}
|
||||
>
|
||||
{o.side}
|
||||
</span>{' '}
|
||||
<span className="font-mono">
|
||||
{o.size.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getDecimalCount(o.size),
|
||||
})}
|
||||
</span>{' '}
|
||||
<span className="text-th-fgd-4">at</span>{' '}
|
||||
<span className="font-mono">
|
||||
{o.price.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getDecimalCount(o.price),
|
||||
})}
|
||||
</span>{' '}
|
||||
<span className="text-th-fgd-4">{quoteSymbol}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3 pl-4">
|
||||
<span>{formatFixedDecimals(o.size * o.price, true)}</span>
|
||||
<IconButton
|
||||
disabled={cancelId === o.orderId.toString()}
|
||||
onClick={() => handleCancelOrder(o)}
|
||||
>
|
||||
{cancelId === o.orderId.toString() ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : (
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
)}
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
.flat()}
|
||||
</tbody>
|
||||
</table>
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<p>No open orders...</p>
|
||||
<p>{t('trade:no-orders')}</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>Connect to view your open orders</p>
|
||||
<p>{t('trade:connect-orders')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ const groupBy = (
|
|||
const depth = 40
|
||||
|
||||
const Orderbook = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
|
||||
|
||||
// const [openOrderPrices, setOpenOrderPrices] = useState<any[]>([])
|
||||
|
@ -380,9 +380,9 @@ const Orderbook = () => {
|
|||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex items-center justify-between border-b border-th-bkg-3 px-4 py-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div id="trade-step-three" className="flex items-center space-x-2">
|
||||
<Tooltip
|
||||
content={showBuys ? 'Hide Buys' : 'Show Buys'}
|
||||
content={showBuys ? t('trade:hide-bids') : t('trade:show-bids')}
|
||||
placement="top"
|
||||
>
|
||||
<button
|
||||
|
@ -396,7 +396,7 @@ const Orderbook = () => {
|
|||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={showSells ? 'Hide Sells' : 'Show Sells'}
|
||||
content={showSells ? t('trade:hide-asks') : t('trade:show-asks')}
|
||||
placement="top"
|
||||
>
|
||||
<button
|
||||
|
@ -411,17 +411,19 @@ const Orderbook = () => {
|
|||
</Tooltip>
|
||||
</div>
|
||||
{serum3MarketExternal ? (
|
||||
<Tooltip content="Grouping" placement="top">
|
||||
<GroupSize
|
||||
tickSize={serum3MarketExternal.tickSize}
|
||||
onChange={onGroupSizeChange}
|
||||
value={grouping}
|
||||
/>
|
||||
</Tooltip>
|
||||
<div id="trade-step-four">
|
||||
<Tooltip content={t('trade:grouping')} placement="top">
|
||||
<GroupSize
|
||||
tickSize={serum3MarketExternal.tickSize}
|
||||
onChange={onGroupSizeChange}
|
||||
value={grouping}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 px-4 pt-2 pb-1 text-xxs text-th-fgd-4">
|
||||
<div className="col-span-1 text-right">{t('size')}</div>
|
||||
<div className="col-span-1 text-right">{t('trade:size')}</div>
|
||||
<div className="col-span-1 text-right">{t('price')}</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -458,9 +460,12 @@ const Orderbook = () => {
|
|||
})
|
||||
: null}
|
||||
{showBuys && showSells ? (
|
||||
<div className="my-2 grid grid-cols-2 border-y border-th-bkg-3 py-2 px-4 text-xs text-th-fgd-4">
|
||||
<div
|
||||
className="my-2 grid grid-cols-2 border-y border-th-bkg-3 py-2 px-4 text-xs text-th-fgd-4"
|
||||
id="trade-step-nine"
|
||||
>
|
||||
<div className="col-span-1 flex justify-between">
|
||||
<div className="text-xxs">{t('spread')}</div>
|
||||
<div className="text-xxs">{t('trade:spread')}</div>
|
||||
<div className="font-mono">
|
||||
{orderbookData?.spreadPercentage.toFixed(2)}%
|
||||
</div>
|
||||
|
|
|
@ -4,12 +4,12 @@ import Orderbook from './Orderbook'
|
|||
import RecentTrades from './RecentTrades'
|
||||
|
||||
const TABS: [string, number][] = [
|
||||
['book', 0],
|
||||
['trades', 0],
|
||||
['trade:book', 0],
|
||||
['trade:trades', 0],
|
||||
]
|
||||
|
||||
const OrderbookAndTrades = () => {
|
||||
const [activeTab, setActiveTab] = useState('book')
|
||||
const [activeTab, setActiveTab] = useState('trade:book')
|
||||
return (
|
||||
<div className="hide-scroll h-full">
|
||||
<div className="border-b border-r border-th-bkg-3">
|
||||
|
@ -20,11 +20,17 @@ const OrderbookAndTrades = () => {
|
|||
fillWidth
|
||||
/>
|
||||
</div>
|
||||
<div className={`h-full ${activeTab === 'book' ? 'visible' : 'hidden'}`}>
|
||||
<div
|
||||
className={`h-full ${
|
||||
activeTab === 'trade:book' ? 'visible' : 'hidden'
|
||||
}`}
|
||||
>
|
||||
<Orderbook />
|
||||
</div>
|
||||
<div
|
||||
className={`h-full ${activeTab === 'trades' ? 'visible' : 'hidden'}`}
|
||||
className={`h-full ${
|
||||
activeTab === 'trade:trades' ? 'visible' : 'hidden'
|
||||
}`}
|
||||
>
|
||||
<RecentTrades />
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ChartTradeType } from 'types'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const RecentTrades = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const [trades, setTrades] = useState<any[]>([])
|
||||
const selectedMarket = mangoStore((s) => s.selectedMarket.current)
|
||||
const selectedMarketPk = selectedMarket?.serumMarketExternal.toBase58()
|
||||
|
@ -60,11 +60,11 @@ const RecentTrades = () => {
|
|||
}
|
||||
}, 5000)
|
||||
return (
|
||||
<div className="h-full overflow-y-scroll ">
|
||||
<div className="thin-scroll h-full overflow-y-scroll">
|
||||
<div className={`mb-1 grid grid-cols-3 px-4 pt-2 text-xxs text-th-fgd-4`}>
|
||||
<div className="text-right">{`${t('price')} (${quoteSymbol})`} </div>
|
||||
<div className={`text-right`}>
|
||||
{t('size')} ({baseSymbol})
|
||||
{t('trade:size')} ({baseSymbol})
|
||||
</div>
|
||||
<div className={`text-right`}>{t('time')}</div>
|
||||
</div>
|
||||
|
|
|
@ -12,6 +12,8 @@ import AdvancedTradeForm from './AdvancedTradeForm'
|
|||
import TradeInfoTabs from './TradeInfoTabs'
|
||||
import MobileTradeAdvancedPage from './MobileTradeAdvancedPage'
|
||||
import OrderbookAndTrades from './OrderbookAndTrades'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import TradeOnboardingTour from '@components/tours/TradeOnboardingTour'
|
||||
|
||||
const TradingViewChart = dynamic(() => import('./TradingViewChart'), {
|
||||
ssr: false,
|
||||
|
@ -49,6 +51,8 @@ const TradeAdvancedPage = () => {
|
|||
const [currentBreakpoint, setCurrentBreakpoint] = useState<string>()
|
||||
const { uiLocked } = mangoStore((s) => s.settings)
|
||||
const showMobileView = width <= breakpoints.md
|
||||
const tourSettings = mangoStore((s) => s.settings.tours)
|
||||
const { connected } = useWallet()
|
||||
|
||||
const defaultLayouts: ReactGridLayout.Layouts = useMemo(() => {
|
||||
const topnavbarHeight = 67
|
||||
|
@ -162,52 +166,62 @@ const TradeAdvancedPage = () => {
|
|||
return showMobileView ? (
|
||||
<MobileTradeAdvancedPage />
|
||||
) : (
|
||||
<ResponsiveGridLayout
|
||||
// layouts={savedLayouts ? savedLayouts : defaultLayouts}
|
||||
layouts={defaultLayouts}
|
||||
breakpoints={gridBreakpoints}
|
||||
cols={{
|
||||
xxxl: totalCols,
|
||||
xxl: totalCols,
|
||||
xl: totalCols,
|
||||
lg: totalCols,
|
||||
md: totalCols,
|
||||
sm: totalCols,
|
||||
}}
|
||||
rowHeight={1}
|
||||
isDraggable={!uiLocked}
|
||||
isResizable={!uiLocked}
|
||||
onBreakpointChange={(newBreakpoint) => onBreakpointChange(newBreakpoint)}
|
||||
onLayoutChange={(layout, layouts) => onLayoutChange(layouts)}
|
||||
measureBeforeMount
|
||||
containerPadding={[0, 0]}
|
||||
margin={[0, 0]}
|
||||
useCSSTransforms
|
||||
>
|
||||
<div key="market-header" className="z-10">
|
||||
<AdvancedMarketHeader />
|
||||
</div>
|
||||
<div key="tv-chart" className="h-full border border-x-0 border-th-bkg-3">
|
||||
<div className={`relative h-full overflow-auto`}>
|
||||
<TradingViewChart />
|
||||
<>
|
||||
<ResponsiveGridLayout
|
||||
// layouts={savedLayouts ? savedLayouts : defaultLayouts}
|
||||
layouts={defaultLayouts}
|
||||
breakpoints={gridBreakpoints}
|
||||
cols={{
|
||||
xxxl: totalCols,
|
||||
xxl: totalCols,
|
||||
xl: totalCols,
|
||||
lg: totalCols,
|
||||
md: totalCols,
|
||||
sm: totalCols,
|
||||
}}
|
||||
rowHeight={1}
|
||||
isDraggable={!uiLocked}
|
||||
isResizable={!uiLocked}
|
||||
onBreakpointChange={(newBreakpoint) =>
|
||||
onBreakpointChange(newBreakpoint)
|
||||
}
|
||||
onLayoutChange={(layout, layouts) => onLayoutChange(layouts)}
|
||||
measureBeforeMount
|
||||
containerPadding={[0, 0]}
|
||||
margin={[0, 0]}
|
||||
useCSSTransforms
|
||||
>
|
||||
<div key="market-header" className="z-10">
|
||||
<AdvancedMarketHeader />
|
||||
</div>
|
||||
</div>
|
||||
<div key="balances">
|
||||
<TradeInfoTabs />
|
||||
</div>
|
||||
<div
|
||||
key="trade-form"
|
||||
className="border border-t-0 border-r-0 border-th-bkg-3 md:border-b lg:border-b-0"
|
||||
>
|
||||
<AdvancedTradeForm />
|
||||
</div>
|
||||
<div
|
||||
key="orderbook"
|
||||
className="border border-y-0 border-r-0 border-th-bkg-3"
|
||||
>
|
||||
<OrderbookAndTrades />
|
||||
</div>
|
||||
</ResponsiveGridLayout>
|
||||
<div
|
||||
key="tv-chart"
|
||||
className="h-full border border-x-0 border-th-bkg-3"
|
||||
>
|
||||
<div className={`relative h-full overflow-auto`}>
|
||||
<TradingViewChart />
|
||||
</div>
|
||||
</div>
|
||||
<div key="balances">
|
||||
<TradeInfoTabs />
|
||||
</div>
|
||||
<div
|
||||
key="trade-form"
|
||||
className="border border-t-0 border-r-0 border-th-bkg-3 md:border-b lg:border-b-0"
|
||||
>
|
||||
<AdvancedTradeForm />
|
||||
</div>
|
||||
<div
|
||||
key="orderbook"
|
||||
className="border border-y-0 border-r-0 border-th-bkg-3"
|
||||
>
|
||||
<OrderbookAndTrades />
|
||||
</div>
|
||||
</ResponsiveGridLayout>
|
||||
{!tourSettings?.trade_tour_seen && connected ? (
|
||||
<TradeOnboardingTour />
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/image'
|
||||
import { useMemo } from 'react'
|
||||
import { formatDecimal } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
|
||||
const Balances = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
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 { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
const banks = useMemo(() => {
|
||||
if (group) {
|
||||
|
@ -41,14 +45,14 @@ const Balances = () => {
|
|||
return []
|
||||
}, [group, mangoAccount])
|
||||
|
||||
return (
|
||||
return showTableView ? (
|
||||
<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>
|
||||
<th className="bg-th-bkg-1 text-right">{t('trade:in-orders')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('trade:unsettled')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -97,6 +101,61 @@ const Balances = () => {
|
|||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<>
|
||||
{banks.map(({ key, value }) => {
|
||||
const bank = value[0]
|
||||
|
||||
let logoURI
|
||||
if (jupiterTokens.length) {
|
||||
logoURI = jupiterTokens.find(
|
||||
(t) => t.address === bank.mint.toString()
|
||||
)!.logoURI
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
key={key}
|
||||
>
|
||||
<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>
|
||||
<div className="text-right">
|
||||
<p className="mb-0.5 font-mono text-sm text-th-fgd-1">
|
||||
{mangoAccount
|
||||
? formatDecimal(
|
||||
mangoAccount.getTokenBalanceUi(bank),
|
||||
bank.mintDecimals
|
||||
)
|
||||
: 0}
|
||||
</p>
|
||||
<div className="flex space-x-3">
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
{t('trade:in-orders')}:{' '}
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{spotBalances[bank.mint.toString()]?.inOrders || 0.0}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
{t('trade:unsettled')}:{' '}
|
||||
<span className="font-mono text-th-fgd-3">
|
||||
{spotBalances[bank.mint.toString()]?.unsettled || 0.0}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,18 +4,59 @@ import OpenOrders from './OpenOrders'
|
|||
import Balances from './TradeBalances'
|
||||
import UnsettledTrades from './UnsettledTrades'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
const TradeInfoTabs = () => {
|
||||
const [selectedTab, setSelectedTab] = useState('Balances')
|
||||
const [selectedTab, setSelectedTab] = useState('balances')
|
||||
const openOrders = mangoStore((s) => s.mangoAccount.openOrders)
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const openOrdersAccounts =
|
||||
mangoStore.getState().mangoAccount.openOrderAccounts
|
||||
const group = mangoStore((s) => s.group)
|
||||
|
||||
const unsettledSpotBalances = useMemo(() => {
|
||||
if (!group || !mangoAccount || !openOrdersAccounts) 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)
|
||||
)
|
||||
if (openOrdersAccForMkt) {
|
||||
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 tabsWithCount: [string, number][] = useMemo(() => {
|
||||
return [
|
||||
['Balances', 0],
|
||||
['Orders', Object.values(openOrders).flat().length],
|
||||
['Unsettled', 0],
|
||||
['balances', 0],
|
||||
['trade:orders', Object.values(openOrders).flat().length],
|
||||
['trade:unsettled', Object.values(unsettledSpotBalances).flat().length],
|
||||
]
|
||||
}, [openOrders])
|
||||
}, [openOrders, mangoAccount])
|
||||
|
||||
return (
|
||||
<div className="hide-scroll h-full overflow-y-scroll">
|
||||
|
@ -27,9 +68,11 @@ const TradeInfoTabs = () => {
|
|||
showBorders
|
||||
/>
|
||||
</div>
|
||||
{selectedTab === 'Balances' ? <Balances /> : null}
|
||||
{selectedTab === 'Orders' ? <OpenOrders /> : null}
|
||||
{selectedTab === 'Unsettled' ? <UnsettledTrades /> : null}
|
||||
{selectedTab === 'balances' ? <Balances /> : null}
|
||||
{selectedTab === 'trade:orders' ? <OpenOrders /> : null}
|
||||
{selectedTab === 'trade:unsettled' ? (
|
||||
<UnsettledTrades unsettledSpotBalances={unsettledSpotBalances} />
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,50 +1,28 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
|
||||
import Button from '@components/shared/Button'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { CheckIcon, LinkIcon } from '@heroicons/react/20/solid'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
|
||||
const UnsettledTrades = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = mangoStore((s) => s.mangoAccount.current)
|
||||
const openOrdersAccounts =
|
||||
mangoStore.getState().mangoAccount.openOrderAccounts
|
||||
const UnsettledTrades = ({
|
||||
unsettledSpotBalances,
|
||||
}: {
|
||||
unsettledSpotBalances: any
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { connected } = useWallet()
|
||||
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 [settleMktAddress, setSettleMktAddress] = useState<string>('')
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
|
||||
const handleSettleFunds = useCallback(async (mktAddress: string) => {
|
||||
const client = mangoStore.getState().client
|
||||
|
@ -53,7 +31,7 @@ const UnsettledTrades = () => {
|
|||
const actions = mangoStore.getState().actions
|
||||
|
||||
if (!group || !mangoAccount) return
|
||||
|
||||
setSettleMktAddress(mktAddress)
|
||||
try {
|
||||
const txid = await client.serum3SettleFunds(
|
||||
group,
|
||||
|
@ -70,63 +48,139 @@ const UnsettledTrades = () => {
|
|||
} catch (e: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: 'Settle transaction failed',
|
||||
title: t('trade:settle-funds-error'),
|
||||
description: e?.message,
|
||||
txid: e?.txid,
|
||||
})
|
||||
console.error('Settle funds error:', e)
|
||||
} finally {
|
||||
setSettleMktAddress('')
|
||||
}
|
||||
}, [])
|
||||
|
||||
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>
|
||||
return connected ? (
|
||||
Object.values(unsettledSpotBalances).flat().length ? (
|
||||
showTableView ? (
|
||||
<table className="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="bg-th-bkg-1 text-left">{t('market')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('trade:base')}</th>
|
||||
<th className="bg-th-bkg-1 text-right">{t('trade:quote')}</th>
|
||||
<th className="bg-th-bkg-1 text-right" />
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(unsettledSpotBalances).map(
|
||||
([mktAddress, balance]) => {
|
||||
const market = group.getSerum3MarketByPk(
|
||||
new PublicKey(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}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{base}
|
||||
</span>
|
||||
</td>
|
||||
<td className="text-right font-mono">
|
||||
{unsettledSpotBalances[mktAddress].quote || 0.0}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{quote}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex justify-end">
|
||||
<Tooltip content={t('trade:settle-funds')}>
|
||||
<IconButton
|
||||
onClick={() => handleSettleFunds(mktAddress)}
|
||||
size="small"
|
||||
>
|
||||
{settleMktAddress === mktAddress ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : (
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="pb-20">
|
||||
{Object.entries(unsettledSpotBalances).map(
|
||||
([mktAddress, balance]) => {
|
||||
const market = group.getSerum3MarketByPk(
|
||||
new PublicKey(mktAddress)
|
||||
)
|
||||
const base = market?.name.split('/')[0]
|
||||
const quote = market?.name.split('/')[1]
|
||||
|
||||
return (
|
||||
<div
|
||||
key={mktAddress}
|
||||
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span>{market ? market.name : ''}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
{unsettledSpotBalances[mktAddress].base ? (
|
||||
<span className="font-mono text-sm">
|
||||
{unsettledSpotBalances[mktAddress].base}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{base}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
{unsettledSpotBalances[mktAddress].quote ? (
|
||||
<span className="font-mono text-sm">
|
||||
{unsettledSpotBalances[mktAddress].quote}{' '}
|
||||
<span className="font-body tracking-wide text-th-fgd-4">
|
||||
{quote}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
<IconButton onClick={() => handleSettleFunds(mktAddress)}>
|
||||
{settleMktAddress === mktAddress ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : (
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
)}
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<p>{t('trade:no-unsettled')}</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col items-center p-8">
|
||||
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p>{t('trade:connect-unsettled')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ const onConnectFetchAccountData = async (wallet: Wallet) => {
|
|||
if (!wallet) return
|
||||
const actions = mangoStore.getState().actions
|
||||
await actions.fetchMangoAccounts(wallet.adapter as unknown as AnchorWallet)
|
||||
actions.fetchProfilePicture(wallet.adapter as unknown as AnchorWallet)
|
||||
actions.fetchTourSettings(wallet.adapter.publicKey?.toString() as string)
|
||||
actions.fetchWalletTokens(wallet.adapter as unknown as AnchorWallet)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import EditProfileModal from '@components/modals/EditProfileModal'
|
|||
|
||||
const ConnectedMenu = () => {
|
||||
const { t } = useTranslation('common')
|
||||
// const [showProfileImageModal, setShowProfileImageModal] = useState(false)
|
||||
const [showEditProfileModal, setShowEditProfileModal] = useState(false)
|
||||
const set = mangoStore((s) => s.set)
|
||||
const { publicKey, disconnect, wallet } = useWallet()
|
||||
|
@ -31,6 +30,7 @@ const ConnectedMenu = () => {
|
|||
set((state) => {
|
||||
state.mangoAccount.current = undefined
|
||||
state.connected = false
|
||||
state.mangoAccount.openOrders = {}
|
||||
})
|
||||
disconnect()
|
||||
wallet?.adapter.disconnect()
|
||||
|
@ -54,27 +54,29 @@ const ConnectedMenu = () => {
|
|||
{({ open }) => (
|
||||
<div className="relative">
|
||||
<Menu.Button
|
||||
className={`default-transition flex h-16 ${
|
||||
className={`default-transition h-16 ${
|
||||
!isMobile ? 'w-48 border-l border-th-bkg-3 px-3' : ''
|
||||
} items-center hover:bg-th-bkg-2 focus:outline-none`}
|
||||
} hover:bg-th-bkg-2 focus:outline-none`}
|
||||
>
|
||||
<ProfileImage
|
||||
imageSize="40"
|
||||
placeholderSize="24"
|
||||
isOwnerProfile
|
||||
/>
|
||||
{!loadProfileDetails && !isMobile ? (
|
||||
<div className="ml-2.5 w-32 text-left">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{wallet_pk
|
||||
? abbreviateAddress(new PublicKey(wallet_pk))
|
||||
: ''}
|
||||
</p>
|
||||
<p className="truncate pr-2 text-sm font-bold capitalize text-th-fgd-1">
|
||||
{profile_name}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex items-center" id="account-step-one">
|
||||
<ProfileImage
|
||||
imageSize="40"
|
||||
placeholderSize="24"
|
||||
isOwnerProfile
|
||||
/>
|
||||
{!loadProfileDetails && !isMobile ? (
|
||||
<div className="ml-2.5 w-32 text-left">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{wallet_pk
|
||||
? abbreviateAddress(new PublicKey(wallet_pk))
|
||||
: ''}
|
||||
</p>
|
||||
<p className="truncate pr-2 text-sm font-bold capitalize text-th-fgd-1">
|
||||
{profile_name}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
appear={true}
|
||||
|
|
|
@ -8,14 +8,12 @@
|
|||
"account-update-success": "Account updated successfully",
|
||||
"account-value": "Account Value",
|
||||
"accounts": "Accounts",
|
||||
"amount": "Amount",
|
||||
"asset-weight": "Asset Weight",
|
||||
"asset-weight-desc": "The asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"book": "Book",
|
||||
"borrow": "Borrow",
|
||||
"borrow-amount": "Borrow Amount",
|
||||
"borrow-fee": "Borrow Fee",
|
||||
|
@ -46,7 +44,6 @@
|
|||
"governance": "Governance",
|
||||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"in-orders": "In Orders",
|
||||
"insufficient-sol": "Solana requires 0.00757 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -54,7 +51,6 @@
|
|||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
"liquidity": "Liquidity",
|
||||
"margin": "Margin",
|
||||
"market": "Market",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow",
|
||||
|
@ -62,7 +58,6 @@
|
|||
"new-account": "New Account",
|
||||
"new-account-failed": "Failed to create account",
|
||||
"new-account-success": "Your new account is ready 😎",
|
||||
"oracle-price": "Oracle Price",
|
||||
"pnl": "PnL",
|
||||
"price": "Price",
|
||||
"quantity": "Quantity",
|
||||
|
@ -75,9 +70,6 @@
|
|||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stats": "Stats",
|
||||
"swap": "Swap",
|
||||
"time": "Time",
|
||||
|
@ -90,11 +82,9 @@
|
|||
"total-deposit-value": "Total Deposit Value",
|
||||
"total-interest-value": "Total Interest Value",
|
||||
"trade": "Trade",
|
||||
"trades": "Trades",
|
||||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"unavailable": "Unavailable",
|
||||
"unsettled": "Unsettled",
|
||||
"update": "Update",
|
||||
"utilization": "Utilization",
|
||||
"value": "Value",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"amount": "Amount",
|
||||
"base": "Base Token",
|
||||
"book": "Book",
|
||||
"cancel-order-error": "Failed to cancel order",
|
||||
"connect-orders": "Connect to view your open orders",
|
||||
"connect-unsettled": "Connect to view your unsettled trades",
|
||||
"grouping": "Grouping",
|
||||
"hide-asks": "Hide Asks",
|
||||
"hide-bids": "Hide Bids",
|
||||
"in-orders": "In Orders",
|
||||
"limit-price": "Limit Price",
|
||||
"margin": "Margin",
|
||||
"no-orders": "No open orders...",
|
||||
"no-unsettled": "No unsettled funds...",
|
||||
"oracle-price": "Oracle Price",
|
||||
"orders": "Orders",
|
||||
"order-error": "Failed to place order",
|
||||
"post": "Post",
|
||||
"place-order": "Place {{side}} Order",
|
||||
"placing-order": "Placing Order",
|
||||
"quote": "Quote Token",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate or cancel orders are guaranteed to be the taker or they will be canceled.",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker order or else they will be canceled.",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
}
|
|
@ -8,14 +8,12 @@
|
|||
"account-update-success": "Account updated successfully",
|
||||
"account-value": "Account Value",
|
||||
"accounts": "Accounts",
|
||||
"amount": "Amount",
|
||||
"asset-weight": "Asset Weight",
|
||||
"asset-weight-desc": "The asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"book": "Book",
|
||||
"borrow": "Borrow",
|
||||
"borrow-amount": "Borrow Amount",
|
||||
"borrow-fee": "Borrow Fee",
|
||||
|
@ -46,7 +44,6 @@
|
|||
"governance": "Governance",
|
||||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"in-orders": "In Orders",
|
||||
"insufficient-sol": "Solana requires 0.00757 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -54,7 +51,6 @@
|
|||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
"liquidity": "Liquidity",
|
||||
"margin": "Margin",
|
||||
"market": "Market",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow",
|
||||
|
@ -62,7 +58,6 @@
|
|||
"new-account": "New Account",
|
||||
"new-account-failed": "Failed to create account",
|
||||
"new-account-success": "Your new account is ready 😎",
|
||||
"oracle-price": "Oracle Price",
|
||||
"pnl": "PnL",
|
||||
"price": "Price",
|
||||
"quantity": "Quantity",
|
||||
|
@ -75,9 +70,6 @@
|
|||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stats": "Stats",
|
||||
"swap": "Swap",
|
||||
"time": "Time",
|
||||
|
@ -90,11 +82,9 @@
|
|||
"total-deposit-value": "Total Deposit Value",
|
||||
"total-interest-value": "Total Interest Value",
|
||||
"trade": "Trade",
|
||||
"trades": "Trades",
|
||||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"unavailable": "Unavailable",
|
||||
"unsettled": "Unsettled",
|
||||
"update": "Update",
|
||||
"utilization": "Utilization",
|
||||
"value": "Value",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"amount": "Amount",
|
||||
"base": "Base Token",
|
||||
"book": "Book",
|
||||
"cancel-order-error": "Failed to cancel order",
|
||||
"connect-orders": "Connect to view your open orders",
|
||||
"connect-unsettled": "Connect to view your unsettled trades",
|
||||
"grouping": "Grouping",
|
||||
"hide-asks": "Hide Asks",
|
||||
"hide-bids": "Hide Bids",
|
||||
"in-orders": "In Orders",
|
||||
"limit-price": "Limit Price",
|
||||
"margin": "Margin",
|
||||
"no-orders": "No open orders...",
|
||||
"no-unsettled": "No unsettled funds...",
|
||||
"oracle-price": "Oracle Price",
|
||||
"orders": "Orders",
|
||||
"order-error": "Failed to place order",
|
||||
"post": "Post",
|
||||
"place-order": "Place {{side}} Order",
|
||||
"placing-order": "Placing Order",
|
||||
"quote": "Quote Token",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate or cancel orders are guaranteed to be the taker or they will be canceled.",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker order or else they will be canceled.",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
}
|
|
@ -8,14 +8,12 @@
|
|||
"account-update-success": "Account updated successfully",
|
||||
"account-value": "Account Value",
|
||||
"accounts": "Accounts",
|
||||
"amount": "Amount",
|
||||
"asset-weight": "Asset Weight",
|
||||
"asset-weight-desc": "The asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"book": "Book",
|
||||
"borrow": "Borrow",
|
||||
"borrow-amount": "Borrow Amount",
|
||||
"borrow-fee": "Borrow Fee",
|
||||
|
@ -46,7 +44,6 @@
|
|||
"governance": "Governance",
|
||||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"in-orders": "In Orders",
|
||||
"insufficient-sol": "Solana requires 0.00757 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -54,7 +51,6 @@
|
|||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
"liquidity": "Liquidity",
|
||||
"margin": "Margin",
|
||||
"market": "Market",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow",
|
||||
|
@ -62,7 +58,6 @@
|
|||
"new-account": "New Account",
|
||||
"new-account-failed": "Failed to create account",
|
||||
"new-account-success": "Your new account is ready 😎",
|
||||
"oracle-price": "Oracle Price",
|
||||
"pnl": "PnL",
|
||||
"price": "Price",
|
||||
"quantity": "Quantity",
|
||||
|
@ -75,9 +70,6 @@
|
|||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stats": "Stats",
|
||||
"swap": "Swap",
|
||||
"time": "Time",
|
||||
|
@ -90,11 +82,9 @@
|
|||
"total-deposit-value": "Total Deposit Value",
|
||||
"total-interest-value": "Total Interest Value",
|
||||
"trade": "Trade",
|
||||
"trades": "Trades",
|
||||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"unavailable": "Unavailable",
|
||||
"unsettled": "Unsettled",
|
||||
"update": "Update",
|
||||
"utilization": "Utilization",
|
||||
"value": "Value",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"amount": "Amount",
|
||||
"base": "Base Token",
|
||||
"book": "Book",
|
||||
"cancel-order-error": "Failed to cancel order",
|
||||
"connect-orders": "Connect to view your open orders",
|
||||
"connect-unsettled": "Connect to view your unsettled trades",
|
||||
"grouping": "Grouping",
|
||||
"hide-asks": "Hide Asks",
|
||||
"hide-bids": "Hide Bids",
|
||||
"in-orders": "In Orders",
|
||||
"limit-price": "Limit Price",
|
||||
"margin": "Margin",
|
||||
"no-orders": "No open orders...",
|
||||
"no-unsettled": "No unsettled funds...",
|
||||
"oracle-price": "Oracle Price",
|
||||
"orders": "Orders",
|
||||
"order-error": "Failed to place order",
|
||||
"post": "Post",
|
||||
"place-order": "Place {{side}} Order",
|
||||
"placing-order": "Placing Order",
|
||||
"quote": "Quote Token",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate or cancel orders are guaranteed to be the taker or they will be canceled.",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker order or else they will be canceled.",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
}
|
|
@ -8,14 +8,12 @@
|
|||
"account-update-success": "Account updated successfully",
|
||||
"account-value": "Account Value",
|
||||
"accounts": "Accounts",
|
||||
"amount": "Amount",
|
||||
"asset-weight": "Asset Weight",
|
||||
"asset-weight-desc": "The asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
|
||||
"available": "Available",
|
||||
"available-balance": "Available Balance",
|
||||
"balance": "Balance",
|
||||
"balances": "Balances",
|
||||
"book": "Book",
|
||||
"borrow": "Borrow",
|
||||
"borrow-amount": "Borrow Amount",
|
||||
"borrow-fee": "Borrow Fee",
|
||||
|
@ -46,7 +44,6 @@
|
|||
"governance": "Governance",
|
||||
"health": "Health",
|
||||
"health-impact": "Health Impact",
|
||||
"in-orders": "In Orders",
|
||||
"insufficient-sol": "Solana requires 0.00757 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
|
@ -54,7 +51,6 @@
|
|||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
"liquidity": "Liquidity",
|
||||
"margin": "Margin",
|
||||
"market": "Market",
|
||||
"max": "Max",
|
||||
"max-borrow": "Max Borrow",
|
||||
|
@ -62,7 +58,6 @@
|
|||
"new-account": "New Account",
|
||||
"new-account-failed": "Failed to create account",
|
||||
"new-account-success": "Your new account is ready 😎",
|
||||
"oracle-price": "Oracle Price",
|
||||
"pnl": "PnL",
|
||||
"price": "Price",
|
||||
"quantity": "Quantity",
|
||||
|
@ -75,9 +70,6 @@
|
|||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"stats": "Stats",
|
||||
"swap": "Swap",
|
||||
"time": "Time",
|
||||
|
@ -90,11 +82,9 @@
|
|||
"total-deposit-value": "Total Deposit Value",
|
||||
"total-interest-value": "Total Interest Value",
|
||||
"trade": "Trade",
|
||||
"trades": "Trades",
|
||||
"trade-history": "Trade History",
|
||||
"transaction": "Transaction",
|
||||
"unavailable": "Unavailable",
|
||||
"unsettled": "Unsettled",
|
||||
"update": "Update",
|
||||
"utilization": "Utilization",
|
||||
"value": "Value",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"amount": "Amount",
|
||||
"base": "Base Token",
|
||||
"book": "Book",
|
||||
"cancel-order-error": "Failed to cancel order",
|
||||
"connect-orders": "Connect to view your open orders",
|
||||
"connect-unsettled": "Connect to view your unsettled trades",
|
||||
"grouping": "Grouping",
|
||||
"hide-asks": "Hide Asks",
|
||||
"hide-bids": "Hide Bids",
|
||||
"in-orders": "In Orders",
|
||||
"limit-price": "Limit Price",
|
||||
"margin": "Margin",
|
||||
"no-orders": "No open orders...",
|
||||
"no-unsettled": "No unsettled funds...",
|
||||
"oracle-price": "Oracle Price",
|
||||
"orders": "Orders",
|
||||
"order-error": "Failed to place order",
|
||||
"post": "Post",
|
||||
"place-order": "Place {{side}} Order",
|
||||
"placing-order": "Placing Order",
|
||||
"quote": "Quote Token",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
"size": "Size",
|
||||
"spread": "Spread",
|
||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate or cancel orders are guaranteed to be the taker or they will be canceled.",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker order or else they will be canceled.",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
}
|
|
@ -4,7 +4,6 @@ import create from 'zustand'
|
|||
import { subscribeWithSelector } from 'zustand/middleware'
|
||||
import { AnchorProvider, Wallet, web3 } from '@project-serum/anchor'
|
||||
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
|
||||
import { getProfilePicture, ProfilePicture } from '@solflare-wallet/pfp'
|
||||
import { TOKEN_LIST_URL } from '@jup-ag/core'
|
||||
import { OpenOrders, Order } from '@project-serum/serum/lib/market'
|
||||
import { Wallet as WalletAdapter } from '@solana/wallet-adapter-react'
|
||||
|
@ -101,6 +100,27 @@ interface ProfileDetails {
|
|||
wallet_pk: string
|
||||
}
|
||||
|
||||
interface TourSettings {
|
||||
account_tour_seen: boolean
|
||||
swap_tour_seen: boolean
|
||||
trade_tour_seen: boolean
|
||||
wallet_pk: string
|
||||
}
|
||||
|
||||
// const defaultUserSettings = {
|
||||
// account_tour_seen: false,
|
||||
// default_language: 'English',
|
||||
// default_market: 'SOL-Perp',
|
||||
// orderbook_animation: false,
|
||||
// rpc_endpoint: 'Triton (RPC Pool)',
|
||||
// rpc_node_url: null,
|
||||
// spot_margin: false,
|
||||
// swap_tour_seen: false,
|
||||
// theme: 'Mango',
|
||||
// trade_tour_seen: false,
|
||||
// wallet_pk: '',
|
||||
// }
|
||||
|
||||
export type MangoStore = {
|
||||
coingeckoPrices: {
|
||||
data: any[]
|
||||
|
@ -140,6 +160,11 @@ export type MangoStore = {
|
|||
}
|
||||
serumMarkets: Serum3Market[]
|
||||
serumOrders: Order[] | undefined
|
||||
settings: {
|
||||
loading: boolean
|
||||
tours: TourSettings
|
||||
uiLocked: boolean
|
||||
}
|
||||
swap: {
|
||||
inputBank: Bank | undefined
|
||||
outputBank: Bank | undefined
|
||||
|
@ -149,9 +174,6 @@ export type MangoStore = {
|
|||
slippage: number
|
||||
}
|
||||
set: (x: (x: MangoStore) => void) => void
|
||||
settings: {
|
||||
uiLocked: boolean
|
||||
}
|
||||
tradeForm: {
|
||||
side: 'buy' | 'sell'
|
||||
price: string
|
||||
|
@ -162,8 +184,6 @@ export type MangoStore = {
|
|||
ioc: boolean
|
||||
}
|
||||
wallet: {
|
||||
loadProfilePic: boolean
|
||||
profilePic: ProfilePicture | undefined
|
||||
tokens: TokenAccount[]
|
||||
nfts: {
|
||||
data: NFT[] | []
|
||||
|
@ -183,8 +203,8 @@ export type MangoStore = {
|
|||
fetchMangoAccounts: (wallet: Wallet) => Promise<void>
|
||||
fetchNfts: (connection: Connection, walletPk: PublicKey) => void
|
||||
fetchSerumOpenOrders: (ma?: MangoAccount) => Promise<void>
|
||||
fetchProfilePicture: (wallet: Wallet) => void
|
||||
fetchProfileDetails: (walletPk: string) => void
|
||||
fetchTourSettings: (walletPk: string) => void
|
||||
fetchTradeHistory: (mangoAccountPk: string) => Promise<void>
|
||||
fetchWalletTokens: (wallet: Wallet) => Promise<void>
|
||||
connectMangoClientWithWallet: (wallet: WalletAdapter) => Promise<void>
|
||||
|
@ -238,6 +258,13 @@ const mangoStore = create<MangoStore>()(
|
|||
serumOrders: undefined,
|
||||
set: (fn) => _set(produce(fn)),
|
||||
settings: {
|
||||
loading: false,
|
||||
tours: {
|
||||
account_tour_seen: true,
|
||||
swap_tour_seen: true,
|
||||
trade_tour_seen: true,
|
||||
wallet_pk: '',
|
||||
},
|
||||
uiLocked: true,
|
||||
},
|
||||
tradeForm: {
|
||||
|
@ -258,8 +285,6 @@ const mangoStore = create<MangoStore>()(
|
|||
slippage: 0.5,
|
||||
},
|
||||
wallet: {
|
||||
loadProfilePic: true,
|
||||
profilePic: undefined,
|
||||
tokens: [],
|
||||
nfts: {
|
||||
data: [],
|
||||
|
@ -674,27 +699,6 @@ const mangoStore = create<MangoStore>()(
|
|||
console.error('Error fetching group', e)
|
||||
}
|
||||
},
|
||||
async fetchProfilePicture(wallet: Wallet) {
|
||||
const set = get().set
|
||||
const walletPk = wallet?.publicKey
|
||||
const connection = get().connection
|
||||
|
||||
if (!walletPk) return
|
||||
|
||||
try {
|
||||
const result = await getProfilePicture(connection, walletPk)
|
||||
|
||||
set((state) => {
|
||||
state.wallet.profilePic = result
|
||||
state.wallet.loadProfilePic = false
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Could not get profile picture', e)
|
||||
set((state) => {
|
||||
state.wallet.loadProfilePic = false
|
||||
})
|
||||
}
|
||||
},
|
||||
async fetchProfileDetails(walletPk: string) {
|
||||
const set = get().set
|
||||
set((state) => {
|
||||
|
@ -711,12 +715,34 @@ const mangoStore = create<MangoStore>()(
|
|||
})
|
||||
} catch (e) {
|
||||
notify({ type: 'error', title: 'Failed to load profile details' })
|
||||
console.error(e)
|
||||
console.log(e)
|
||||
set((state) => {
|
||||
state.profile.loadDetails = false
|
||||
})
|
||||
}
|
||||
},
|
||||
async fetchTourSettings(walletPk: string) {
|
||||
const set = get().set
|
||||
set((state) => {
|
||||
state.settings.loading = true
|
||||
})
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://mango-transaction-log.herokuapp.com/v4/user-data/settings-unsigned?wallet-pk=${walletPk}`
|
||||
)
|
||||
const data = await response.json()
|
||||
set((state) => {
|
||||
state.settings.tours = data
|
||||
state.settings.loading = false
|
||||
})
|
||||
} catch (e) {
|
||||
notify({ type: 'error', title: 'Failed to load profile details' })
|
||||
console.error(e)
|
||||
set((state) => {
|
||||
state.settings.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue