swap form button component

This commit is contained in:
saml33 2023-10-30 11:01:35 +11:00
parent e2abfb5bec
commit b5941acf3e
4 changed files with 150 additions and 220 deletions

View File

@ -19,24 +19,14 @@ import SwapSlider from './SwapSlider'
import PercentageSelectButtons from './PercentageSelectButtons'
import BuyTokenInput from './BuyTokenInput'
import SellTokenInput from './SellTokenInput'
import Button from '@components/shared/Button'
import SwapReviewRouteInfo from './SwapReviewRouteInfo'
import useIpAddress from 'hooks/useIpAddress'
import { useTranslation } from 'react-i18next'
import useQuoteRoutes from './useQuoteRoutes'
import { useTokenMax } from './useTokenMax'
import Loading from '@components/shared/Loading'
import InlineNotification from '@components/shared/InlineNotification'
import useMangoAccount from 'hooks/useMangoAccount'
import Link from 'next/link'
import SecondaryConnectButton from '@components/shared/SecondaryConnectButton'
import useRemainingBorrowsInPeriod from 'hooks/useRemainingBorrowsInPeriod'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { formatCurrencyValue } from 'utils/numbers'
import { SwapFormTokenListType } from './SwapFormTokenList'
import useTokenPositionsFull from 'hooks/useTokenPositionsFull'
import TopBarStore from '@store/topBarStore'
import SwapFormSubmitButton from './SwapFormSubmitButton'
dayjs.extend(relativeTime)
@ -61,7 +51,6 @@ const MarketSwapForm = ({
setShowTokenSelect,
onSuccess,
}: MarketSwapFormProps) => {
const { t } = useTranslation(['common', 'swap', 'trade'])
//initial state is undefined null is returned on error
const [selectedRoute, setSelectedRoute] =
useState<JupiterV6RouteInfo | null>()
@ -90,7 +79,6 @@ const MarketSwapForm = ({
wallet: publicKey?.toBase58(),
mangoAccount,
})
const { ipAllowed, ipCountry } = useIpAddress()
const amountInAsDecimal: Decimal | null = useMemo(() => {
return Number(debouncedAmountIn)
@ -276,131 +264,15 @@ const MarketSwapForm = ({
setShowTokenSelect={setShowTokenSelect}
handleRepay={handleRepay}
/>
{ipAllowed ? (
<SwapFormSubmitButton
loadingSwapDetails={loadingSwapDetails}
useMargin={useMargin}
selectedRoute={selectedRoute}
setShowConfirm={setShowConfirm}
amountIn={amountInAsDecimal}
inputSymbol={inputBank?.name}
amountOut={selectedRoute ? amountOutAsDecimal.toNumber() : undefined}
/>
) : (
<Button
disabled
className="mb-4 mt-6 flex w-full items-center justify-center text-base"
size="large"
>
{t('country-not-allowed', {
country: ipCountry ? `(${ipCountry})` : '',
})}
</Button>
)}
<SwapFormSubmitButton
loadingSwapDetails={loadingSwapDetails}
selectedRoute={selectedRoute}
setShowConfirm={setShowConfirm}
amountIn={amountInAsDecimal}
amountOut={selectedRoute ? amountOutAsDecimal.toNumber() : undefined}
/>
</>
)
}
export default MarketSwapForm
const SwapFormSubmitButton = ({
amountIn,
amountOut,
loadingSwapDetails,
selectedRoute,
setShowConfirm,
}: {
amountIn: Decimal
amountOut: number | undefined
inputSymbol: string | undefined
loadingSwapDetails: boolean
selectedRoute: JupiterV6RouteInfo | undefined | null
setShowConfirm: (x: boolean) => void
useMargin: boolean
}) => {
const { t } = useTranslation('common')
const { mangoAccountAddress } = useMangoAccount()
const { connected } = useWallet()
const { setShowSettingsModal } = TopBarStore()
// const { amount: tokenMax, amountWithBorrow } = useTokenMax(useMargin)
const { inputBank, outputBank } = mangoStore((s) => s.swap)
const { remainingBorrowsInPeriod, timeToNextPeriod } =
useRemainingBorrowsInPeriod(true)
const tokenPositionsFull = useTokenPositionsFull([outputBank, inputBank])
// check if the borrowed amount exceeds the net borrow limit in the current period
const borrowExceedsLimitInPeriod = useMemo(() => {
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount || !inputBank || !remainingBorrowsInPeriod) return false
const balance = mangoAccount.getTokenDepositsUi(inputBank)
const remainingBalance = balance - amountIn.toNumber()
const borrowAmount = remainingBalance < 0 ? Math.abs(remainingBalance) : 0
const borrowAmountNotional = borrowAmount * inputBank.uiPrice
return borrowAmountNotional > remainingBorrowsInPeriod
}, [amountIn, inputBank, mangoAccountAddress, remainingBorrowsInPeriod])
const disabled =
!amountIn.toNumber() ||
!amountOut ||
!selectedRoute ||
!!selectedRoute.error
return (
<>
{connected ? (
<Button
onClick={() => setShowConfirm(true)}
className="mb-4 mt-6 flex w-full items-center justify-center text-base"
disabled={disabled}
size="large"
>
{loadingSwapDetails ? (
<Loading />
) : (
<span>{t('swap:review-swap')}</span>
)}
</Button>
) : (
<SecondaryConnectButton
className="mb-4 mt-6 flex w-full items-center justify-center"
isLarge
/>
)}
{tokenPositionsFull ? (
<div className="pb-4">
<InlineNotification
type="error"
desc={
<>
{t('error-token-positions-full')}{' '}
<Link href={''} onClick={() => setShowSettingsModal(true)}>
{t('manage')}
</Link>
</>
}
/>
</div>
) : null}
{borrowExceedsLimitInPeriod &&
remainingBorrowsInPeriod &&
timeToNextPeriod ? (
<div className="mb-4">
<InlineNotification
type="error"
desc={t('error-borrow-exceeds-limit', {
remaining: formatCurrencyValue(remainingBorrowsInPeriod),
resetTime: dayjs().to(dayjs().add(timeToNextPeriod, 'second')),
})}
/>
</div>
) : null}
{(selectedRoute === null && amountIn.gt(0)) ||
(selectedRoute && !!selectedRoute.error) ? (
<div className="mb-4">
<InlineNotification type="error" desc={t('swap:no-swap-found')} />
</div>
) : null}
</>
)
}

View File

@ -0,0 +1,132 @@
import InlineNotification from '@components/shared/InlineNotification'
import { formatCurrencyValue } from 'utils/numbers'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import Decimal from 'decimal.js'
import { JupiterV6RouteInfo } from 'types/jupiter'
import { useTranslation } from 'react-i18next'
import useMangoAccount from 'hooks/useMangoAccount'
import { useWallet } from '@solana/wallet-adapter-react'
import TopBarStore from '@store/topBarStore'
import mangoStore from '@store/mangoStore'
import useRemainingBorrowsInPeriod from 'hooks/useRemainingBorrowsInPeriod'
import useTokenPositionsFull from 'hooks/useTokenPositionsFull'
import { useMemo } from 'react'
import Button from '@components/shared/Button'
import Loading from '@components/shared/Loading'
import SecondaryConnectButton from '@components/shared/SecondaryConnectButton'
import Link from 'next/link'
import useIpAddress from 'hooks/useIpAddress'
dayjs.extend(relativeTime)
const SwapFormSubmitButton = ({
amountIn,
amountOut,
loadingSwapDetails,
selectedRoute,
setShowConfirm,
}: {
amountIn: Decimal
amountOut: number | undefined
loadingSwapDetails: boolean
selectedRoute: JupiterV6RouteInfo | undefined | null
setShowConfirm: (x: boolean) => void
}) => {
const { t } = useTranslation('common')
const { mangoAccountAddress } = useMangoAccount()
const { connected } = useWallet()
const { ipAllowed, ipCountry } = useIpAddress()
const { setShowSettingsModal } = TopBarStore()
const { inputBank, outputBank } = mangoStore((s) => s.swap)
const { remainingBorrowsInPeriod, timeToNextPeriod } =
useRemainingBorrowsInPeriod(true)
const tokenPositionsFull = useTokenPositionsFull([outputBank, inputBank])
// check if the borrowed amount exceeds the net borrow limit in the current period
const borrowExceedsLimitInPeriod = useMemo(() => {
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount || !inputBank || !remainingBorrowsInPeriod) return false
const balance = mangoAccount.getTokenDepositsUi(inputBank)
const remainingBalance = balance - amountIn.toNumber()
const borrowAmount = remainingBalance < 0 ? Math.abs(remainingBalance) : 0
const borrowAmountNotional = borrowAmount * inputBank.uiPrice
return borrowAmountNotional > remainingBorrowsInPeriod
}, [amountIn, inputBank, mangoAccountAddress, remainingBorrowsInPeriod])
const disabled =
!amountIn.toNumber() ||
!amountOut ||
!selectedRoute ||
!!selectedRoute.error
return ipAllowed ? (
<>
{connected ? (
<Button
onClick={() => setShowConfirm(true)}
className="mb-4 mt-6 flex w-full items-center justify-center text-base"
disabled={disabled}
size="large"
>
{loadingSwapDetails ? (
<Loading />
) : (
<span>{t('swap:review-swap')}</span>
)}
</Button>
) : (
<SecondaryConnectButton
className="mb-4 mt-6 flex w-full items-center justify-center"
isLarge
/>
)}
{tokenPositionsFull ? (
<div className="pb-4">
<InlineNotification
type="error"
desc={
<>
{t('error-token-positions-full')}{' '}
<Link href={''} onClick={() => setShowSettingsModal(true)}>
{t('manage')}
</Link>
</>
}
/>
</div>
) : null}
{borrowExceedsLimitInPeriod &&
remainingBorrowsInPeriod &&
timeToNextPeriod ? (
<div className="mb-4">
<InlineNotification
type="error"
desc={t('error-borrow-exceeds-limit', {
remaining: formatCurrencyValue(remainingBorrowsInPeriod),
resetTime: dayjs().to(dayjs().add(timeToNextPeriod, 'second')),
})}
/>
</div>
) : null}
{(selectedRoute === null && amountIn.gt(0)) ||
(selectedRoute && !!selectedRoute.error) ? (
<div className="mb-4">
<InlineNotification type="error" desc={t('swap:no-swap-found')} />
</div>
) : null}
</>
) : (
<Button
disabled
className="mb-4 mt-6 flex w-full items-center justify-center text-base"
size="large"
>
{t('country-not-allowed', {
country: ipCountry ? `(${ipCountry})` : '',
})}
</Button>
)
}
export default SwapFormSubmitButton

View File

@ -17,14 +17,8 @@ import { JupiterV6RouteInfo } from 'types/jupiter'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { DEFAULT_PERCENTAGE_VALUES } from './PercentageSelectButtons'
import BuyTokenInput from './BuyTokenInput'
import Button from '@components/shared/Button'
import SwapReviewRouteInfo from './SwapReviewRouteInfo'
import useIpAddress from 'hooks/useIpAddress'
import { useTranslation } from 'react-i18next'
import useQuoteRoutes from './useQuoteRoutes'
import Loading from '@components/shared/Loading'
import InlineNotification from '@components/shared/InlineNotification'
import SecondaryConnectButton from '@components/shared/SecondaryConnectButton'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { floorToDecimal } from 'utils/numbers'
@ -33,6 +27,7 @@ import WalletSellTokenInput from './WalletSellTokenInput'
import { walletBalanceForToken } from '@components/DepositForm'
import WalletSwapSlider from './WalletSwapSlider'
import ButtonGroup from '@components/forms/ButtonGroup'
import SwapFormSubmitButton from './SwapFormSubmitButton'
dayjs.extend(relativeTime)
@ -43,7 +38,6 @@ type WalletSwapFormProps = {
const set = mangoStore.getState().set
const WalletSwapForm = ({ setShowTokenSelect }: WalletSwapFormProps) => {
const { t } = useTranslation(['common', 'swap', 'trade'])
//initial state is undefined null is returned on error
const [selectedRoute, setSelectedRoute] =
useState<JupiterV6RouteInfo | null>()
@ -52,7 +46,6 @@ const WalletSwapForm = ({ setShowTokenSelect }: WalletSwapFormProps) => {
const [sizePercentage, setSizePercentage] = useState('')
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
const {
margin: useMargin,
slippage,
inputBank,
outputBank,
@ -73,7 +66,6 @@ const WalletSwapForm = ({ setShowTokenSelect }: WalletSwapFormProps) => {
mangoAccount: undefined,
mode: 'JUPITER',
})
const { ipAllowed, ipCountry } = useIpAddress()
const walletTokens = mangoStore((s) => s.wallet.tokens)
@ -278,81 +270,15 @@ const WalletSwapForm = ({ setShowTokenSelect }: WalletSwapFormProps) => {
loading={loadingSwapDetails}
setShowTokenSelect={setShowTokenSelect}
/>
{ipAllowed ? (
<SwapFormSubmitButton
loadingSwapDetails={loadingSwapDetails}
useMargin={useMargin}
selectedRoute={selectedRoute}
setShowConfirm={setShowConfirm}
amountIn={amountInAsDecimal}
inputSymbol={inputBank?.name}
amountOut={selectedRoute ? amountOutAsDecimal.toNumber() : undefined}
/>
) : (
<Button
disabled
className="mb-4 mt-6 flex w-full items-center justify-center text-base"
size="large"
>
{t('country-not-allowed', {
country: ipCountry ? `(${ipCountry})` : '',
})}
</Button>
)}
<SwapFormSubmitButton
loadingSwapDetails={loadingSwapDetails}
selectedRoute={selectedRoute}
setShowConfirm={setShowConfirm}
amountIn={amountInAsDecimal}
amountOut={selectedRoute ? amountOutAsDecimal.toNumber() : undefined}
/>
</>
)
}
export default WalletSwapForm
const SwapFormSubmitButton = ({
amountIn,
amountOut,
loadingSwapDetails,
selectedRoute,
setShowConfirm,
}: {
amountIn: Decimal
amountOut: number | undefined
inputSymbol: string | undefined
loadingSwapDetails: boolean
selectedRoute: JupiterV6RouteInfo | undefined | null
setShowConfirm: (x: boolean) => void
useMargin: boolean
}) => {
const { t } = useTranslation('common')
const { connected } = useWallet()
const disabled =
(connected && !amountIn.toNumber()) || !amountOut || !selectedRoute
return (
<>
{connected ? (
<Button
onClick={() => setShowConfirm(true)}
className="mb-4 mt-6 flex w-full items-center justify-center text-base"
disabled={disabled}
size="large"
>
{loadingSwapDetails ? (
<Loading />
) : (
<span>{t('swap:review-swap')}</span>
)}
</Button>
) : (
<SecondaryConnectButton
className="mb-4 mt-6 flex w-full items-center justify-center"
isLarge
/>
)}
{(selectedRoute === null && amountIn.gt(0)) ||
(selectedRoute && !!selectedRoute.error) ? (
<div className="mb-4">
<InlineNotification type="error" desc={t('swap:no-swap-found')} />
</div>
) : null}
</>
)
}

View File

@ -46,8 +46,8 @@ const fetchIpGeolocation = async () => {
}
export default function useIpAddress() {
const [ipAllowed, setIpAllowed] = useState(false)
const [spotAllowed, setSpotAllowed] = useState(false)
const [ipAllowed, setIpAllowed] = useState(true)
const [spotAllowed, setSpotAllowed] = useState(true)
const [ipCountry, setIpCountry] = useState('')
const ipCountryCode = useQuery<string, Error>(