combine deposit and withdraw to single modal

This commit is contained in:
saml33 2022-12-17 23:37:00 +11:00
parent 945d38aefc
commit 13cdf408ef
19 changed files with 445 additions and 446 deletions

View File

@ -12,20 +12,18 @@ import Image from 'next/legacy/image'
import React, { useCallback, useMemo, useState } from 'react'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import mangoStore from '@store/mangoStore'
import { ModalProps } from '../../types/modal'
import { ALPHA_DEPOSIT_LIMIT, INPUT_TOKEN_DEFAULT } from '../../utils/constants'
import { notify } from '../../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
import { TokenAccount } from '../../utils/tokens'
import ActionTokenList from '../account/ActionTokenList'
import ButtonGroup from '../forms/ButtonGroup'
import Label from '../forms/Label'
import Button from '../shared/Button'
import InlineNotification from '../shared/InlineNotification'
import Loading from '../shared/Loading'
import Modal from '../shared/Modal'
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
import { withValueLimit } from '../swap/SwapForm'
import { ALPHA_DEPOSIT_LIMIT, INPUT_TOKEN_DEFAULT } from './../utils/constants'
import { notify } from './../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
import { TokenAccount } from './../utils/tokens'
import ActionTokenList from './account/ActionTokenList'
import ButtonGroup from './forms/ButtonGroup'
import Label from './forms/Label'
import Button from './shared/Button'
import InlineNotification from './shared/InlineNotification'
import Loading from './shared/Loading'
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
import { withValueLimit } from './swap/SwapForm'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import Tooltip from '@components/shared/Tooltip'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
@ -33,12 +31,11 @@ import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
interface DepositModalProps {
interface DepositFormProps {
onSuccess: () => void
token?: string
}
type ModalCombinedProps = DepositModalProps & ModalProps
export const walletBalanceForToken = (
walletTokens: TokenAccount[],
token: string
@ -60,7 +57,7 @@ export const walletBalanceForToken = (
}
}
function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
function DepositForm({ onSuccess, token }: DepositFormProps) {
const { t } = useTranslation('common')
const { group } = useMangoGroup()
const [inputAmount, setInputAmount] = useState('')
@ -124,8 +121,8 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
if (!mangoAccount || !group || !bank) return
setSubmitting(true)
try {
setSubmitting(true)
const tx = await client.tokenDeposit(
group,
mangoAccount,
@ -140,7 +137,6 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
await actions.reloadMangoAccount()
actions.fetchWalletTokens(wallet!.adapter as unknown as Wallet)
setSubmitting(false)
} catch (e: any) {
notify({
title: 'Transaction failed',
@ -149,10 +145,11 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
type: 'error',
})
console.error('Error depositing:', e)
} finally {
setSubmitting(false)
onSuccess()
}
onClose()
}, [bank, wallet, inputAmount, onClose])
}, [bank, wallet, inputAmount])
// TODO extract into a shared hook for UserSetup.tsx
const banks = useMemo(() => {
@ -194,12 +191,14 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
const showInsufficientBalance = tokenMax.maxAmount < Number(inputAmount)
return (
<Modal isOpen={isOpen} onClose={onClose}>
<>
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
show={showTokenList}
>
<h2 className="mb-4 text-center">{t('select-token')}</h2>
<h2 className="mb-4 text-center text-lg">
{t('select-deposit-token')}
</h2>
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
<div className="text-left">
<p className="text-xs">{t('token')}</p>
@ -221,13 +220,12 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
</EnterBottomExitBottom>
<FadeInFadeOut
className="flex h-full flex-col justify-between"
show={isOpen}
show={!showTokenList}
>
<div>
<h2 className="mb-2 text-center">{t('deposit')}</h2>
<InlineNotification
type="info"
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} deposit limit during alpha
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} account value limit during alpha
testing.`}
/>
<SolBalanceWarnings
@ -237,7 +235,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
/>
<div className="mt-4 grid grid-cols-2">
<div className="col-span-2 flex justify-between">
<Label text={t('token')} />
<Label text={`${t('deposit')} ${t('token')}`} />
<MaxAmountButton
className="mb-2"
label={t('wallet-balance')}
@ -312,14 +310,14 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
: '-'}
</p>
</div>
<div className="flex justify-between">
{/* <div className="flex justify-between">
<div className="flex items-center">
<Tooltip content={t('asset-weight-desc')}>
<p className="tooltip-underline">{t('asset-weight')}</p>
</Tooltip>
</div>
<p className="font-mono">{bank!.initAssetWeight.toFixed(2)}x</p>
</div>
</div> */}
<div className="flex justify-between">
<Tooltip content={t('tooltip-collateral-value')}>
<p className="tooltip-underline">{t('collateral-value')}</p>
@ -360,8 +358,8 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
</Button>
</div>
</FadeInFadeOut>
</Modal>
</>
)
}
export default DepositModal
export default DepositForm

View File

@ -167,7 +167,7 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
alignBottom
hideIconBg
>
<div className="px-4 pb-4 pt-2">
<div className="px-4 py-2">
<MangoAccountSummary />
</div>
</ExpandableMenuItem>

View File

@ -21,8 +21,6 @@ import {
import { breakpoints } from '../utils/theme'
import Switch from './forms/Switch'
import BorrowModal from './modals/BorrowModal'
import DepositModal from './modals/DepositModal'
import WithdrawModal from './modals/WithdrawModal'
import { IconButton, LinkButton } from './shared/Button'
import ContentBox from './shared/ContentBox'
import IconDropMenu from './shared/IconDropMenu'
@ -33,6 +31,7 @@ import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from '../hooks/useJupiterMints'
import { Table, Td, Th, TrBody, TrHead } from './shared/TableElements'
import useMangoGroup from 'hooks/useMangoGroup'
import DepositWithdrawModal from './modals/DepositWithdrawModal'
const TokenList = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
@ -554,14 +553,16 @@ const ActionsMenu = ({
</LinkButton> */}
</IconDropMenu>
{showDepositModal ? (
<DepositModal
<DepositWithdrawModal
action="deposit"
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
token={selectedToken}
/>
) : null}
{showWithdrawModal ? (
<WithdrawModal
<DepositWithdrawModal
action="withdraw"
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
token={selectedToken}

View File

@ -8,7 +8,7 @@ import {
import { useWallet } from '@solana/wallet-adapter-react'
import { useTranslation } from 'next-i18next'
import WalletIcon from './icons/WalletIcon'
import { IconButton } from './shared/Button'
import Button, { IconButton } from './shared/Button'
import ConnectedMenu from './wallet/ConnectedMenu'
import { ConnectWalletButton } from './wallet/ConnectWalletButton'
import { IS_ONBOARDED_KEY } from '../utils/constants'
@ -23,7 +23,8 @@ import useOnlineStatus from 'hooks/useOnlineStatus'
import { DEFAULT_DELEGATE } from './modals/DelegateModal'
import Tooltip from './shared/Tooltip'
import { abbreviateAddress } from 'utils/formatting'
import ThemeSwitcher from './ThemeSwitcher'
import DepositWithdrawModal from './modals/DepositWithdrawModal'
// import ThemeSwitcher from './ThemeSwitcher'
const TopBar = () => {
const { t } = useTranslation('common')
@ -33,6 +34,8 @@ const TopBar = () => {
const [showUserSetup, setShowUserSetup] = useState(false)
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
const [showMangoAccountsModal, setShowMangoAccountsModal] = useState(false)
const [showDepositWithdrawModal, setShowDepositWithdrawModal] =
useState(false)
const isOnline = useOnlineStatus()
const router = useRouter()
const { query } = router
@ -92,11 +95,16 @@ const TopBar = () => {
</div>
) : null}
<div className="flex items-center">
<div className="px-3 md:px-4">
{/* <div className="px-3 md:px-4">
<ThemeSwitcher />
</div>
</div> */}
{connected ? (
<div className="flex items-center pr-4 md:pr-0">
<Button
onClick={() => setShowDepositWithdrawModal(true)}
secondary
className="mx-4"
>{`${t('deposit')} / ${t('withdraw')}`}</Button>
<button
className="hidden h-16 border-l border-th-bkg-3 px-4 md:block"
id="account-step-two"
@ -142,6 +150,13 @@ const TopBar = () => {
)}
</div>
</div>
{showDepositWithdrawModal ? (
<DepositWithdrawModal
action="deposit"
isOpen={showDepositWithdrawModal}
onClose={() => setShowDepositWithdrawModal(false)}
/>
) : null}
{showMangoAccountsModal ? (
<MangoAccountsListModal
isOpen={showMangoAccountsModal}

View File

@ -34,7 +34,7 @@ import ButtonGroup from './forms/ButtonGroup'
import Input from './forms/Input'
import Label from './forms/Label'
import WalletIcon from './icons/WalletIcon'
import { walletBalanceForToken } from './modals/DepositModal'
import { walletBalanceForToken } from './DepositForm'
import ParticlesBackground from './ParticlesBackground'
import EditNftProfilePic from './profile/EditNftProfilePic'
import EditProfileForm from './profile/EditProfileForm'
@ -393,7 +393,7 @@ const UserSetup = ({ onClose }: { onClose: () => void }) => {
<div className="mb-4">
<InlineNotification
type="info"
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} deposit limit during alpha testing.`}
desc={`There is a $${ALPHA_DEPOSIT_LIMIT} account value limit during alpha testing.`}
/>
<SolBalanceWarnings
amount={depositAmount}

310
components/WithdrawForm.tsx Normal file
View File

@ -0,0 +1,310 @@
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
import {
ArrowUpTrayIcon,
ChevronDownIcon,
ExclamationCircleIcon,
} from '@heroicons/react/20/solid'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useCallback, useMemo, useState } from 'react'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import mangoStore from '@store/mangoStore'
import { INPUT_TOKEN_DEFAULT } from './../utils/constants'
import { notify } from './../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from './../utils/numbers'
import ActionTokenList from './account/ActionTokenList'
import ButtonGroup from './forms/ButtonGroup'
import Label from './forms/Label'
import Button from './shared/Button'
import InlineNotification from './shared/InlineNotification'
import Loading from './shared/Loading'
import { EnterBottomExitBottom, FadeInFadeOut } from './shared/Transitions'
import { withValueLimit } from './swap/SwapForm'
import { getMaxWithdrawForBank } from './swap/useTokenMax'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
interface WithdrawFormProps {
onSuccess: () => void
token?: string
}
function WithdrawForm({ onSuccess, token }: WithdrawFormProps) {
const { t } = useTranslation(['common', 'trade'])
const { group } = useMangoGroup()
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState(
token || INPUT_TOKEN_DEFAULT
)
const [showTokenList, setShowTokenList] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
const { mangoTokens } = useJupiterMints()
const { mangoAccount } = useMangoAccount()
const bank = useMemo(() => {
const group = mangoStore.getState().group
return group?.banksMapByName.get(selectedToken)?.[0]
}, [selectedToken])
const logoUri = useMemo(() => {
let logoURI
if (mangoTokens?.length) {
logoURI = mangoTokens.find(
(t) => t.address === bank?.mint.toString()
)?.logoURI
}
return logoURI
}, [bank?.mint, mangoTokens])
const tokenMax = useMemo(() => {
if (!bank || !mangoAccount || !group) return new Decimal(0)
const amount = getMaxWithdrawForBank(group, bank, mangoAccount)
return amount && amount.gt(0)
? floorToDecimal(amount, bank.mintDecimals)
: new Decimal(0)
}, [mangoAccount, bank, group])
const handleSizePercentage = useCallback(
(percentage: string) => {
setSizePercentage(percentage)
const amount = tokenMax.mul(Number(percentage) / 100)
setInputAmount(amount.toFixed())
},
[tokenMax]
)
const handleWithdraw = useCallback(async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!mangoAccount || !group || !bank) return
setSubmitting(true)
try {
const tx = await client.tokenWithdraw(
group,
mangoAccount,
bank.mint,
parseFloat(inputAmount),
false
)
notify({
title: 'Transaction confirmed',
type: 'success',
txid: tx,
})
actions.reloadMangoAccount()
} catch (e: any) {
console.error(e)
notify({
title: 'Transaction failed',
description: e.message,
txid: e?.txid,
type: 'error',
})
} finally {
setSubmitting(false)
onSuccess()
}
}, [bank, inputAmount])
const handleSelectToken = useCallback((token: string) => {
setSelectedToken(token)
setShowTokenList(false)
}, [])
const withdrawBanks = useMemo(() => {
if (mangoAccount) {
const banks = group?.banksMapByName
? Array.from(group?.banksMapByName, ([key, value]) => {
const bank: Bank = value[0]
const accountBalance = getMaxWithdrawForBank(
group,
bank,
mangoAccount
)
return {
key,
value,
accountBalance: accountBalance
? floorToDecimal(accountBalance, bank.mintDecimals).toNumber()
: 0,
accountBalanceValue:
accountBalance && bank.uiPrice
? accountBalance.toNumber() * bank.uiPrice
: 0,
}
})
: []
return banks
}
return []
}, [mangoAccount, group])
const initHealth = useMemo(() => {
return group && mangoAccount
? mangoAccount.getHealthRatioUi(group, HealthType.init)
: 100
}, [mangoAccount])
const showInsufficientBalance = Number(inputAmount)
? tokenMax.lt(inputAmount)
: false
return (
<>
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
show={showTokenList}
>
<h2 className="mb-4 text-center text-lg">
{t('select-withdraw-token')}
</h2>
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
<div className="text-left">
<p className="text-xs">{t('token')}</p>
</div>
<div className="flex justify-end">
<p className="text-xs">{t('available-balance')}</p>
</div>
</div>
<ActionTokenList
banks={withdrawBanks}
onSelect={handleSelectToken}
sortByKey="accountBalanceValue"
valueKey="accountBalance"
/>
</EnterBottomExitBottom>
<FadeInFadeOut
className="flex h-[386px] flex-col justify-between"
show={!showTokenList}
>
<div>
{initHealth <= 0 ? (
<div className="mb-4">
<InlineNotification
type="error"
desc="You have no available collateral to withdraw."
/>
</div>
) : null}
<div className="grid grid-cols-2">
<div className="col-span-2 flex justify-between">
<Label text={`${t('withdraw')} ${t('token')}`} />
<MaxAmountButton
className="mb-2"
label={t('max')}
onClick={() => handleSizePercentage('100')}
value={tokenMax.toString()}
/>
</div>
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
<button
onClick={() => setShowTokenList(true)}
className="default-transition flex h-full w-full items-center rounded-lg rounded-r-none py-2 px-3 text-th-fgd-2 hover:cursor-pointer hover:bg-th-bkg-2 hover:text-th-fgd-1"
>
<div className="mr-2.5 flex min-w-[24px] items-center">
<Image
alt=""
width="24"
height="24"
src={logoUri || `/icons/${selectedToken.toLowerCase()}.svg`}
/>
</div>
<div className="flex w-full items-center justify-between">
<div className="text-xl font-bold">{selectedToken}</div>
<ChevronDownIcon className="h-6 w-6" />
</div>
</button>
</div>
<div className="col-span-1">
<NumberFormat
name="amountIn"
id="amountIn"
inputMode="decimal"
thousandSeparator=","
allowNegative={false}
isNumericString={true}
decimalScale={bank?.mintDecimals || 6}
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
placeholder="0.00"
value={inputAmount}
onValueChange={(e: NumberFormatValues) =>
setInputAmount(!Number.isNaN(Number(e.value)) ? e.value : '')
}
isAllowed={withValueLimit}
/>
</div>
<div className="col-span-2 mt-2">
<ButtonGroup
activeValue={sizePercentage}
className="font-mono"
onChange={(p) => handleSizePercentage(p)}
values={['10', '25', '50', '75', '100']}
unit="%"
/>
</div>
</div>
<div className="my-6 space-y-2 border-y border-th-bkg-3 px-2 py-4">
<HealthImpactTokenChange
mintPk={bank!.mint}
uiAmount={Number(inputAmount)}
/>
<div className="flex justify-between">
<p>{t('withdraw-value')}</p>
<p className="font-mono text-th-fgd-1">
{bank?.uiPrice
? formatFixedDecimals(
bank.uiPrice * Number(inputAmount),
true
)
: '-'}
</p>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
onClick={handleWithdraw}
className="flex w-full items-center justify-center"
size="large"
disabled={
!inputAmount || showInsufficientBalance || initHealth <= 0
}
>
{submitting ? (
<Loading className="mr-2 h-5 w-5" />
) : showInsufficientBalance ? (
<div className="flex items-center">
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('swap:insufficient-balance', {
symbol: selectedToken,
})}
</div>
) : (
<div className="flex items-center">
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
{t('withdraw')}
</div>
)}
</Button>
{bank ? (
<div className="pt-4">
<TokenVaultWarnings bank={bank} />
</div>
) : null}
</div>
</FadeInFadeOut>
</>
)
}
export default WithdrawForm

View File

@ -1,9 +1,6 @@
import { useMemo, useState } from 'react'
import Button, { LinkButton } from '../shared/Button'
import DepositModal from '../modals/DepositModal'
import WithdrawModal from '../modals/WithdrawModal'
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
BanknotesIcon,
DocumentDuplicateIcon,
@ -27,15 +24,15 @@ import RepayModal from '@components/modals/RepayModal'
import DelegateModal from '@components/modals/DelegateModal'
import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import BorrowModal from '@components/modals/BorrowModal'
const AccountActions = () => {
const { t } = useTranslation(['common', 'close-account'])
const { group } = useMangoGroup()
const { mangoAccount } = useMangoAccount()
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
const [showDepositModal, setShowDepositModal] = useState(false)
const [showEditAccountModal, setShowEditAccountModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
const [showBorrowModal, setShowBorrowModal] = useState(false)
const [showRepayModal, setShowRepayModal] = useState(false)
const [showDelegateModal, setShowDelegateModal] = useState(false)
@ -74,20 +71,11 @@ const AccountActions = () => {
<Button
className="flex items-center"
disabled={!mangoAccount}
onClick={() => setShowDepositModal(true)}
onClick={() => setShowBorrowModal(true)}
secondary={hasBorrows}
>
<ArrowDownTrayIcon className="mr-2 h-5 w-5" />
{t('deposit')}
</Button>
<Button
className="flex items-center"
disabled={!mangoAccount}
onClick={() => setShowWithdrawModal(true)}
secondary
>
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
{t('withdraw')}
{t('borrow')}
</Button>
<IconDropMenu
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
@ -135,22 +123,16 @@ const AccountActions = () => {
onClose={() => setShowCloseAccountModal(false)}
/>
) : null}
{showDepositModal ? (
<DepositModal
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
/>
) : null}
{showEditAccountModal ? (
<AccountNameModal
isOpen={showEditAccountModal}
onClose={() => setShowEditAccountModal(false)}
/>
) : null}
{showWithdrawModal ? (
<WithdrawModal
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
{showBorrowModal ? (
<BorrowModal
isOpen={showBorrowModal}
onClose={() => setShowBorrowModal(false)}
/>
) : null}

View File

@ -6,8 +6,6 @@ import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { 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 { formatFixedDecimals } from '../../utils/numbers'
import FlipNumbers from 'react-flip-numbers'
@ -64,8 +62,6 @@ const AccountPage = () => {
const totalInterestData = mangoStore(
(s) => s.mangoAccount.stats.interestTotals.data
)
const [showDepositModal, setShowDepositModal] = useState<boolean>(false)
const [showWithdrawModal, setShowWithdrawModal] = useState<boolean>(false)
const [chartToShow, setChartToShow] = useState<
'account-value' | 'cumulative-interest-value' | 'pnl' | ''
>('')
@ -492,18 +488,6 @@ const AccountPage = () => {
</button>
</div>
<AccountTabs />
{showDepositModal ? (
<DepositModal
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
/>
) : null}
{showWithdrawModal ? (
<WithdrawModal
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
/>
) : null}
{!tourSettings?.account_tour_seen && isOnBoarded && connected ? (
<AccountOnboardingTour />
) : null}

View File

@ -4,22 +4,13 @@ import {
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
import { formatDecimal, formatFixedDecimals } from '../../utils/numbers'
import Button from '../shared/Button'
import { useState } from 'react'
import DepositModal from '../modals/DepositModal'
import WithdrawModal from '../modals/WithdrawModal'
import { useTranslation } from 'next-i18next'
import { ArrowDownTrayIcon } from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import useMangoAccount from 'hooks/useMangoAccount'
const MangoAccountSummary = () => {
const { t } = useTranslation('common')
const { connected } = useWallet()
const group = mangoStore.getState().group
const { mangoAccount } = useMangoAccount()
const [showDepositModal, setShowDepositModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
// const leverage = useMemo(() => {
// if (!group || !mangoAccount) return 0
@ -36,7 +27,7 @@ const MangoAccountSummary = () => {
return (
<>
<div className="mb-4 space-y-2">
<div className="space-y-2">
<div>
<p className="text-sm text-th-fgd-3">{t('health')}</p>
<p className="font-mono text-sm text-th-fgd-1">
@ -95,29 +86,6 @@ const MangoAccountSummary = () => {
</p>
</div> */}
</div>
<div className="space-y-2">
<Button
className="flex w-full items-center justify-center"
disabled={!mangoAccount || !connected}
onClick={() => setShowDepositModal(true)}
>
<ArrowDownTrayIcon className="mr-2 h-5 w-5" />
{t('deposit')}
</Button>
</div>
{showDepositModal ? (
<DepositModal
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
/>
) : null}
{showWithdrawModal ? (
<WithdrawModal
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
/>
) : null}
</>
)
}

View File

@ -16,7 +16,6 @@ const AccountNameModal = ({ isOpen, onClose }: ModalProps) => {
const [loading, setLoading] = useState(false)
const [name, setName] = useState(mangoAccount?.name || '')
// This doesn't work yet...
const handleUpdateccountName = async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group

View File

@ -0,0 +1,44 @@
import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
import { useState } from 'react'
import TabUnderline from '@components/shared/TabUnderline'
import DepositForm from '@components/DepositForm'
import WithdrawForm from '@components/WithdrawForm'
interface DepositWithdrawModalProps {
action: 'deposit' | 'withdraw'
token?: string
}
type ModalCombinedProps = DepositWithdrawModalProps & ModalProps
const DepositWithdrawModal = ({
action,
isOpen,
onClose,
token,
}: ModalCombinedProps) => {
const [activeTab, setActiveTab] = useState(action)
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="h-[448px]">
<div className="pb-2">
<TabUnderline
activeValue={activeTab}
values={['deposit', 'withdraw']}
onChange={(v) => setActiveTab(v)}
/>
</div>
{activeTab === 'deposit' ? (
<DepositForm onSuccess={onClose} token={token} />
) : null}
{activeTab === 'withdraw' ? (
<WithdrawForm onSuccess={onClose} token={token} />
) : null}
</div>
</Modal>
)
}
export default DepositWithdrawModal

View File

@ -25,7 +25,7 @@ import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
import { withValueLimit } from '../swap/SwapForm'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
import { walletBalanceForToken } from './DepositModal'
import { walletBalanceForToken } from '../DepositForm'
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from 'hooks/useJupiterMints'

View File

@ -1,318 +0,0 @@
import { Bank, HealthType } from '@blockworks-foundation/mango-v4'
import {
ArrowUpTrayIcon,
ChevronDownIcon,
ExclamationCircleIcon,
} from '@heroicons/react/20/solid'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useCallback, useMemo, useState } from 'react'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import mangoStore from '@store/mangoStore'
import { ModalProps } from '../../types/modal'
import { INPUT_TOKEN_DEFAULT } from '../../utils/constants'
import { notify } from '../../utils/notifications'
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
import ActionTokenList from '../account/ActionTokenList'
import ButtonGroup from '../forms/ButtonGroup'
import Label from '../forms/Label'
import Button from '../shared/Button'
import InlineNotification from '../shared/InlineNotification'
import Loading from '../shared/Loading'
import Modal from '../shared/Modal'
import { EnterBottomExitBottom, FadeInFadeOut } from '../shared/Transitions'
import { withValueLimit } from '../swap/SwapForm'
import { getMaxWithdrawForBank } from '../swap/useTokenMax'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import HealthImpactTokenChange from '@components/HealthImpactTokenChange'
import useMangoAccount from 'hooks/useMangoAccount'
import useJupiterMints from 'hooks/useJupiterMints'
import useMangoGroup from 'hooks/useMangoGroup'
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
interface WithdrawModalProps {
token?: string
}
type ModalCombinedProps = WithdrawModalProps & ModalProps
function WithdrawModal({ isOpen, onClose, token }: ModalCombinedProps) {
const { t } = useTranslation(['common', 'trade'])
const { group } = useMangoGroup()
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState(
token || INPUT_TOKEN_DEFAULT
)
const [showTokenList, setShowTokenList] = useState(false)
const [sizePercentage, setSizePercentage] = useState('')
const { mangoTokens } = useJupiterMints()
const { mangoAccount } = useMangoAccount()
const bank = useMemo(() => {
const group = mangoStore.getState().group
return group?.banksMapByName.get(selectedToken)?.[0]
}, [selectedToken])
const logoUri = useMemo(() => {
let logoURI
if (mangoTokens?.length) {
logoURI = mangoTokens.find(
(t) => t.address === bank?.mint.toString()
)?.logoURI
}
return logoURI
}, [bank?.mint, mangoTokens])
const tokenMax = useMemo(() => {
if (!bank || !mangoAccount || !group) return new Decimal(0)
const amount = getMaxWithdrawForBank(group, bank, mangoAccount)
return amount && amount.gt(0)
? floorToDecimal(amount, bank.mintDecimals)
: new Decimal(0)
}, [mangoAccount, bank, group])
const handleSizePercentage = useCallback(
(percentage: string) => {
setSizePercentage(percentage)
const amount = tokenMax.mul(Number(percentage) / 100)
setInputAmount(amount.toFixed())
},
[tokenMax]
)
const handleWithdraw = useCallback(async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!mangoAccount || !group || !bank) return
setSubmitting(true)
try {
const tx = await client.tokenWithdraw(
group,
mangoAccount,
bank.mint,
parseFloat(inputAmount),
false
)
notify({
title: 'Transaction confirmed',
type: 'success',
txid: tx,
})
actions.reloadMangoAccount()
} catch (e: any) {
console.error(e)
notify({
title: 'Transaction failed',
description: e.message,
txid: e?.txid,
type: 'error',
})
} finally {
setSubmitting(false)
onClose()
}
}, [bank, inputAmount])
const handleSelectToken = useCallback((token: string) => {
setSelectedToken(token)
setShowTokenList(false)
}, [])
const withdrawBanks = useMemo(() => {
if (mangoAccount) {
const banks = group?.banksMapByName
? Array.from(group?.banksMapByName, ([key, value]) => {
const bank: Bank = value[0]
const accountBalance = getMaxWithdrawForBank(
group,
bank,
mangoAccount
)
return {
key,
value,
accountBalance: accountBalance
? floorToDecimal(accountBalance, bank.mintDecimals).toNumber()
: 0,
accountBalanceValue:
accountBalance && bank.uiPrice
? accountBalance.toNumber() * bank.uiPrice
: 0,
}
})
: []
return banks
}
return []
}, [mangoAccount, group])
const initHealth = useMemo(() => {
return group && mangoAccount
? mangoAccount.getHealthRatioUi(group, HealthType.init)
: 100
}, [mangoAccount])
const showInsufficientBalance = Number(inputAmount)
? tokenMax.lt(inputAmount)
: false
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="">
<EnterBottomExitBottom
className="absolute bottom-0 left-0 z-20 h-full w-full overflow-auto rounded-lg bg-th-bkg-1 p-6"
show={showTokenList}
>
<h2 className="mb-4 text-center">{t('select-token')}</h2>
<div className="grid auto-cols-fr grid-flow-col px-4 pb-2">
<div className="text-left">
<p className="text-xs">{t('token')}</p>
</div>
<div className="flex justify-end">
<p className="text-xs">{t('available-balance')}</p>
</div>
</div>
<ActionTokenList
banks={withdrawBanks}
onSelect={handleSelectToken}
sortByKey="accountBalanceValue"
valueKey="accountBalance"
/>
</EnterBottomExitBottom>
<FadeInFadeOut
className="flex h-full flex-col justify-between"
show={isOpen}
>
<div>
<h2 className="mb-4 text-center">{t('withdraw')}</h2>
{initHealth <= 0 ? (
<div className="mb-4">
<InlineNotification
type="error"
desc="You have no available collateral to withdraw."
/>
</div>
) : null}
<div className="grid grid-cols-2">
<div className="col-span-2 flex justify-between">
<Label text={t('token')} />
<MaxAmountButton
className="mb-2"
label={t('max')}
onClick={() => handleSizePercentage('100')}
value={tokenMax.toString()}
/>
</div>
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
<button
onClick={() => setShowTokenList(true)}
className="default-transition flex h-full w-full items-center rounded-lg rounded-r-none py-2 px-3 text-th-fgd-2 hover:cursor-pointer hover:bg-th-bkg-2 hover:text-th-fgd-1"
>
<div className="mr-2.5 flex min-w-[24px] items-center">
<Image
alt=""
width="24"
height="24"
src={
logoUri || `/icons/${selectedToken.toLowerCase()}.svg`
}
/>
</div>
<div className="flex w-full items-center justify-between">
<div className="text-xl font-bold">{selectedToken}</div>
<ChevronDownIcon className="h-6 w-6" />
</div>
</button>
</div>
<div className="col-span-1">
<NumberFormat
name="amountIn"
id="amountIn"
inputMode="decimal"
thousandSeparator=","
allowNegative={false}
isNumericString={true}
decimalScale={bank?.mintDecimals || 6}
className="w-full rounded-lg rounded-l-none border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-xl tracking-wider text-th-fgd-1 focus:border-th-input-border-hover focus:outline-none md:hover:border-th-input-border-hover"
placeholder="0.00"
value={inputAmount}
onValueChange={(e: NumberFormatValues) =>
setInputAmount(
!Number.isNaN(Number(e.value)) ? e.value : ''
)
}
isAllowed={withValueLimit}
/>
</div>
<div className="col-span-2 mt-2">
<ButtonGroup
activeValue={sizePercentage}
className="font-mono"
onChange={(p) => handleSizePercentage(p)}
values={['10', '25', '50', '75', '100']}
unit="%"
/>
</div>
</div>
<div className="my-6 space-y-2 border-y border-th-bkg-3 px-2 py-4">
<HealthImpactTokenChange
mintPk={bank!.mint}
uiAmount={Number(inputAmount)}
/>
<div className="flex justify-between">
<p>{t('withdrawal-value')}</p>
<p className="font-mono text-th-fgd-1">
{bank?.uiPrice
? formatFixedDecimals(
bank.uiPrice * Number(inputAmount),
true
)
: '-'}
</p>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
onClick={handleWithdraw}
className="flex w-full items-center justify-center"
size="large"
disabled={
!inputAmount || showInsufficientBalance || initHealth <= 0
}
>
{submitting ? (
<Loading className="mr-2 h-5 w-5" />
) : showInsufficientBalance ? (
<div className="flex items-center">
<ExclamationCircleIcon className="mr-2 h-5 w-5 flex-shrink-0" />
{t('swap:insufficient-balance', {
symbol: selectedToken,
})}
</div>
) : (
<div className="flex items-center">
<ArrowUpTrayIcon className="mr-2 h-5 w-5" />
{t('withdraw')}
</div>
)}
</Button>
</div>
{bank ? (
<div className="pt-4">
<TokenVaultWarnings bank={bank} />
</div>
) : null}
</FadeInFadeOut>
</div>
</Modal>
)
}
export default WithdrawModal

View File

@ -1,6 +1,6 @@
import { Bank } from '@blockworks-foundation/mango-v4'
import BorrowModal from '@components/modals/BorrowModal'
import DepositModal from '@components/modals/DepositModal'
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
import Button from '@components/shared/Button'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
@ -87,7 +87,8 @@ const ActionPanel = ({ bank }: { bank: Bank }) => {
</div>
</div>
{showDepositModal ? (
<DepositModal
<DepositWithdrawModal
action="deposit"
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
token={bank!.name}

View File

@ -21,6 +21,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -93,7 +94,9 @@
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-deposit-token": "Select Deposit Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -127,6 +130,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -21,6 +21,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -93,7 +94,9 @@
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-deposit-token": "Select Deposit Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -127,6 +130,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -21,6 +21,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -93,7 +94,9 @@
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-deposit-token": "Select Deposit Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -127,6 +130,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -21,6 +21,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -93,7 +94,9 @@
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-deposit-token": "Select Deposit Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -127,6 +130,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}

View File

@ -21,6 +21,7 @@
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow Rate (APR)",
"borrow-value": "Borrow Value",
"buy": "Buy",
@ -93,7 +94,9 @@
"repayment-value": "Repayment Value",
"rolling-change": "24h Change",
"save": "Save",
"select-deposit-token": "Select Deposit Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-zero-balances": "Show Zero Balances",
@ -127,6 +130,6 @@
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
"withdraw-value": "Withdraw Value"
}