add customizable slots for new accounts

This commit is contained in:
saml33 2023-12-07 14:54:53 +11:00
parent 0566345311
commit 30427a2c66
21 changed files with 240 additions and 13 deletions

View File

@ -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> | 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 (
<div>
<div className="mb-2 flex items-center">
{onClose ? (
<IconButton className="mr-3" onClick={onClose} size="small">
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
) : null}
<h2 className="w-full text-center">{t('account:advanced-options')}</h2>
{onClose ? <div className="h-5 w-5" /> : null}
</div>
<p>{t('account:advanced-options-desc')}</p>
<div className="mt-4 rounded-md border border-th-bkg-3 p-4">
<div className="mb-2 flex items-center justify-between">
<h3 className="text-sm">{t('settings:account-slots')}</h3>
<LinkButton onClick={handleResetDefaults}>
{t('account:reset-defaults')}
</LinkButton>
</div>
<div className="mb-4">
<div className="mb-2 flex items-center justify-between">
<p>{t('tokens')}</p>
<p className="font-mono text-th-fgd-1">{tokenSlots}</p>
</div>
<Slider
amount={tokenSlots}
max={calculateMaxValues().maxTokenSlots.toString()}
min="0"
onChange={(value) => handleSliderChange('tokenSlots', value)}
step={1}
/>
</div>
<div className="mb-4">
<div className="mb-2 flex items-center justify-between">
<p>{t('spot-markets')}</p>
<p className="font-mono text-th-fgd-1">{serumSlots}</p>
</div>
<Slider
amount={serumSlots}
max={calculateMaxValues().maxSerumSlots.toString()}
min="0"
onChange={(value) => handleSliderChange('serumSlots', value)}
step={1}
/>
</div>
<div className="mb-4">
<div className="mb-2 flex items-center justify-between">
<p>{t('perp-markets')}</p>
<p className="font-mono text-th-fgd-1">{perpSlots}</p>
</div>
<Slider
amount={perpSlots}
max={calculateMaxValues().maxPerpSlots.toString()}
min="0"
onChange={(value) => handleSliderChange('perpSlots', value)}
step={1}
/>
</div>
</div>
<Button className="mt-6 w-full" size="large" onClick={onClose}>
{t('save')}
</Button>
</div>
)
}
export default CreateAccountAdvancedOptions

View File

@ -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<ACCOUNT_SLOTS>(DEFAULT_SLOTS)
//whole context needed to sign msgs
const walletContext = useWallet()
const { maxSolDeposit } = useSolBalance()
const telemetry = usePlausible<TelemetryEvents>()
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 ? (
<div className="flex h-full flex-1 flex-col items-center justify-center">
<BounceLoader loadingMessage={t('creating-account')} />
</div>
) : (
<div className="flex h-full flex-col justify-between">
<div className="relative flex h-full min-h-[462px] flex-col justify-between overflow-hidden">
<div className="pb-3">
<div className="flex items-center">
{handleBack ? (
@ -163,6 +185,17 @@ const CreateAccountForm = ({
{maxSolDeposit <= 0 ? (
<InlineNotification type="error" desc={t('deposit-more-sol')} />
) : null}
<LinkButton
className="mx-auto mt-4"
onClick={() => setShowAdvancedOptionsAccountForm(true)}
>
{t('account:advanced-options')}
</LinkButton>
{hasSetCustomOptions ? (
<p className="mt-1 text-center text-th-success">
{t('account:custom-account-options-saved')}
</p>
) : null}
</div>
<Button
className="mt-6 w-full"
@ -172,6 +205,17 @@ const CreateAccountForm = ({
>
{t('create-account')}
</Button>
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full bg-th-bkg-1"
show={showAdvancedOptionsAccountForm}
>
<CreateAccountAdvancedOptions
slots={slots}
setSlots={setSlots}
onClose={() => setShowAdvancedOptionsAccountForm(false)}
/>
</EnterBottomExitBottom>
</div>
)
}

View File

@ -27,7 +27,7 @@ const Slider = ({
? '0% 100%'
: ((value - min) * 100) / (max - min) + '% 100%'
}
}, [value])
}, [value, max])
useEffect(() => {
if (amount) {

View File

@ -103,13 +103,13 @@ const MangoAccountsListModal = ({
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="inline-block w-full transform overflow-x-hidden">
<div className="flex min-h-[400px] flex-col justify-between">
<div className="flex min-h-[462px] flex-col justify-between">
<div>
<h2 className="text-center">{t('accounts')}</h2>
{loading ? (
<Loading />
) : mangoAccounts.length ? (
<div className="thin-scroll mt-4 max-h-[320px] space-y-2 overflow-y-auto">
<div className="thin-scroll mt-4 max-h-[374px] space-y-2 overflow-y-auto">
{sortedMangoAccounts.map((acc) => {
if (
mangoAccount &&

View File

@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'notifications',

View File

@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'borrow',
'close-account',
'common',

View File

@ -40,6 +40,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'notifications',

View File

@ -20,6 +20,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'notifications',

View File

@ -18,6 +18,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'notifications',

View File

@ -14,6 +14,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'notifications',

View File

@ -10,6 +10,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'governance',

View File

@ -8,6 +8,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'governance',

View File

@ -20,6 +20,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'nft-market',

View File

@ -8,6 +8,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'governance',

View File

@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'notifications',

View File

@ -6,6 +6,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'activity',
'close-account',
'common',

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",