1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00
metamask-extension/ui/pages/swaps/transaction-settings/transaction-settings.js

382 lines
14 KiB
JavaScript

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_VERY_HIGH_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('swapSlippageLowDescription', [newSlippage]);
notificationTitle = t('swapSlippageLowTitle');
} else if (
Number(customValue) >= 5 &&
Number(customValue) <= maxAllowedSlippage
) {
notificationSeverity = SEVERITIES.WARNING;
notificationText = t('swapSlippageHighDescription', [newSlippage]);
notificationTitle = t('swapSlippageHighTitle');
} else if (Number(customValue) > maxAllowedSlippage) {
notificationSeverity = SEVERITIES.DANGER;
notificationText = t('swapSlippageOverLimitDescription');
notificationTitle = t('swapSlippageOverLimitTitle');
dispatch(setSwapsErrorKey(SLIPPAGE_VERY_HIGH_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 (
<Modal
onClose={onModalClose}
isOpen
isClosedOnOutsideClick
isClosedOnEscapeKey
className="mm-modal__custom-scrollbar"
>
<ModalOverlay />
<ModalContent>
<ModalHeader onClose={onModalClose}>
{t('transactionSettings')}
</ModalHeader>
<Box
display={DISPLAY.FLEX}
flexDirection={FlexDirection.Column}
justifyContent={JustifyContent.spaceBetween}
alignItems={AlignItems.stretch}
className="transaction-settings__content"
>
<Box marginTop={7} marginBottom={5}>
<>
{smartTransactionsEnabled && (
<Box
marginTop={2}
marginBottom={6}
display={DISPLAY.FLEX}
justifyContent={JustifyContent.spaceBetween}
>
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
paddingRight={3}
>
<Typography
variant={TypographyVariant.H6}
boxProps={{ paddingRight: 2 }}
>
{t('smartSwap')}
</Typography>
{currentSmartTransactionsError ? (
<InfoTooltip
position="top"
iconFillColor="var(--color-icon-muted)"
contentText={getTranslatedStxErrorMessage(
currentSmartTransactionsError,
t,
)}
/>
) : (
<InfoTooltip
position="top"
contentText={t('stxTooltip')}
iconFillColor="var(--color-icon-muted)"
/>
)}
</Box>
<ToggleButton
value={newSmartTransactionsOptInStatus}
onToggle={(value) => {
setNewSmartTransactionsOptInStatus(!value, value);
}}
offLabel={t('off')}
onLabel={t('on')}
disabled={Boolean(currentSmartTransactionsError)}
/>
</Box>
)}
{!isDirectWrappingEnabled && (
<>
<Box display={DISPLAY.FLEX} alignItems={AlignItems.center}>
<Typography
variant={TypographyVariant.H6}
boxProps={{ paddingRight: 2 }}
>
{t('swapsMaxSlippage')}
</Typography>
{currentSmartTransactionsError ? (
<InfoTooltip
position="top"
iconFillColor="var(--color-icon-muted)"
contentText={getTranslatedStxErrorMessage(
currentSmartTransactionsError,
t,
)}
/>
) : (
<InfoTooltip
position="top"
iconFillColor="var(--color-icon-muted)"
contentText={t('swapSlippageTooltip')}
/>
)}
</Box>
<Box display={DISPLAY.FLEX}>
<ButtonGroup
defaultActiveButtonIndex={
activeButtonIndex === 2 && !customValue
? 1
: activeButtonIndex
}
variant="radiogroup"
newActiveButtonIndex={activeButtonIndex}
className={classnames(
'button-group',
'transaction-settings__button-group',
)}
>
<Button
onClick={() => {
setCustomValue('');
setEnteringCustomValue(false);
setActiveButtonIndex(0);
setNewSlippage(Slippage.default);
}}
>
{t('swapSlippagePercent', [Slippage.default])}
</Button>
<Button
onClick={() => {
setCustomValue('');
setEnteringCustomValue(false);
setActiveButtonIndex(1);
setNewSlippage(Slippage.high);
}}
>
{t('swapSlippagePercent', [Slippage.high])}
</Button>
<Button
className={classnames(
'transaction-settings__button-group-custom-button',
{
'radio-button--danger': isDangerSeverity,
},
)}
onClick={() => {
setActiveButtonIndex(2);
setEnteringCustomValue(true);
}}
>
{enteringCustomValue ? (
<div
className={classnames(
'transaction-settings__custom-input',
{
'transaction-settings__custom-input--danger':
isDangerSeverity,
},
)}
>
<input
data-testid="transaction-settings-custom-slippage"
onChange={(event) => {
const { value } = event.target;
const isValueNumeric = !isNaN(Number(value));
if (isValueNumeric) {
setCustomValue(value);
setNewSlippage(Number(value));
}
}}
type="text"
maxLength="4"
ref={setInputRef}
onBlur={() => {
setEnteringCustomValue(false);
}}
onKeyDown={(event) => {
if (event.key === 'Enter') {
setEnteringCustomValue(false);
}
}}
value={customValue || ''}
/>
</div>
) : (
customValueText
)}
{(customValue || enteringCustomValue) && (
<div className="transaction-settings__percentage-suffix">
%
</div>
)}
</Button>
</ButtonGroup>
</Box>
</>
)}
</>
{notificationText && (
<Box marginTop={5}>
<BannerAlert
severity={notificationSeverity}
title={notificationTitle}
titleProps={{ 'data-testid': 'swaps-banner-title' }}
>
<Typography
variant={TypographyVariant.H6}
testId="mm-banner-alert-notification-text"
>
{notificationText}
</Typography>
</BannerAlert>
</Box>
)}
</Box>
<Box marginTop={5}>
<ButtonPrimary
onClick={() => {
updateTransactionSettings();
onModalClose();
}}
block
disabled={!didFormChange}
data-testid="update-transaction-settings-button"
>
{t('update')}
</ButtonPrimary>
</Box>
</Box>
</ModalContent>
</Modal>
);
}
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,
};