import React, { useState, useEffect, useContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { I18nContext } from '../../../contexts/i18n'; import ButtonGroup from '../../../components/ui/button-group'; import Button from '../../../components/ui/button'; import InfoTooltip from '../../../components/ui/info-tooltip'; import ToggleButton from '../../../components/ui/toggle-button'; import Box from '../../../components/ui/box'; import Typography from '../../../components/ui/typography'; import { TypographyVariant, AlignItems, JustifyContent, DISPLAY, SEVERITIES, FlexDirection, } from '../../../helpers/constants/design-system'; import { getTranslatedStxErrorMessage } from '../swaps.util'; import { Slippage, SLIPPAGE_OVER_LIMIT_ERROR, SLIPPAGE_NEGATIVE_ERROR, } from '../../../../shared/constants/swaps'; import { BannerAlert, Modal, ModalOverlay, ModalContent, ModalHeader, ButtonPrimary, } from '../../../components/component-library'; import { setSwapsErrorKey } from '../../../store/actions'; import { getSwapsErrorKey } from '../../../ducks/swaps/swaps'; export default function TransactionSettings({ onSelect, onModalClose, maxAllowedSlippage, currentSlippage, smartTransactionsEnabled, smartTransactionsOptInStatus, setSmartTransactionsOptInStatus, currentSmartTransactionsError, isDirectWrappingEnabled, }) { const t = useContext(I18nContext); const dispatch = useDispatch(); const swapsErrorKey = useSelector(getSwapsErrorKey); const [customValue, setCustomValue] = useState(() => { if ( typeof currentSlippage === 'number' && !Object.values(Slippage).includes(currentSlippage) ) { return currentSlippage.toString(); } return ''; }); const [enteringCustomValue, setEnteringCustomValue] = useState(false); const [activeButtonIndex, setActiveButtonIndex] = useState(() => { if (currentSlippage === Slippage.high) { return 1; // 3% slippage. } else if (currentSlippage === Slippage.default) { return 0; // 2% slippage. } else if (typeof currentSlippage === 'number') { return 2; // Custom slippage. } return 0; }); const [inputRef, setInputRef] = useState(null); const [newSlippage, setNewSlippage] = useState(currentSlippage); const [newSmartTransactionsOptInStatus, setNewSmartTransactionsOptInStatus] = useState(smartTransactionsOptInStatus); const didFormChange = newSlippage !== currentSlippage || newSmartTransactionsOptInStatus !== smartTransactionsOptInStatus; const updateTransactionSettings = () => { if (newSlippage !== currentSlippage) { onSelect(newSlippage); } if (newSmartTransactionsOptInStatus !== smartTransactionsOptInStatus) { setSmartTransactionsOptInStatus(newSmartTransactionsOptInStatus); } }; let notificationText = ''; let notificationTitle = ''; let notificationSeverity = SEVERITIES.INFO; if (customValue) { // customValue is a string, e.g. '0' if (Number(customValue) < 0) { notificationSeverity = SEVERITIES.DANGER; notificationText = t('swapSlippageNegativeDescription'); notificationTitle = t('swapSlippageNegativeTitle'); dispatch(setSwapsErrorKey(SLIPPAGE_NEGATIVE_ERROR)); } else if (Number(customValue) > 0 && Number(customValue) <= 1) { // We will not show this warning for 0% slippage, because we will only // return non-slippage quotes from off-chain makers. notificationSeverity = SEVERITIES.WARNING; notificationText = t('swapSlippageTooLowDescription'); notificationTitle = t('swapSlippageTooLowTitle'); } else if ( Number(customValue) >= 5 && Number(customValue) <= maxAllowedSlippage ) { notificationSeverity = SEVERITIES.WARNING; notificationText = t('swapSlippageVeryHighDescription'); notificationTitle = t('swapSlippageVeryHighTitle'); } else if (Number(customValue) > maxAllowedSlippage) { notificationSeverity = SEVERITIES.DANGER; notificationText = t('swapSlippageOverLimitDescription'); notificationTitle = t('swapSlippageOverLimitTitle'); dispatch(setSwapsErrorKey(SLIPPAGE_OVER_LIMIT_ERROR)); } else if (Number(customValue) === 0) { notificationSeverity = SEVERITIES.INFO; notificationText = t('swapSlippageZeroDescription'); notificationTitle = t('swapSlippageZeroTitle'); } else if (swapsErrorKey) { dispatch(setSwapsErrorKey('')); } } const isDangerSeverity = notificationSeverity === SEVERITIES.DANGER; const customValueText = customValue || t('swapCustom'); useEffect(() => { if ( inputRef && enteringCustomValue && window.document.activeElement !== inputRef ) { inputRef.focus(); } }, [inputRef, enteringCustomValue]); useEffect(() => { if (activeButtonIndex !== 2) { // If it's not a custom slippage, remove an error key. dispatch(setSwapsErrorKey('')); } }, [dispatch, activeButtonIndex]); return ( {t('transactionSettings')} <> {smartTransactionsEnabled && ( {t('smartSwap')} {currentSmartTransactionsError ? ( ) : ( )} { setNewSmartTransactionsOptInStatus(!value, value); }} offLabel={t('off')} onLabel={t('on')} disabled={Boolean(currentSmartTransactionsError)} /> )} {!isDirectWrappingEnabled && ( <> {t('swapsMaxSlippage')} {currentSmartTransactionsError ? ( ) : ( )} )} {notificationText && ( {notificationText} )} { updateTransactionSettings(); onModalClose(); }} block disabled={!didFormChange} data-testid="update-transaction-settings-button" > {t('update')} ); } TransactionSettings.propTypes = { onSelect: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired, maxAllowedSlippage: PropTypes.number.isRequired, currentSlippage: PropTypes.number, smartTransactionsEnabled: PropTypes.bool.isRequired, smartTransactionsOptInStatus: PropTypes.bool, setSmartTransactionsOptInStatus: PropTypes.func, currentSmartTransactionsError: PropTypes.string, isDirectWrappingEnabled: PropTypes.bool, };