import React, { useState, useRef, useContext } from 'react' import styled, { ThemeContext } from 'styled-components' import { useTranslation } from 'react-i18next' import QuestionHelper from '../QuestionHelper' import { TYPE } from '../../theme' import { AutoColumn } from '../Column' import { RowBetween, RowFixed } from '../Row' import { darken } from 'polished' enum SlippageError { InvalidInput = 'InvalidInput', RiskyLow = 'RiskyLow', RiskyHigh = 'RiskyHigh' } enum DeadlineError { InvalidInput = 'InvalidInput' } const FancyButton = styled.button` color: ${({ theme }) => theme.text1}; align-items: center; height: 2rem; border-radius: 36px; font-size: 12px; width: auto; min-width: 3rem; border: 1px solid ${({ theme }) => theme.bg3}; outline: none; background: ${({ theme }) => theme.bg1}; :hover { border: 1px solid ${({ theme }) => theme.bg4}; } :focus { border: 1px solid ${({ theme }) => theme.primary1}; } ` const Option = styled(FancyButton)<{ active: boolean }>` margin-right: 8px; :hover { cursor: pointer; } background-color: ${({ active, theme }) => active && theme.primary1}; color: ${({ active, theme }) => (active ? theme.white : theme.text1)}; ` const Input = styled.input` background: ${({ theme }) => theme.bg1}; font-size: 16px; width: auto; outline: none; &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { -webkit-appearance: none; } color: ${({ theme, color }) => (color === 'red' ? theme.red1 : theme.text1)}; text-align: right; ` const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }>` height: 2rem; position: relative; padding: 0 0.75rem; flex: 1; border: ${({ theme, active, warning }) => active && `1px solid ${warning ? theme.red1 : theme.primary1}`}; :hover { border: ${({ theme, active, warning }) => active && `1px solid ${warning ? darken(0.1, theme.red1) : darken(0.1, theme.primary1)}`}; } input { width: 100%; height: 100%; border: 0px; border-radius: 2rem; } ` const SlippageEmojiContainer = styled.span` color: #f3841e; ${({ theme }) => theme.mediaWidth.upToSmall` display: none; `} ` export interface SlippageTabsProps { rawSlippage: number setRawSlippage: (rawSlippage: number) => void deadline: number setDeadline: (deadline: number) => void } export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, setDeadline }: SlippageTabsProps) { const theme = useContext(ThemeContext) const { t } = useTranslation() const inputRef = useRef() const [slippageInput, setSlippageInput] = useState('') const [deadlineInput, setDeadlineInput] = useState('') const slippageInputIsValid = slippageInput === '' || (rawSlippage / 100).toFixed(2) === Number.parseFloat(slippageInput).toFixed(2) const deadlineInputIsValid = deadlineInput === '' || (deadline / 60).toString() === deadlineInput let slippageError: SlippageError | undefined if (slippageInput !== '' && !slippageInputIsValid) { slippageError = SlippageError.InvalidInput } else if (slippageInputIsValid && rawSlippage < 50) { slippageError = SlippageError.RiskyLow } else if (slippageInputIsValid && rawSlippage > 500) { slippageError = SlippageError.RiskyHigh } else { slippageError = undefined } let deadlineError: DeadlineError | undefined if (deadlineInput !== '' && !deadlineInputIsValid) { deadlineError = DeadlineError.InvalidInput } else { deadlineError = undefined } function parseCustomSlippage(value: string) { setSlippageInput(value) try { const valueAsIntFromRoundedFloat = Number.parseInt((Number.parseFloat(value) * 100).toString()) if (!Number.isNaN(valueAsIntFromRoundedFloat) && valueAsIntFromRoundedFloat < 5000) { setRawSlippage(valueAsIntFromRoundedFloat) } } catch {} } function parseCustomDeadline(value: string) { setDeadlineInput(value) try { const valueAsInt: number = Number.parseInt(value) * 60 if (!Number.isNaN(valueAsInt) && valueAsInt > 0) { setDeadline(valueAsInt) } } catch {} } return ( {t('slippageTolerance')} {!!slippageInput && (slippageError === SlippageError.RiskyLow || slippageError === SlippageError.RiskyHigh) ? ( ⚠️ ) : null} {/* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 */} { parseCustomSlippage((rawSlippage / 100).toFixed(2)) }} onChange={e => parseCustomSlippage(e.target.value)} color={!slippageInputIsValid ? 'red' : ''} /> % {!!slippageError && ( {slippageError === SlippageError.InvalidInput ? 'Enter a valid slippage percentage' : slippageError === SlippageError.RiskyLow ? 'Your transaction may fail' : 'Your transaction may be frontrun'} )} {t('transactionDeadline')} { parseCustomDeadline((deadline / 60).toString()) }} placeholder={(deadline / 60).toString()} value={deadlineInput} onChange={e => parseCustomDeadline(e.target.value)} /> {t('minutes')} ) }