import mangoStore from '@store/mangoStore' import useMangoAccount from 'hooks/useMangoAccount' import { ReactNode, useCallback, useEffect, useState } from 'react' 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' import useMangoAccountAccounts, { getAvaialableAccountsColor, } from 'hooks/useMangoAccountAccounts' import { MAX_ACCOUNTS } from 'utils/constants' const MIN_ACCOUNTS = 8 const INPUT_CLASSES = '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' type FormErrors = Partial> 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(DEFAULT_FORM) const [formErrors, setFormErrors] = useState() const [submitting, setSubmitting] = useState(false) const { usedTokens, usedSerum3, usedPerps, usedPerpOo, totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders, } = useMangoAccountAccounts() useEffect(() => { if (mangoAccount) { setAccountSizeForm({ tokenAccounts: mangoAccount.tokens.length.toString(), spotOpenOrders: mangoAccount.serum3.length.toString(), perpAccounts: mangoAccount.perps.length.toString(), perpOpenOrders: mangoAccount.perpOpenOrders.length.toString(), }) } }, [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', { type: t('settings:spot-markets'), 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({}) const defaultSizes = MAX_ACCOUNTS as AccountSizeForm setAccountSizeForm((prevState) => ({ ...prevState, [propertyName]: defaultSizes[propertyName], })) } // 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 { const { signature: tx, slot } = await client.accountExpandV2( group, mangoAccount, parseInt(tokenAccounts), parseInt(spotOpenOrders), parseInt(perpAccounts), parseInt(perpOpenOrders), mangoAccount.tokenConditionalSwaps.length, ) notify({ title: 'Transaction confirmed', type: 'success', txid: tx, }) await actions.reloadMangoAccount(slot) 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 ( <>

{t('settings:account-slots')}

{/* {t('settings:max-all')} */}

{t('settings:increase-account-slots-desc')}

{`${usedTokens.length}/${totalTokens.length}`} } disabled={ mangoAccount ? mangoAccount.tokens.length >= Number(MAX_ACCOUNTS.tokenAccounts) : false } error={formErrors?.tokenAccounts} label={t('tokens')} handleMax={() => handleMax('tokenAccounts')} handleSetForm={handleSetForm} tooltipContent={t('settings:tooltip-token-accounts', { max: MAX_ACCOUNTS.tokenAccounts, })} type="tokenAccounts" value={accountSizeForm.tokenAccounts} />
{`${usedSerum3.length}/${totalSerum3.length}`} } disabled error={formErrors?.spotOpenOrders} label={t('settings:spot-markets')} handleMax={() => handleMax('spotOpenOrders')} handleSetForm={handleSetForm} tooltipContent={t('settings:tooltip-spot-markets', { max: MAX_ACCOUNTS.spotOpenOrders, })} type="spotOpenOrders" value={accountSizeForm.spotOpenOrders} />
{`${usedPerps.length}/${totalPerps.length}`} } disabled error={formErrors?.perpAccounts} label={t('settings:perp-markets')} handleMax={() => handleMax('perpAccounts')} handleSetForm={handleSetForm} tooltipContent={t('settings:tooltip-perp-markets', { max: MAX_ACCOUNTS.perpAccounts, })} type="perpAccounts" value={accountSizeForm.perpAccounts} />
{`${usedPerpOo.length}/${totalPerpOpenOrders.length}`} } disabled={ mangoAccount ? mangoAccount.perpOpenOrders.length >= Number(MAX_ACCOUNTS.perpOpenOrders) : false } error={formErrors?.perpOpenOrders} label={t('settings:perp-open-orders')} handleMax={() => handleMax('perpOpenOrders')} handleSetForm={handleSetForm} tooltipContent={t('settings:tooltip-perp-open-orders', { max: MAX_ACCOUNTS.perpOpenOrders, })} type="perpOpenOrders" value={accountSizeForm.perpOpenOrders} />
{t('cancel')}
) } export default MangoAccountSizeModal const AccountSizeFormInput = ({ availableAccounts, disabled, error, label, handleMax, handleSetForm, tooltipContent, type, value, }: { availableAccounts: ReactNode disabled?: boolean 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 ( <>
{!disabled ? ( handleMax('tokenAccounts')} > {t('max')} ) : null}
handleSetForm(type, e, sourceInfo)} disabled={disabled} />

{availableAccounts}

{error ? (
) : null} ) }