diff --git a/components/account/CreateAccountAdvancedOptions.tsx b/components/account/CreateAccountAdvancedOptions.tsx new file mode 100644 index 00000000..b505fae4 --- /dev/null +++ b/components/account/CreateAccountAdvancedOptions.tsx @@ -0,0 +1,151 @@ +import Slider from '@components/forms/Slider' +import Button, { IconButton, LinkButton } from '@components/shared/Button' +import { ArrowLeftIcon } from '@heroicons/react/20/solid' +import { useTranslation } from 'react-i18next' +import { MAX_ACCOUNTS } from 'utils/constants' + +export type ACCOUNT_SLOTS = { + tokenSlots: number + serumSlots: number + perpSlots: number +} + +export const DEFAULT_SLOTS: ACCOUNT_SLOTS = { + tokenSlots: parseInt(MAX_ACCOUNTS.tokenAccounts), + serumSlots: parseInt(MAX_ACCOUNTS.spotOpenOrders), + perpSlots: parseInt(MAX_ACCOUNTS.perpAccounts), +} + +const CreateAccountAdvancedOptions = ({ + slots, + setSlots, + onClose, +}: { + slots: ACCOUNT_SLOTS + setSlots: (slots: React.SetStateAction | ACCOUNT_SLOTS) => void + onClose?: () => void +}) => { + const { t } = useTranslation(['common', 'settings']) + + const handleResetDefaults = () => { + setSlots(DEFAULT_SLOTS) + } + + const calculateMaxValues = () => { + const maxTotalSlots = 28 + + const maxTokenSlots = Math.floor( + (maxTotalSlots - slots.serumSlots - slots.perpSlots * 2) / 2, + ) + const maxSerumSlots = + maxTotalSlots - slots.tokenSlots * 2 - slots.perpSlots * 2 + const maxPerpSlots = Math.floor( + (maxTotalSlots - slots.tokenSlots * 2 - slots.serumSlots) / 2, + ) + + return { + maxTokenSlots, + maxSerumSlots, + maxPerpSlots, + } + } + + const handleSliderChange = (property: keyof ACCOUNT_SLOTS, value: string) => { + setSlots((prevSlots: ACCOUNT_SLOTS) => ({ + ...prevSlots, + [property]: parseInt(value), + })) + + const { maxTokenSlots, maxSerumSlots, maxPerpSlots } = calculateMaxValues() + + // Ensure each property stays within its maximum value + if (property === 'tokenSlots') { + setSlots((prevSlots: ACCOUNT_SLOTS) => ({ + ...prevSlots, + serumSlots: Math.min(prevSlots.serumSlots, maxSerumSlots), + perpSlots: Math.min(prevSlots.perpSlots, maxPerpSlots), + })) + } else if (property === 'serumSlots') { + setSlots((prevSlots: ACCOUNT_SLOTS) => ({ + ...prevSlots, + tokenSlots: Math.min(prevSlots.tokenSlots, maxTokenSlots), + perpSlots: Math.min(prevSlots.perpSlots, maxPerpSlots), + })) + } else if (property === 'perpSlots') { + setSlots((prevSlots: ACCOUNT_SLOTS) => ({ + ...prevSlots, + tokenSlots: Math.min(prevSlots.tokenSlots, maxTokenSlots), + serumSlots: Math.min(prevSlots.serumSlots, maxSerumSlots), + })) + } + } + + const { tokenSlots, serumSlots, perpSlots } = slots + + return ( +
+
+ {onClose ? ( + + + + ) : null} +

{t('account:advanced-options')}

+ {onClose ?
: null} +
+

{t('account:advanced-options-desc')}

+
+
+

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

+ + {t('account:reset-defaults')} + +
+
+
+

{t('tokens')}

+

{tokenSlots}

+
+ handleSliderChange('tokenSlots', value)} + step={1} + /> +
+
+
+

{t('spot-markets')}

+

{serumSlots}

+
+ handleSliderChange('serumSlots', value)} + step={1} + /> +
+
+
+

{t('perp-markets')}

+

{perpSlots}

+
+ handleSliderChange('perpSlots', value)} + step={1} + /> +
+
+ +
+ ) +} + +export default CreateAccountAdvancedOptions diff --git a/components/account/CreateAccountForm.tsx b/components/account/CreateAccountForm.tsx index 724b9e3f..d695f943 100644 --- a/components/account/CreateAccountForm.tsx +++ b/components/account/CreateAccountForm.tsx @@ -1,8 +1,8 @@ -import { ChangeEvent, useState } from 'react' +import { ChangeEvent, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'next-i18next' import mangoStore from '@store/mangoStore' import { createSolanaMessage, notify } from '../../utils/notifications' -import Button, { IconButton } from '../shared/Button' +import Button, { IconButton, LinkButton } from '../shared/Button' import BounceLoader from '../shared/BounceLoader' import Input from '../forms/Input' import Label from '../forms/Label' @@ -18,6 +18,11 @@ import NotificationCookieStore from '@store/notificationCookieStore' import { usePlausible } from 'next-plausible' import { TelemetryEvents } from 'utils/telemetry' import { waitForSlot } from 'utils/network' +import CreateAccountAdvancedOptions, { + ACCOUNT_SLOTS, + DEFAULT_SLOTS, +} from './CreateAccountAdvancedOptions' +import { EnterBottomExitBottom } from '@components/shared/Transitions' const getNextAccountNumber = (accounts: MangoAccount[]): number => { if (accounts.length > 1) { @@ -43,13 +48,22 @@ const CreateAccountForm = ({ const [loading, setLoading] = useState(false) const [name, setName] = useState('') const [signToNotifications, setSignToNotifications] = useState(true) + const [showAdvancedOptionsAccountForm, setShowAdvancedOptionsAccountForm] = + useState(false) + const [slots, setSlots] = useState(DEFAULT_SLOTS) //whole context needed to sign msgs const walletContext = useWallet() const { maxSolDeposit } = useSolBalance() const telemetry = usePlausible() const setCookie = NotificationCookieStore((s) => s.setCookie) - const handleNewAccount = async () => { + const hasSetCustomOptions = useMemo(() => { + return !!Object.entries(slots).find( + (slot) => slot[1] !== DEFAULT_SLOTS[slot[0] as keyof ACCOUNT_SLOTS], + ) + }, [slots]) + + const handleNewAccount = useCallback(async () => { const client = mangoStore.getState().client const group = mangoStore.getState().group const existingMangoAccts = mangoStore.getState().mangoAccounts @@ -58,16 +72,19 @@ const CreateAccountForm = ({ if (!group || !walletContext.wallet) return setLoading(true) + const perpOpenOrdersSlots = slots.perpSlots + ? parseInt(MAX_ACCOUNTS.perpOpenOrders) + : 0 try { const newAccountNum = getNextAccountNumber(existingMangoAccts) const { signature: tx, slot } = await client.createMangoAccount( group, newAccountNum, name || `Account ${newAccountNum + 1}`, - parseInt(MAX_ACCOUNTS.tokenAccounts), // tokens - parseInt(MAX_ACCOUNTS.spotOpenOrders), // serum3 - parseInt(MAX_ACCOUNTS.perpAccounts), // perps - parseInt(MAX_ACCOUNTS.perpOpenOrders), // perp Oo + slots.tokenSlots, // tokens + slots.serumSlots, // serum3 + slots.perpSlots, // perps + perpOpenOrdersSlots, // perp Oo ) if (tx) { if (signToNotifications) { @@ -83,11 +100,16 @@ const CreateAccountForm = ({ const newAccount = mangoAccounts.find( (acc) => acc.accountNum === newAccountNum, ) + const filteredMangoAccounts = reloadedMangoAccounts?.length + ? reloadedMangoAccounts.filter( + (acc) => !acc.name.includes('Leverage Stake'), + ) + : [] if (newAccount) { set((s) => { s.mangoAccount.current = newAccount s.mangoAccount.lastSlot = slot - s.mangoAccounts = reloadedMangoAccounts + s.mangoAccounts = filteredMangoAccounts }) } telemetry('accountCreate', { @@ -116,14 +138,14 @@ const CreateAccountForm = ({ type: 'error', }) } - } + }, [signToNotifications, slots]) return loading ? (
) : ( -
+
{handleBack ? ( @@ -163,6 +185,17 @@ const CreateAccountForm = ({ {maxSolDeposit <= 0 ? ( ) : null} + setShowAdvancedOptionsAccountForm(true)} + > + {t('account:advanced-options')} + + {hasSetCustomOptions ? ( +

+ {t('account:custom-account-options-saved')} +

+ ) : null}
+ + + setShowAdvancedOptionsAccountForm(false)} + /> +
) } diff --git a/components/forms/Slider.tsx b/components/forms/Slider.tsx index 2b052341..51c5a766 100644 --- a/components/forms/Slider.tsx +++ b/components/forms/Slider.tsx @@ -27,7 +27,7 @@ const Slider = ({ ? '0% 100%' : ((value - min) * 100) / (max - min) + '% 100%' } - }, [value]) + }, [value, max]) useEffect(() => { if (amount) { diff --git a/components/modals/MangoAccountsListModal.tsx b/components/modals/MangoAccountsListModal.tsx index c67a15b3..0c4b63ff 100644 --- a/components/modals/MangoAccountsListModal.tsx +++ b/components/modals/MangoAccountsListModal.tsx @@ -103,13 +103,13 @@ const MangoAccountsListModal = ({ return (
-
+

{t('accounts')}

{loading ? ( ) : mangoAccounts.length ? ( -
+
{sortedMangoAccounts.map((acc) => { if ( mangoAccount && diff --git a/pages/404.tsx b/pages/404.tsx index b5d89631..1b71f860 100644 --- a/pages/404.tsx +++ b/pages/404.tsx @@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'notifications', diff --git a/pages/borrow.tsx b/pages/borrow.tsx index 1cc09e7d..53054937 100644 --- a/pages/borrow.tsx +++ b/pages/borrow.tsx @@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'borrow', 'close-account', 'common', diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index 1e20ca21..bdc3439d 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -40,6 +40,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'notifications', diff --git a/pages/dashboard/mangoaccount.tsx b/pages/dashboard/mangoaccount.tsx index 7efd716d..4f7e08ab 100644 --- a/pages/dashboard/mangoaccount.tsx +++ b/pages/dashboard/mangoaccount.tsx @@ -20,6 +20,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'notifications', diff --git a/pages/dashboard/risks.tsx b/pages/dashboard/risks.tsx index 83a530a2..d0223b0e 100644 --- a/pages/dashboard/risks.tsx +++ b/pages/dashboard/risks.tsx @@ -18,6 +18,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'notifications', diff --git a/pages/dashboard/slippage.tsx b/pages/dashboard/slippage.tsx index 5638b413..bc811089 100644 --- a/pages/dashboard/slippage.tsx +++ b/pages/dashboard/slippage.tsx @@ -14,6 +14,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'notifications', diff --git a/pages/governance/list.tsx b/pages/governance/list.tsx index 59225612..aec69c19 100644 --- a/pages/governance/list.tsx +++ b/pages/governance/list.tsx @@ -10,6 +10,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'governance', diff --git a/pages/governance/vote.tsx b/pages/governance/vote.tsx index 9c1ea599..4a2fd514 100644 --- a/pages/governance/vote.tsx +++ b/pages/governance/vote.tsx @@ -8,6 +8,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'governance', diff --git a/pages/nft/index.tsx b/pages/nft/index.tsx index be0b7909..dbd54536 100644 --- a/pages/nft/index.tsx +++ b/pages/nft/index.tsx @@ -20,6 +20,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'nft-market', diff --git a/pages/rewards.tsx b/pages/rewards.tsx index 6d5ab079..004d0043 100644 --- a/pages/rewards.tsx +++ b/pages/rewards.tsx @@ -8,6 +8,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'governance', diff --git a/pages/search.tsx b/pages/search.tsx index 5bf4ae19..d02c0c98 100644 --- a/pages/search.tsx +++ b/pages/search.tsx @@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'close-account', 'common', 'notifications', diff --git a/pages/stats.tsx b/pages/stats.tsx index bf81d836..37c2aae1 100644 --- a/pages/stats.tsx +++ b/pages/stats.tsx @@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) { return { props: { ...(await serverSideTranslations(locale, [ + 'account', 'activity', 'close-account', 'common', diff --git a/public/locales/en/account.json b/public/locales/en/account.json index 32ad123f..1c362a5c 100644 --- a/public/locales/en/account.json +++ b/public/locales/en/account.json @@ -1,9 +1,12 @@ { "account-is-private": "Account enabled private mode", "account-stats": "Account Stats", + "advanced-options": "Advanced Options", + "advanced-options-desc": "Mango Accounts have limits on the number of tokens and markets an account can hold at one time. This is due to how accounts on Solana work. Use the sliders to customize the slots for your new account.", "assets": "Assets", "assets-liabilities": "Assets & Liabilities", "collateral-value": "Collateral Value", + "custom-account-options-saved": "Advanced options set", "cumulative-interest-chart": "Cumulative Interest Chart", "daily-volume": "24h Volume", "export": "Export {{dataType}}", @@ -28,6 +31,7 @@ "pnl-chart": "PnL Chart", "pnl-history": "PnL History", "refresh-balance": "Refresh Balance", + "reset-defaults": "Reset Defaults", "slots-open-account": "Open a new account to increase the number of tokens you can hold on Mango.", "slots-settings-path": "Account Settings > Account Slots", "token-slots-full": "You're out of token slots", diff --git a/public/locales/es/account.json b/public/locales/es/account.json index 32ad123f..1c362a5c 100644 --- a/public/locales/es/account.json +++ b/public/locales/es/account.json @@ -1,9 +1,12 @@ { "account-is-private": "Account enabled private mode", "account-stats": "Account Stats", + "advanced-options": "Advanced Options", + "advanced-options-desc": "Mango Accounts have limits on the number of tokens and markets an account can hold at one time. This is due to how accounts on Solana work. Use the sliders to customize the slots for your new account.", "assets": "Assets", "assets-liabilities": "Assets & Liabilities", "collateral-value": "Collateral Value", + "custom-account-options-saved": "Advanced options set", "cumulative-interest-chart": "Cumulative Interest Chart", "daily-volume": "24h Volume", "export": "Export {{dataType}}", @@ -28,6 +31,7 @@ "pnl-chart": "PnL Chart", "pnl-history": "PnL History", "refresh-balance": "Refresh Balance", + "reset-defaults": "Reset Defaults", "slots-open-account": "Open a new account to increase the number of tokens you can hold on Mango.", "slots-settings-path": "Account Settings > Account Slots", "token-slots-full": "You're out of token slots", diff --git a/public/locales/ru/account.json b/public/locales/ru/account.json index 32ad123f..1c362a5c 100644 --- a/public/locales/ru/account.json +++ b/public/locales/ru/account.json @@ -1,9 +1,12 @@ { "account-is-private": "Account enabled private mode", "account-stats": "Account Stats", + "advanced-options": "Advanced Options", + "advanced-options-desc": "Mango Accounts have limits on the number of tokens and markets an account can hold at one time. This is due to how accounts on Solana work. Use the sliders to customize the slots for your new account.", "assets": "Assets", "assets-liabilities": "Assets & Liabilities", "collateral-value": "Collateral Value", + "custom-account-options-saved": "Advanced options set", "cumulative-interest-chart": "Cumulative Interest Chart", "daily-volume": "24h Volume", "export": "Export {{dataType}}", @@ -28,6 +31,7 @@ "pnl-chart": "PnL Chart", "pnl-history": "PnL History", "refresh-balance": "Refresh Balance", + "reset-defaults": "Reset Defaults", "slots-open-account": "Open a new account to increase the number of tokens you can hold on Mango.", "slots-settings-path": "Account Settings > Account Slots", "token-slots-full": "You're out of token slots", diff --git a/public/locales/zh/account.json b/public/locales/zh/account.json index fbd8ebee..f2214c45 100644 --- a/public/locales/zh/account.json +++ b/public/locales/zh/account.json @@ -1,9 +1,12 @@ { "account-is-private": "此帐户已开启隐私模式", "account-stats": "帐户统计", + "advanced-options": "Advanced Options", + "advanced-options-desc": "Mango Accounts have limits on the number of tokens and markets an account can hold at one time. This is due to how accounts on Solana work. Use the sliders to customize the slots for your new account.", "assets": "资产", "assets-liabilities": "资产和债务", "collateral-value": "质押品价值", + "custom-account-options-saved": "Advanced options set", "cumulative-interest-chart": "累积利息图表", "daily-volume": "24小时交易量", "export": "导出{{dataType}}", @@ -28,6 +31,7 @@ "pnl-chart": "盈亏图表", "pnl-history": "盈亏历史", "refresh-balance": "更新余额", + "reset-defaults": "Reset Defaults", "slots-open-account": "Open a new account to increase the number of tokens you can hold on Mango.", "slots-settings-path": "Account Settings > Account Slots", "token-slots-full": "You're out of token slots", diff --git a/public/locales/zh_tw/account.json b/public/locales/zh_tw/account.json index f6bde9c2..b6b8b264 100644 --- a/public/locales/zh_tw/account.json +++ b/public/locales/zh_tw/account.json @@ -1,9 +1,12 @@ { "account-is-private": "此帳戶已開啟隱私模式", "account-stats": "帳戶統計", + "advanced-options": "Advanced Options", + "advanced-options-desc": "Mango Accounts have limits on the number of tokens and markets an account can hold at one time. This is due to how accounts on Solana work. Use the sliders to customize the slots for your new account.", "assets": "資產", "assets-liabilities": "資產和債務", "collateral-value": "質押品價值", + "custom-account-options-saved": "Advanced options set", "cumulative-interest-chart": "累積利息圖表", "daily-volume": "24小時交易量", "export": "導出{{dataType}}", @@ -28,6 +31,7 @@ "pnl-chart": "盈虧圖表", "pnl-history": "盈虧歷史", "refresh-balance": "更新餘額", + "reset-defaults": "Reset Defaults", "slots-open-account": "Open a new account to increase the number of tokens you can hold on Mango.", "slots-settings-path": "Account Settings > Account Slots", "token-slots-full": "You're out of token slots",