2023-08-06 21:08:35 -07:00
|
|
|
import mangoStore from '@store/mangoStore'
|
|
|
|
import useMangoAccount from 'hooks/useMangoAccount'
|
2023-08-14 20:06:00 -07:00
|
|
|
import { ReactNode, useCallback, useEffect, useState } from 'react'
|
2023-08-06 21:08:35 -07:00
|
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
import NumberFormat, {
|
|
|
|
NumberFormatValues,
|
|
|
|
SourceInfo,
|
|
|
|
} from 'react-number-format'
|
|
|
|
import { isMangoError } from 'types'
|
|
|
|
import { notify } from 'utils/notifications'
|
|
|
|
import Tooltip from '../shared/Tooltip'
|
|
|
|
import Button, { LinkButton } from '../shared/Button'
|
|
|
|
import Loading from '../shared/Loading'
|
|
|
|
import InlineNotification from '../shared/InlineNotification'
|
|
|
|
import Modal from '@components/shared/Modal'
|
|
|
|
import { ModalProps } from 'types/modal'
|
|
|
|
import Label from '@components/forms/Label'
|
2023-08-14 20:06:00 -07:00
|
|
|
import useMangoAccountAccounts, {
|
2023-08-06 21:08:35 -07:00
|
|
|
getAvaialableAccountsColor,
|
2023-08-14 20:06:00 -07:00
|
|
|
} from 'hooks/useMangoAccountAccounts'
|
2023-08-24 05:05:22 -07:00
|
|
|
import { MAX_ACCOUNTS } from 'utils/constants'
|
2023-08-06 21:08:35 -07:00
|
|
|
|
|
|
|
const MIN_ACCOUNTS = 8
|
|
|
|
|
|
|
|
const INPUT_CLASSES =
|
2023-08-07 15:26:12 -07:00
|
|
|
'h-10 rounded-md rounded-r-none border w-full border-th-input-border bg-th-input-bkg px-3 font-mono text-base text-th-fgd-1 focus:border-th-fgd-4 focus:outline-none md:hover:border-th-input-border-hover disabled:text-th-fgd-4 disabled:bg-th-bkg-2 disabled:hover:border-th-input-border'
|
2023-08-06 21:08:35 -07:00
|
|
|
|
|
|
|
type FormErrors = Partial<Record<keyof AccountSizeForm, string>>
|
|
|
|
|
|
|
|
type AccountSizeForm = {
|
|
|
|
tokenAccounts: string | undefined
|
|
|
|
spotOpenOrders: string | undefined
|
|
|
|
perpAccounts: string | undefined
|
|
|
|
perpOpenOrders: string | undefined
|
|
|
|
[key: string]: string | undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
const DEFAULT_FORM = {
|
|
|
|
tokenAccounts: '',
|
|
|
|
spotOpenOrders: '',
|
|
|
|
perpAccounts: '',
|
|
|
|
perpOpenOrders: '',
|
|
|
|
}
|
|
|
|
|
|
|
|
const MangoAccountSizeModal = ({ isOpen, onClose }: ModalProps) => {
|
|
|
|
const { t } = useTranslation(['common', 'settings'])
|
|
|
|
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
|
|
|
const [accountSizeForm, setAccountSizeForm] =
|
|
|
|
useState<AccountSizeForm>(DEFAULT_FORM)
|
|
|
|
const [formErrors, setFormErrors] = useState<FormErrors>()
|
|
|
|
const [submitting, setSubmitting] = useState(false)
|
2023-08-14 20:06:00 -07:00
|
|
|
const {
|
|
|
|
usedTokens,
|
|
|
|
usedSerum3,
|
|
|
|
usedPerps,
|
|
|
|
usedPerpOo,
|
|
|
|
totalTokens,
|
|
|
|
totalSerum3,
|
|
|
|
totalPerps,
|
|
|
|
totalPerpOpenOrders,
|
|
|
|
} = useMangoAccountAccounts()
|
2023-08-06 21:08:35 -07:00
|
|
|
|
|
|
|
useEffect(() => {
|
2023-08-10 20:06:38 -07:00
|
|
|
if (mangoAccount) {
|
2023-08-06 21:08:35 -07:00
|
|
|
setAccountSizeForm({
|
2023-08-10 20:06:38 -07:00
|
|
|
tokenAccounts: mangoAccount.tokens.length.toString(),
|
|
|
|
spotOpenOrders: mangoAccount.serum3.length.toString(),
|
|
|
|
perpAccounts: mangoAccount.perps.length.toString(),
|
|
|
|
perpOpenOrders: mangoAccount.perpOpenOrders.length.toString(),
|
2023-08-06 21:08:35 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}, [mangoAccountAddress])
|
|
|
|
|
|
|
|
const isFormValid = (form: AccountSizeForm) => {
|
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
|
|
const invalidFields: FormErrors = {}
|
|
|
|
setFormErrors({})
|
|
|
|
const { tokenAccounts, spotOpenOrders, perpAccounts, perpOpenOrders } = form
|
|
|
|
|
|
|
|
if (tokenAccounts) {
|
|
|
|
const minTokenAccountsLength = mangoAccount?.tokens.length || MIN_ACCOUNTS
|
|
|
|
if (parseInt(tokenAccounts) < minTokenAccountsLength) {
|
|
|
|
invalidFields.tokenAccounts = t('settings:error-amount', {
|
|
|
|
type: t('settings:token-accounts'),
|
|
|
|
greaterThan: mangoAccount?.tokens.length,
|
|
|
|
lessThan: '17',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (spotOpenOrders) {
|
|
|
|
const minSpotOpenOrdersLength =
|
|
|
|
mangoAccount?.serum3.length || MIN_ACCOUNTS
|
|
|
|
if (parseInt(spotOpenOrders) < minSpotOpenOrdersLength) {
|
|
|
|
invalidFields.spotOpenOrders = t('settings:error-amount', {
|
2023-10-01 16:53:01 -07:00
|
|
|
type: t('settings:spot-markets'),
|
2023-08-06 21:08:35 -07:00
|
|
|
greaterThan: mangoAccount?.serum3.length,
|
|
|
|
lessThan: '17',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (perpAccounts) {
|
|
|
|
const minPerpAccountsLength = mangoAccount?.perps.length || MIN_ACCOUNTS
|
|
|
|
if (parseInt(perpAccounts) < minPerpAccountsLength) {
|
|
|
|
invalidFields.perpAccounts = t('settings:error-amount', {
|
|
|
|
type: t('settings:perp-accounts'),
|
|
|
|
greaterThan: mangoAccount?.perps.length,
|
|
|
|
lessThan: '17',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (perpOpenOrders) {
|
|
|
|
const minPerpOpenOrdersLength =
|
|
|
|
mangoAccount?.perpOpenOrders.length || MIN_ACCOUNTS
|
|
|
|
if (parseInt(perpOpenOrders) < minPerpOpenOrdersLength) {
|
|
|
|
invalidFields.perpOpenOrders = t('settings:error-amount', {
|
|
|
|
type: t('settings:perp-open-orders'),
|
|
|
|
greaterThan: mangoAccount?.perpOpenOrders.length,
|
|
|
|
lessThan: '17',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Object.keys(invalidFields).length) {
|
|
|
|
setFormErrors(invalidFields)
|
|
|
|
}
|
|
|
|
return invalidFields
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleMax = (propertyName: keyof AccountSizeForm) => {
|
|
|
|
setFormErrors({})
|
2023-08-23 18:40:40 -07:00
|
|
|
const defaultSizes = MAX_ACCOUNTS as AccountSizeForm
|
2023-08-06 21:08:35 -07:00
|
|
|
setAccountSizeForm((prevState) => ({
|
|
|
|
...prevState,
|
2023-08-23 18:40:40 -07:00
|
|
|
[propertyName]: defaultSizes[propertyName],
|
2023-08-06 21:08:35 -07:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
// const handleMaxAll = () => {
|
|
|
|
// setFormErrors({})
|
|
|
|
// const newValues = { ...accountSizeForm }
|
|
|
|
// for (const key in newValues) {
|
|
|
|
// newValues[key] = MAX_ACCOUNTS
|
|
|
|
// }
|
|
|
|
// setAccountSizeForm(newValues)
|
|
|
|
// }
|
|
|
|
|
|
|
|
const handleSetForm = (
|
|
|
|
propertyName: keyof AccountSizeForm,
|
|
|
|
e: NumberFormatValues,
|
|
|
|
info: SourceInfo,
|
|
|
|
) => {
|
|
|
|
if (info.source !== 'event') return
|
|
|
|
setFormErrors({})
|
|
|
|
setAccountSizeForm((prevState) => ({
|
|
|
|
...prevState,
|
|
|
|
[propertyName]: e.value,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleUpdateAccountSize = useCallback(async () => {
|
|
|
|
const invalidFields = isFormValid(accountSizeForm)
|
|
|
|
if (Object.keys(invalidFields).length) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const client = mangoStore.getState().client
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
|
|
const actions = mangoStore.getState().actions
|
|
|
|
const { tokenAccounts, spotOpenOrders, perpAccounts, perpOpenOrders } =
|
|
|
|
accountSizeForm
|
|
|
|
if (
|
|
|
|
!mangoAccount ||
|
|
|
|
!group ||
|
|
|
|
!tokenAccounts ||
|
|
|
|
!spotOpenOrders ||
|
|
|
|
!perpAccounts ||
|
|
|
|
!perpOpenOrders
|
|
|
|
)
|
|
|
|
return
|
|
|
|
setSubmitting(true)
|
|
|
|
try {
|
2023-08-12 11:40:09 -07:00
|
|
|
const { signature: tx, slot } = await client.accountExpandV2(
|
2023-08-06 21:08:35 -07:00
|
|
|
group,
|
|
|
|
mangoAccount,
|
|
|
|
parseInt(tokenAccounts),
|
|
|
|
parseInt(spotOpenOrders),
|
|
|
|
parseInt(perpAccounts),
|
|
|
|
parseInt(perpOpenOrders),
|
|
|
|
mangoAccount.tokenConditionalSwaps.length,
|
|
|
|
)
|
|
|
|
notify({
|
|
|
|
title: 'Transaction confirmed',
|
|
|
|
type: 'success',
|
|
|
|
txid: tx,
|
|
|
|
})
|
2023-08-12 11:40:09 -07:00
|
|
|
await actions.reloadMangoAccount(slot)
|
2023-08-06 21:08:35 -07:00
|
|
|
setSubmitting(false)
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e)
|
|
|
|
if (!isMangoError(e)) return
|
|
|
|
notify({
|
|
|
|
title: 'Transaction failed',
|
|
|
|
description: e.message,
|
|
|
|
txid: e.txid,
|
|
|
|
type: 'error',
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
setSubmitting(false)
|
|
|
|
}
|
|
|
|
}, [accountSizeForm])
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Modal isOpen={isOpen} onClose={onClose}>
|
|
|
|
<>
|
2023-09-03 20:43:34 -07:00
|
|
|
<h2 className="mb-2 text-center">{t('settings:account-slots')}</h2>
|
2023-08-06 21:08:35 -07:00
|
|
|
{/* <LinkButton className="font-normal mb-0.5" onClick={handleMaxAll}>
|
|
|
|
{t('settings:max-all')}
|
|
|
|
</LinkButton> */}
|
|
|
|
<p className="mb-4 text-center text-xs">
|
2023-11-21 19:51:34 -08:00
|
|
|
{t('settings:increase-account-slots-desc')}
|
2023-08-06 21:08:35 -07:00
|
|
|
</p>
|
|
|
|
<div className="mb-4">
|
|
|
|
<AccountSizeFormInput
|
2023-08-14 20:06:00 -07:00
|
|
|
availableAccounts={
|
|
|
|
<span
|
|
|
|
className={getAvaialableAccountsColor(
|
|
|
|
usedTokens.length,
|
|
|
|
totalTokens.length,
|
|
|
|
)}
|
|
|
|
>{`${usedTokens.length}/${totalTokens.length}`}</span>
|
|
|
|
}
|
2023-08-10 20:06:38 -07:00
|
|
|
disabled={
|
|
|
|
mangoAccount
|
|
|
|
? mangoAccount.tokens.length >=
|
|
|
|
Number(MAX_ACCOUNTS.tokenAccounts)
|
|
|
|
: false
|
|
|
|
}
|
2023-08-06 21:08:35 -07:00
|
|
|
error={formErrors?.tokenAccounts}
|
2023-08-07 15:44:45 -07:00
|
|
|
label={t('tokens')}
|
2023-08-06 21:08:35 -07:00
|
|
|
handleMax={() => handleMax('tokenAccounts')}
|
|
|
|
handleSetForm={handleSetForm}
|
2023-08-07 15:44:45 -07:00
|
|
|
tooltipContent={t('settings:tooltip-token-accounts', {
|
|
|
|
max: MAX_ACCOUNTS.tokenAccounts,
|
|
|
|
})}
|
2023-08-06 21:08:35 -07:00
|
|
|
type="tokenAccounts"
|
|
|
|
value={accountSizeForm.tokenAccounts}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
|
|
<AccountSizeFormInput
|
2023-08-14 20:06:00 -07:00
|
|
|
availableAccounts={
|
|
|
|
<span
|
|
|
|
className={getAvaialableAccountsColor(
|
|
|
|
usedSerum3.length,
|
|
|
|
totalSerum3.length,
|
|
|
|
)}
|
|
|
|
>{`${usedSerum3.length}/${totalSerum3.length}`}</span>
|
|
|
|
}
|
2023-08-07 15:26:12 -07:00
|
|
|
disabled
|
2023-08-06 21:08:35 -07:00
|
|
|
error={formErrors?.spotOpenOrders}
|
2023-10-01 16:53:01 -07:00
|
|
|
label={t('settings:spot-markets')}
|
2023-08-06 21:08:35 -07:00
|
|
|
handleMax={() => handleMax('spotOpenOrders')}
|
|
|
|
handleSetForm={handleSetForm}
|
2023-10-01 16:53:01 -07:00
|
|
|
tooltipContent={t('settings:tooltip-spot-markets', {
|
2023-08-07 15:44:45 -07:00
|
|
|
max: MAX_ACCOUNTS.spotOpenOrders,
|
|
|
|
})}
|
2023-08-06 21:08:35 -07:00
|
|
|
type="spotOpenOrders"
|
|
|
|
value={accountSizeForm.spotOpenOrders}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
|
|
<AccountSizeFormInput
|
2023-08-14 20:06:00 -07:00
|
|
|
availableAccounts={
|
|
|
|
<span
|
|
|
|
className={getAvaialableAccountsColor(
|
|
|
|
usedPerps.length,
|
|
|
|
totalPerps.length,
|
|
|
|
)}
|
|
|
|
key="spotOpenOrders"
|
|
|
|
>{`${usedPerps.length}/${totalPerps.length}`}</span>
|
|
|
|
}
|
2023-08-07 15:26:12 -07:00
|
|
|
disabled
|
2023-08-06 21:08:35 -07:00
|
|
|
error={formErrors?.perpAccounts}
|
2023-10-01 16:53:01 -07:00
|
|
|
label={t('settings:perp-markets')}
|
2023-08-06 21:08:35 -07:00
|
|
|
handleMax={() => handleMax('perpAccounts')}
|
|
|
|
handleSetForm={handleSetForm}
|
2023-10-01 16:53:01 -07:00
|
|
|
tooltipContent={t('settings:tooltip-perp-markets', {
|
2023-08-07 15:44:45 -07:00
|
|
|
max: MAX_ACCOUNTS.perpAccounts,
|
|
|
|
})}
|
2023-08-06 21:08:35 -07:00
|
|
|
type="perpAccounts"
|
|
|
|
value={accountSizeForm.perpAccounts}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<AccountSizeFormInput
|
2023-08-14 20:06:00 -07:00
|
|
|
availableAccounts={
|
|
|
|
<span
|
|
|
|
className={getAvaialableAccountsColor(
|
|
|
|
usedPerpOo.length,
|
|
|
|
totalPerpOpenOrders.length,
|
|
|
|
)}
|
|
|
|
key="spotOpenOrders"
|
|
|
|
>{`${usedPerpOo.length}/${totalPerpOpenOrders.length}`}</span>
|
|
|
|
}
|
2023-08-10 20:06:38 -07:00
|
|
|
disabled={
|
|
|
|
mangoAccount
|
|
|
|
? mangoAccount.perpOpenOrders.length >=
|
|
|
|
Number(MAX_ACCOUNTS.perpOpenOrders)
|
|
|
|
: false
|
|
|
|
}
|
2023-08-06 21:08:35 -07:00
|
|
|
error={formErrors?.perpOpenOrders}
|
|
|
|
label={t('settings:perp-open-orders')}
|
|
|
|
handleMax={() => handleMax('perpOpenOrders')}
|
|
|
|
handleSetForm={handleSetForm}
|
2023-08-07 15:44:45 -07:00
|
|
|
tooltipContent={t('settings:tooltip-perp-open-orders', {
|
|
|
|
max: MAX_ACCOUNTS.perpOpenOrders,
|
|
|
|
})}
|
2023-08-06 21:08:35 -07:00
|
|
|
type="perpOpenOrders"
|
|
|
|
value={accountSizeForm.perpOpenOrders}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<Button
|
2023-08-17 16:47:11 -07:00
|
|
|
className="mb-4 mt-6 flex w-full items-center justify-center"
|
2023-08-06 21:08:35 -07:00
|
|
|
onClick={handleUpdateAccountSize}
|
|
|
|
size="large"
|
|
|
|
>
|
2023-09-03 20:43:34 -07:00
|
|
|
{submitting ? <Loading /> : t('settings:increase-account-slots')}
|
2023-08-06 21:08:35 -07:00
|
|
|
</Button>
|
|
|
|
<LinkButton className="mx-auto" onClick={onClose}>
|
|
|
|
{t('cancel')}
|
|
|
|
</LinkButton>
|
|
|
|
</>
|
|
|
|
</Modal>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default MangoAccountSizeModal
|
|
|
|
|
|
|
|
const AccountSizeFormInput = ({
|
|
|
|
availableAccounts,
|
2023-08-07 15:26:12 -07:00
|
|
|
disabled,
|
2023-08-06 21:08:35 -07:00
|
|
|
error,
|
|
|
|
label,
|
|
|
|
handleMax,
|
|
|
|
handleSetForm,
|
|
|
|
tooltipContent,
|
|
|
|
type,
|
|
|
|
value,
|
|
|
|
}: {
|
|
|
|
availableAccounts: ReactNode
|
2023-08-07 15:26:12 -07:00
|
|
|
disabled?: boolean
|
2023-08-06 21:08:35 -07:00
|
|
|
error: string | undefined
|
|
|
|
label: string
|
|
|
|
handleMax: (type: keyof AccountSizeForm) => void
|
|
|
|
handleSetForm: (
|
|
|
|
type: keyof AccountSizeForm,
|
|
|
|
values: NumberFormatValues,
|
|
|
|
info: SourceInfo,
|
|
|
|
) => void
|
|
|
|
tooltipContent: string
|
|
|
|
type: keyof AccountSizeForm
|
|
|
|
value: string | undefined
|
|
|
|
}) => {
|
|
|
|
const { t } = useTranslation(['common', 'settings'])
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
<div className="flex items-center">
|
|
|
|
<Tooltip content={tooltipContent}>
|
2023-08-17 16:47:11 -07:00
|
|
|
<Label className="tooltip-underline mr-1" text={label} />
|
2023-08-06 21:08:35 -07:00
|
|
|
</Tooltip>
|
|
|
|
</div>
|
2023-08-07 15:26:12 -07:00
|
|
|
{!disabled ? (
|
|
|
|
<LinkButton
|
|
|
|
className="mb-2 font-normal"
|
|
|
|
onClick={() => handleMax('tokenAccounts')}
|
|
|
|
>
|
|
|
|
{t('max')}
|
|
|
|
</LinkButton>
|
|
|
|
) : null}
|
2023-08-06 21:08:35 -07:00
|
|
|
</div>
|
2023-08-07 15:26:12 -07:00
|
|
|
<div className="flex items-center">
|
2023-08-06 21:08:35 -07:00
|
|
|
<NumberFormat
|
2023-08-07 15:26:12 -07:00
|
|
|
name={type as string}
|
|
|
|
id={type as string}
|
2023-08-06 21:08:35 -07:00
|
|
|
inputMode="numeric"
|
|
|
|
thousandSeparator=","
|
|
|
|
allowNegative={false}
|
|
|
|
isNumericString={true}
|
|
|
|
className={INPUT_CLASSES}
|
|
|
|
value={value}
|
|
|
|
onValueChange={(e, sourceInfo) => handleSetForm(type, e, sourceInfo)}
|
2023-08-07 15:26:12 -07:00
|
|
|
disabled={disabled}
|
2023-08-06 21:08:35 -07:00
|
|
|
/>
|
2023-08-07 15:26:12 -07:00
|
|
|
<div
|
2023-08-17 16:47:11 -07:00
|
|
|
className={`flex h-10 items-center rounded-r-md border border-l-0 border-th-input-border px-2 ${
|
2023-08-07 15:26:12 -07:00
|
|
|
disabled ? 'bg-th-bkg-2' : 'bg-th-input-bkg'
|
|
|
|
}`}
|
|
|
|
>
|
2023-08-06 21:08:35 -07:00
|
|
|
<p className="font-mono text-xs">{availableAccounts}</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{error ? (
|
|
|
|
<div className="mt-1">
|
|
|
|
<InlineNotification
|
|
|
|
type="error"
|
|
|
|
desc={error}
|
|
|
|
hideBorder
|
|
|
|
hidePadding
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|