address feedback
This commit is contained in:
parent
d8658574f7
commit
9f57d84349
|
@ -0,0 +1,85 @@
|
|||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useEffect, useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import ButtonGroup from '../forms/ButtonGroup'
|
||||
import Input from '../forms/Input'
|
||||
import Button, { IconButton, LinkButton } from '../shared/Button'
|
||||
|
||||
const slippagePresets = ['0.1', '0.5', '1', '2']
|
||||
|
||||
const SlippageSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const slippage = mangoStore((s) => s.swap.slippage)
|
||||
const set = mangoStore((s) => s.set)
|
||||
|
||||
const [showCustomSlippageForm, setShowCustomSlippageForm] = useState(false)
|
||||
const [inputValue, setInputValue] = useState(slippage.toString())
|
||||
|
||||
const handleSetSlippage = (slippage: string) => {
|
||||
set((s) => {
|
||||
s.swap.slippage = parseFloat(slippage)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!slippagePresets.includes(slippage.toString())) {
|
||||
setShowCustomSlippageForm(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="mb-3">{t('swap:slippage')}</h3>
|
||||
<IconButton
|
||||
className="absolute top-2 right-2 text-th-fgd-3"
|
||||
onClick={onClose}
|
||||
hideBg
|
||||
>
|
||||
<XMarkIcon className="h-6 w-6" />
|
||||
</IconButton>
|
||||
<div className="mt-4">
|
||||
<div className="mb-2 flex justify-between">
|
||||
<p className="text-th-fgd-2">{`${t('max')} ${t('swap:slippage')}`}</p>
|
||||
<LinkButton
|
||||
onClick={() => setShowCustomSlippageForm(!showCustomSlippageForm)}
|
||||
>
|
||||
{showCustomSlippageForm ? 'Preset' : t('settings:custom')}
|
||||
</LinkButton>
|
||||
</div>
|
||||
{showCustomSlippageForm ? (
|
||||
<>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="0.00"
|
||||
value={inputValue}
|
||||
onChange={(e: any) => setInputValue(e.target.value)}
|
||||
suffix="%"
|
||||
/>
|
||||
<Button
|
||||
disabled={
|
||||
!inputValue ||
|
||||
isNaN(Number(inputValue)) ||
|
||||
parseFloat(inputValue) <= 0
|
||||
}
|
||||
className="mt-4"
|
||||
onClick={() => handleSetSlippage(inputValue)}
|
||||
>
|
||||
{t('save')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<ButtonGroup
|
||||
activeValue={slippage.toString()}
|
||||
className="h-10 font-mono"
|
||||
onChange={(v) => handleSetSlippage(v)}
|
||||
unit="%"
|
||||
values={slippagePresets}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SlippageSettings
|
|
@ -1,11 +1,10 @@
|
|||
import { useState, useCallback, useEffect, useMemo } from 'react'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import {
|
||||
AdjustmentsHorizontalIcon,
|
||||
ArrowDownIcon,
|
||||
Cog8ToothIcon,
|
||||
ExclamationCircleIcon,
|
||||
LinkIcon,
|
||||
PencilIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import NumberFormat, {
|
||||
NumberFormatValues,
|
||||
|
@ -20,11 +19,11 @@ import useDebounce from '../shared/useDebounce'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import SwapFormTokenList from './SwapFormTokenList'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import Button, { IconButton } from '../shared/Button'
|
||||
import Button from '../shared/Button'
|
||||
import Loading from '../shared/Loading'
|
||||
import { EnterBottomExitBottom } from '../shared/Transitions'
|
||||
import useJupiterRoutes from './useJupiterRoutes'
|
||||
import SwapSettings from './SwapSettings'
|
||||
import SlippageSettings from './SlippageSettings'
|
||||
import SheenLoader from '../shared/SheenLoader'
|
||||
import { HealthType } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
|
@ -45,8 +44,8 @@ import SwapSlider from './SwapSlider'
|
|||
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
||||
import MaxSwapAmount from './MaxSwapAmount'
|
||||
import PercentageSelectButtons from './PercentageSelectButtons'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import useIpAddress from 'hooks/useIpAddress'
|
||||
import Checkbox from '@components/forms/Checkbox'
|
||||
|
||||
const MAX_DIGITS = 11
|
||||
export const withValueLimit = (values: NumberFormatValues): boolean => {
|
||||
|
@ -262,13 +261,19 @@ const SwapForm = () => {
|
|||
return !!amountInAsDecimal.toNumber() && connected && !selectedRoute
|
||||
}, [amountInAsDecimal, connected, selectedRoute])
|
||||
|
||||
const handleSetMargin = () => {
|
||||
set((s) => {
|
||||
s.swap.margin = !s.swap.margin
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentBox
|
||||
hidePadding
|
||||
// showBackground
|
||||
className="relative overflow-hidden border-x-0 md:border-l md:border-r-0 md:border-t-0 md:border-b-0"
|
||||
>
|
||||
<div className="px-6 pb-8 pt-3">
|
||||
<div className="">
|
||||
<Transition
|
||||
className="absolute top-0 left-0 z-10 h-full w-full bg-th-bkg-1 pb-0"
|
||||
show={showConfirm}
|
||||
|
@ -286,8 +291,6 @@ const SwapForm = () => {
|
|||
routes={routes}
|
||||
selectedRoute={selectedRoute}
|
||||
setSelectedRoute={setSelectedRoute}
|
||||
maintProjectedHealth={maintProjectedHealth}
|
||||
setShowSettings={setShowSettings}
|
||||
/>
|
||||
</Transition>
|
||||
<EnterBottomExitBottom
|
||||
|
@ -309,162 +312,164 @@ const SwapForm = () => {
|
|||
className="thin-scroll absolute bottom-0 left-0 z-10 h-full w-full overflow-auto bg-th-bkg-1 p-6 pb-0"
|
||||
show={showSettings}
|
||||
>
|
||||
<SwapSettings onClose={() => setShowSettings(false)} />
|
||||
<SlippageSettings onClose={() => setShowSettings(false)} />
|
||||
</EnterBottomExitBottom>
|
||||
<div className="flex items-center justify-end">
|
||||
<div id="swap-step-one">
|
||||
<IconButton
|
||||
className="-mr-2 text-th-fgd-3"
|
||||
hideBg
|
||||
onClick={() => setShowSettings(true)}
|
||||
size="small"
|
||||
>
|
||||
<Cog8ToothIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-2 flex items-end justify-between">
|
||||
<p className="text-th-fgd-2 lg:text-base">{t('swap:pay')}</p>
|
||||
<MaxSwapAmount
|
||||
useMargin={useMargin}
|
||||
setAmountIn={(v) => setAmountInFormValue(v, true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 grid grid-cols-2" id="swap-step-two">
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
|
||||
<TokenSelect
|
||||
bank={
|
||||
inputBank || group?.banksMapByName.get(INPUT_TOKEN_DEFAULT)?.[0]
|
||||
}
|
||||
showTokenList={setShowTokenSelect}
|
||||
type="input"
|
||||
<div className="p-6">
|
||||
<div className="mb-2 flex items-end justify-between">
|
||||
<p className="text-th-fgd-2 lg:text-base">{t('swap:pay')}</p>
|
||||
<MaxSwapAmount
|
||||
useMargin={useMargin}
|
||||
setAmountIn={(v) => setAmountInFormValue(v, true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 flex h-[54px]">
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={inputBank?.mintDecimals || 6}
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
className="w-full rounded-l-none rounded-r-lg border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-base font-bold text-th-fgd-1 focus:outline-none lg:text-lg xl:text-xl"
|
||||
placeholder="0.00"
|
||||
value={amountInFormValue}
|
||||
onValueChange={handleAmountInChange}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mb-2 flex justify-center">
|
||||
<button
|
||||
className="rounded-full border border-th-bkg-4 p-1.5 text-th-fgd-3 md:hover:text-th-active"
|
||||
onClick={handleSwitchTokens}
|
||||
>
|
||||
<ArrowDownIcon
|
||||
className="h-5 w-5"
|
||||
style={
|
||||
animateSwitchArrow % 2 == 0
|
||||
? { transform: 'rotate(0deg)' }
|
||||
: { transform: 'rotate(360deg)' }
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<p className="mb-2 text-th-fgd-2 lg:text-base">{t('swap:receive')}</p>
|
||||
<div id="swap-step-three" className="mb-3 grid grid-cols-2">
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
|
||||
<TokenSelect
|
||||
bank={
|
||||
outputBank ||
|
||||
group?.banksMapByName.get(OUTPUT_TOKEN_DEFAULT)?.[0]
|
||||
}
|
||||
showTokenList={setShowTokenSelect}
|
||||
type="output"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-[54px] w-full items-center justify-end rounded-r-lg border border-th-input-border text-right text-lg font-bold text-th-fgd-3 xl:text-xl">
|
||||
{loadingSwapDetails ? (
|
||||
<div className="w-full">
|
||||
<SheenLoader className="flex flex-1 rounded-l-none">
|
||||
<div className="h-[52px] w-full rounded-r-lg bg-th-bkg-4" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-3 grid grid-cols-2" id="swap-step-two">
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
|
||||
<TokenSelect
|
||||
bank={
|
||||
inputBank ||
|
||||
group?.banksMapByName.get(INPUT_TOKEN_DEFAULT)?.[0]
|
||||
}
|
||||
showTokenList={setShowTokenSelect}
|
||||
type="input"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 flex h-[54px]">
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={outputBank?.mintDecimals || 6}
|
||||
name="amountOut"
|
||||
id="amountOut"
|
||||
className="w-full rounded-l-none rounded-r-lg bg-th-input-bkg p-3 text-right font-mono text-base font-bold text-th-fgd-1 focus:outline-none lg:text-lg xl:text-xl"
|
||||
decimalScale={inputBank?.mintDecimals || 6}
|
||||
name="amountIn"
|
||||
id="amountIn"
|
||||
className="w-full rounded-l-none rounded-r-lg border border-th-input-border bg-th-input-bkg p-3 text-right font-mono text-base font-bold text-th-fgd-1 focus:outline-none lg:text-lg xl:text-xl"
|
||||
placeholder="0.00"
|
||||
value={amountOutFormValue}
|
||||
onValueChange={handleAmountOutChange}
|
||||
value={amountInFormValue}
|
||||
onValueChange={handleAmountInChange}
|
||||
isAllowed={withValueLimit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{swapFormSizeUi === 'slider' ? (
|
||||
<SwapSlider
|
||||
useMargin={useMargin}
|
||||
amount={amountInAsDecimal.toNumber()}
|
||||
onChange={(v) => setAmountInFormValue(v, true)}
|
||||
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
|
||||
/>
|
||||
) : (
|
||||
<PercentageSelectButtons
|
||||
amountIn={amountInAsDecimal.toString()}
|
||||
setAmountIn={(v) => setAmountInFormValue(v, true)}
|
||||
useMargin={useMargin}
|
||||
/>
|
||||
)}
|
||||
{ipAllowed ? (
|
||||
<SwapFormSubmitButton
|
||||
loadingSwapDetails={loadingSwapDetails}
|
||||
useMargin={useMargin}
|
||||
setShowConfirm={setShowConfirm}
|
||||
amountIn={amountInAsDecimal}
|
||||
inputSymbol={inputBank?.name}
|
||||
amountOut={
|
||||
selectedRoute ? amountOutAsDecimal.toNumber() : undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="mt-6 mb-4 flex-grow">
|
||||
<div className="flex">
|
||||
<Button disabled className="flex-grow">
|
||||
<span>
|
||||
{t('country-not-allowed', {
|
||||
country: ipCountry ? `(${ipCountry})` : '(Unknown)',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{group && inputBank ? <TokenVaultWarnings bank={inputBank} /> : null}
|
||||
<div className="space-y-2">
|
||||
<div id="swap-step-four">
|
||||
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('swap:tooltip-max-slippage')} delay={250}>
|
||||
<p className="tooltip-underline text-sm text-th-fgd-3">
|
||||
{`${t('max')} ${t('swap:slippage')}`}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<div className="flex items-center">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{slippage}%
|
||||
</p>
|
||||
<PencilIcon
|
||||
className="default-transition ml-2 h-4 w-4 md:hover:cursor-pointer md:hover:text-th-active"
|
||||
onClick={() => setShowSettings(true)}
|
||||
<div className="-mb-2 flex justify-center">
|
||||
<button
|
||||
className="rounded-full border border-th-bkg-4 p-1.5 text-th-fgd-3 md:hover:text-th-active"
|
||||
onClick={handleSwitchTokens}
|
||||
>
|
||||
<ArrowDownIcon
|
||||
className="h-5 w-5"
|
||||
style={
|
||||
animateSwitchArrow % 2 == 0
|
||||
? { transform: 'rotate(0deg)' }
|
||||
: { transform: 'rotate(360deg)' }
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<p className="mb-2 text-th-fgd-2 lg:text-base">{t('swap:receive')}</p>
|
||||
<div id="swap-step-three" className="mb-3 grid grid-cols-2">
|
||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">
|
||||
<TokenSelect
|
||||
bank={
|
||||
outputBank ||
|
||||
group?.banksMapByName.get(OUTPUT_TOKEN_DEFAULT)?.[0]
|
||||
}
|
||||
showTokenList={setShowTokenSelect}
|
||||
type="output"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-[54px] w-full items-center justify-end rounded-r-lg border border-th-input-border text-right text-lg font-bold text-th-fgd-3 xl:text-xl">
|
||||
{loadingSwapDetails ? (
|
||||
<div className="w-full">
|
||||
<SheenLoader className="flex flex-1 rounded-l-none">
|
||||
<div className="h-[52px] w-full rounded-r-lg bg-th-bkg-4" />
|
||||
</SheenLoader>
|
||||
</div>
|
||||
) : (
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={outputBank?.mintDecimals || 6}
|
||||
name="amountOut"
|
||||
id="amountOut"
|
||||
className="w-full rounded-l-none rounded-r-lg bg-th-input-bkg p-3 text-right font-mono text-base font-bold text-th-fgd-1 focus:outline-none lg:text-lg xl:text-xl"
|
||||
placeholder="0.00"
|
||||
value={amountOutFormValue}
|
||||
onValueChange={handleAmountOutChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{swapFormSizeUi === 'slider' ? (
|
||||
<SwapSlider
|
||||
useMargin={useMargin}
|
||||
amount={amountInAsDecimal.toNumber()}
|
||||
onChange={(v) => setAmountInFormValue(v, true)}
|
||||
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
|
||||
/>
|
||||
) : (
|
||||
<PercentageSelectButtons
|
||||
amountIn={amountInAsDecimal.toString()}
|
||||
setAmountIn={(v) => setAmountInFormValue(v, true)}
|
||||
useMargin={useMargin}
|
||||
/>
|
||||
)}
|
||||
{ipAllowed ? (
|
||||
<SwapFormSubmitButton
|
||||
loadingSwapDetails={loadingSwapDetails}
|
||||
useMargin={useMargin}
|
||||
setShowConfirm={setShowConfirm}
|
||||
amountIn={amountInAsDecimal}
|
||||
inputSymbol={inputBank?.name}
|
||||
amountOut={
|
||||
selectedRoute ? amountOutAsDecimal.toNumber() : undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="mt-6 mb-4 flex-grow">
|
||||
<div className="flex">
|
||||
<Button disabled className="flex-grow">
|
||||
<span>
|
||||
{t('country-not-allowed', {
|
||||
country: ipCountry ? `(${ipCountry})` : '(Unknown)',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{group && inputBank ? <TokenVaultWarnings bank={inputBank} /> : null}
|
||||
<div className="space-y-2">
|
||||
<div id="swap-step-four">
|
||||
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm text-th-fgd-3">{t('swap:price-impact')}</p>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{selectedRoute?.priceImpactPct
|
||||
? selectedRoute?.priceImpactPct * 100 < 0.1
|
||||
? '<0.1%'
|
||||
: `${(selectedRoute?.priceImpactPct * 100).toFixed(2)}%`
|
||||
: '–'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t border-th-bkg-3 px-6 py-4">
|
||||
<p>{`${t('swap')} ${t('settings')}`}</p>
|
||||
<div className="flex items-center space-x-5">
|
||||
<Checkbox checked={useMargin} onChange={handleSetMargin}>
|
||||
<span className="text-xs text-th-fgd-3">{t('trade:margin')}</span>
|
||||
</Checkbox>
|
||||
<div id="swap-step-one">
|
||||
<button
|
||||
className="default-transition flex items-center text-th-fgd-3 focus:outline-none md:hover:text-th-active"
|
||||
onClick={() => setShowSettings(true)}
|
||||
>
|
||||
<AdjustmentsHorizontalIcon className="mr-1.5 h-4 w-4" />
|
||||
<span className="text-xs">{slippage}%</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -39,18 +39,16 @@ import useLocalStorageState from 'hooks/useLocalStorageState'
|
|||
import { Howl } from 'howler'
|
||||
import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import HealthImpact from '@components/shared/HealthImpact'
|
||||
import { Disclosure } from '@headlessui/react'
|
||||
import RoutesModal from './RoutesModal'
|
||||
|
||||
type JupiterRouteInfoProps = {
|
||||
amountIn: Decimal
|
||||
maintProjectedHealth: number
|
||||
onClose: () => void
|
||||
routes: RouteInfo[] | undefined
|
||||
selectedRoute: RouteInfo | undefined
|
||||
setSelectedRoute: Dispatch<SetStateAction<RouteInfo | undefined>>
|
||||
slippage: number
|
||||
setShowSettings: (x: boolean) => void
|
||||
}
|
||||
|
||||
const deserializeJupiterIxAndAlt = async (
|
||||
|
@ -147,14 +145,14 @@ const successSound = new Howl({
|
|||
|
||||
const SwapReviewRouteInfo = ({
|
||||
amountIn,
|
||||
maintProjectedHealth,
|
||||
onClose,
|
||||
routes,
|
||||
selectedRoute,
|
||||
setShowSettings,
|
||||
setSelectedRoute,
|
||||
}: JupiterRouteInfoProps) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const slippage = mangoStore((s) => s.swap.slippage)
|
||||
const [showRoutesModal, setShowRoutesModal] = useState<boolean>(false)
|
||||
const [swapRate, setSwapRate] = useState<boolean>(false)
|
||||
const [feeValue] = useState<number | null>(null)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
@ -336,7 +334,7 @@ const SwapReviewRouteInfo = ({
|
|||
</div>
|
||||
<div className="space-y-2 overflow-auto px-6">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm text-th-fgd-3">{t('swap:rate')}</p>
|
||||
<p className="text-sm text-th-fgd-3">{t('price')}</p>
|
||||
<div>
|
||||
<div className="flex items-center justify-end">
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
|
@ -390,67 +388,111 @@ const SwapReviewRouteInfo = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('swap:tooltip-max-slippage')} delay={250}>
|
||||
<p className="tooltip-underline text-sm text-th-fgd-3">{`${t(
|
||||
'max'
|
||||
)} ${t('swap:slippage')}`}</p>
|
||||
</Tooltip>
|
||||
<div className="flex items-center">
|
||||
{slippage}%
|
||||
<PencilIcon
|
||||
className="default-transition ml-2 h-4 w-4 md:hover:cursor-pointer md:hover:text-th-active"
|
||||
onClick={() => setShowSettings(true)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-th-fgd-3">
|
||||
{t('swap:minimum-received')}
|
||||
</p>
|
||||
{outputTokenInfo?.decimals ? (
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{formatDecimal(
|
||||
selectedRoute?.otherAmountThreshold /
|
||||
10 ** outputTokenInfo.decimals || 1,
|
||||
outputTokenInfo.decimals
|
||||
)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
{outputTokenInfo?.symbol}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm text-th-fgd-3">{t('swap:price-impact')}</p>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
{selectedRoute?.priceImpactPct * 100 < 0.1
|
||||
? '<0.1%'
|
||||
: `${(selectedRoute?.priceImpactPct * 100).toFixed(2)}%`}
|
||||
</p>
|
||||
</div>
|
||||
{borrowAmount ? (
|
||||
<>
|
||||
<div className="flex justify-between">
|
||||
<Tooltip
|
||||
content={
|
||||
balance
|
||||
? t('swap:tooltip-borrow-balance', {
|
||||
balance: formatFixedDecimals(balance),
|
||||
borrowAmount: formatFixedDecimals(borrowAmount),
|
||||
token: inputTokenInfo?.symbol,
|
||||
})
|
||||
: t('swap:tooltip-borrow-no-balance', {
|
||||
borrowAmount: formatFixedDecimals(borrowAmount),
|
||||
token: inputTokenInfo?.symbol,
|
||||
})
|
||||
}
|
||||
>
|
||||
<p className="tooltip-underline text-sm text-th-fgd-3">
|
||||
{t('borrow-amount')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
~{formatFixedDecimals(borrowAmount)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
{inputTokenInfo?.symbol}
|
||||
</span>
|
||||
<div className="flex justify-between">
|
||||
<Tooltip
|
||||
content={
|
||||
balance
|
||||
? t('swap:tooltip-borrow-balance', {
|
||||
balance: formatFixedDecimals(balance),
|
||||
borrowAmount: formatFixedDecimals(borrowAmount),
|
||||
token: inputTokenInfo?.symbol,
|
||||
rate: formatDecimal(inputBank!.getBorrowRateUi(), 2, {
|
||||
fixed: true,
|
||||
}),
|
||||
})
|
||||
: t('swap:tooltip-borrow-no-balance', {
|
||||
borrowAmount: formatFixedDecimals(borrowAmount),
|
||||
token: inputTokenInfo?.symbol,
|
||||
rate: formatDecimal(inputBank!.getBorrowRateUi(), 2, {
|
||||
fixed: true,
|
||||
}),
|
||||
})
|
||||
}
|
||||
delay={250}
|
||||
>
|
||||
<p className="tooltip-underline text-sm text-th-fgd-3">
|
||||
{t('borrow-amount')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('tooltip-borrow-rate')}>
|
||||
<p className="tooltip-underline text-sm text-th-fgd-3">
|
||||
{t('borrow-rate')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="text-right font-mono text-sm text-th-down">
|
||||
{formatDecimal(inputBank!.getBorrowRateUi(), 2, {
|
||||
fixed: true,
|
||||
})}
|
||||
%
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
</Tooltip>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-2">
|
||||
~{formatFixedDecimals(borrowAmount)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
{inputTokenInfo?.symbol}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-th-fgd-3">{t('swap:swap-route')}</p>
|
||||
<div
|
||||
className="flex items-center text-th-fgd-2 md:hover:cursor-pointer md:hover:text-th-fgd-3"
|
||||
role="button"
|
||||
onClick={() => setShowRoutesModal(true)}
|
||||
>
|
||||
<span className="overflow-ellipsis whitespace-nowrap">
|
||||
{selectedRoute?.marketInfos.map((info, index) => {
|
||||
let includeSeparator = false
|
||||
if (
|
||||
selectedRoute?.marketInfos.length > 1 &&
|
||||
index !== selectedRoute?.marketInfos.length - 1
|
||||
) {
|
||||
includeSeparator = true
|
||||
}
|
||||
return (
|
||||
<span key={index}>{`${info?.label} ${
|
||||
includeSeparator ? 'x ' : ''
|
||||
}`}</span>
|
||||
)
|
||||
})}
|
||||
</span>
|
||||
<PencilIcon className="ml-2 h-4 w-4 hover:text-th-active" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="mb-4 flex items-center justify-center">
|
||||
<Button
|
||||
onClick={onSwap}
|
||||
className="flex w-full items-center justify-center text-base"
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<ArrowsRightLeftIcon className="mr-2 h-5 w-5" />
|
||||
{t('swap')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rounded-md bg-th-bkg-2">
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
|
@ -466,7 +508,10 @@ const SwapReviewRouteInfo = ({
|
|||
<Disclosure.Panel className="space-y-2 p-3 pt-0">
|
||||
{borrowAmount ? (
|
||||
<div className="flex justify-between">
|
||||
<Tooltip content={t('loan-origination-fee-tooltip')}>
|
||||
<Tooltip
|
||||
content={t('loan-origination-fee-tooltip')}
|
||||
delay={250}
|
||||
>
|
||||
<p className="tooltip-underline text-sm text-th-fgd-3">
|
||||
{t('loan-origination-fee')}
|
||||
</p>
|
||||
|
@ -480,7 +525,12 @@ const SwapReviewRouteInfo = ({
|
|||
)}{' '}
|
||||
<span className="font-body tracking-wide">
|
||||
{inputBank!.name}
|
||||
</span>
|
||||
</span>{' '}
|
||||
(
|
||||
{formatFixedDecimals(
|
||||
inputBank!.loanOriginationFeeRate.toNumber() * 100
|
||||
)}
|
||||
%)
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -533,23 +583,18 @@ const SwapReviewRouteInfo = ({
|
|||
)}
|
||||
</Disclosure>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-center">
|
||||
<Button
|
||||
onClick={onSwap}
|
||||
className="flex w-full items-center justify-center text-base"
|
||||
size="large"
|
||||
>
|
||||
{submitting ? (
|
||||
<Loading className="mr-2 h-5 w-5" />
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<ArrowsRightLeftIcon className="mr-2 h-5 w-5" />
|
||||
{t('swap')}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{showRoutesModal ? (
|
||||
<RoutesModal
|
||||
show={showRoutesModal}
|
||||
onClose={() => setShowRoutesModal(false)}
|
||||
setSelectedRoute={setSelectedRoute}
|
||||
selectedRoute={selectedRoute}
|
||||
routes={routes}
|
||||
inputTokenSymbol={inputTokenInfo!.name}
|
||||
outputTokenInfo={outputTokenInfo}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
import { XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useState } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import ButtonGroup from '../forms/ButtonGroup'
|
||||
import Input from '../forms/Input'
|
||||
import Switch from '../forms/Switch'
|
||||
import { IconButton } from '../shared/Button'
|
||||
|
||||
const slippagePresets = ['0.1', '0.5', '1', '2']
|
||||
|
||||
const SwapSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const margin = mangoStore((s) => s.swap.margin)
|
||||
const slippage = mangoStore((s) => s.swap.slippage)
|
||||
const set = mangoStore((s) => s.set)
|
||||
|
||||
const [showCustomSlippageForm] = useState(false)
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
|
||||
const handleSetMargin = () => {
|
||||
set((s) => {
|
||||
s.swap.margin = !s.swap.margin
|
||||
})
|
||||
}
|
||||
|
||||
const handleSetSlippage = (slippage: string) => {
|
||||
set((s) => {
|
||||
s.swap.slippage = parseFloat(slippage)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="mb-3">{t('settings')}</h3>
|
||||
<IconButton
|
||||
className="absolute top-2 right-2 text-th-fgd-3"
|
||||
onClick={onClose}
|
||||
hideBg
|
||||
>
|
||||
<XMarkIcon className="h-6 w-6" />
|
||||
</IconButton>
|
||||
|
||||
<div className="mt-4">
|
||||
<p className="mb-2 text-th-fgd-2">{`${t('max')} ${t(
|
||||
'swap:slippage'
|
||||
)}`}</p>
|
||||
{showCustomSlippageForm ? (
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="0.00"
|
||||
value={inputValue}
|
||||
onChange={(e: any) => setInputValue(e.target.value)}
|
||||
suffix="%"
|
||||
/>
|
||||
) : (
|
||||
<ButtonGroup
|
||||
activeValue={slippage.toString()}
|
||||
className="h-10 font-mono"
|
||||
onChange={(v) => handleSetSlippage(v)}
|
||||
unit="%"
|
||||
values={slippagePresets}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 flex items-center justify-between rounded-md bg-th-bkg-2 p-3">
|
||||
<p className="text-th-fgd-2">{t('swap:use-margin')}</p>
|
||||
<Switch
|
||||
className="text-th-fgd-3"
|
||||
checked={margin}
|
||||
onChange={handleSetMargin}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SwapSettings
|
|
@ -237,7 +237,7 @@ const SwapTokenChart = () => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 h-40 w-auto md:h-72">
|
||||
<div className="mt-2 h-40 w-auto md:h-80">
|
||||
<div className="absolute top-[2px] right-0 -mb-2 flex justify-end">
|
||||
<ChartRangeButtons
|
||||
activeValue={daysToShow}
|
||||
|
|
|
@ -10,14 +10,16 @@
|
|||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"pay": "You Pay",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"use-margin": "Allow Margin"
|
||||
}
|
|
@ -38,7 +38,7 @@
|
|||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the differnece between the current price and the price your trade will be executed at",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
|
|
|
@ -10,12 +10,14 @@
|
|||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"pay": "You Pay",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the differnece between the current price and the price your trade will be executed at",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
|
|
|
@ -10,12 +10,14 @@
|
|||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"pay": "You Pay",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the differnece between the current price and the price your trade will be executed at",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
|
|
|
@ -10,12 +10,14 @@
|
|||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"pay": "You Pay",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the differnece between the current price and the price your trade will be executed at",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
|
|
|
@ -10,12 +10,14 @@
|
|||
"minimum-received": "Minimum Received",
|
||||
"no-history": "No swap history",
|
||||
"pay": "You Pay",
|
||||
"price-impact": "Price Impact",
|
||||
"rate": "Rate",
|
||||
"receive": "You Receive",
|
||||
"review-swap": "Review Swap",
|
||||
"show-fees": "Show Fees",
|
||||
"slippage": "Slippage",
|
||||
"swap-history": "Swap History",
|
||||
"swap-route": "Swap Route",
|
||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap",
|
||||
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
|
||||
"tooltip-post": "Post orders are guaranteed to be the maker or they will be canceled",
|
||||
"tooltip-slippage": "An estimate of the differnece between the current price and the price your trade will be executed at",
|
||||
"tooltip-slippage": "An estimate of the difference between the current price and the price your trade will be executed at",
|
||||
"trade-sounds-tooltip": "Play a sound alert for every new trade",
|
||||
"trades": "Trades",
|
||||
"unsettled": "Unsettled"
|
||||
|
|
Loading…
Reference in New Issue