2022-11-07 19:21:38 +01:00
|
|
|
import React, { useState, useContext, useEffect } from 'react';
|
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
2022-09-28 21:46:29 +02:00
|
|
|
import PropTypes from 'prop-types';
|
2023-01-16 18:41:46 +01:00
|
|
|
import classnames from 'classnames';
|
2023-01-05 15:58:16 +01:00
|
|
|
import BigNumber from 'bignumber.js';
|
2022-09-28 21:46:29 +02:00
|
|
|
import { I18nContext } from '../../../contexts/i18n';
|
|
|
|
import Box from '../../ui/box';
|
|
|
|
import FormField from '../../ui/form-field';
|
|
|
|
import Typography from '../../ui/typography';
|
2023-04-04 18:48:04 +02:00
|
|
|
import { Icon, ICON_NAMES } from '../../component-library/icon/deprecated';
|
|
|
|
import { ButtonLink } from '../../component-library';
|
2022-09-28 21:46:29 +02:00
|
|
|
import {
|
2023-02-02 21:15:26 +01:00
|
|
|
AlignItems,
|
2022-09-28 21:46:29 +02:00
|
|
|
DISPLAY,
|
|
|
|
FLEX_DIRECTION,
|
|
|
|
TEXT_ALIGN,
|
|
|
|
FONT_WEIGHT,
|
2023-02-02 21:15:26 +01:00
|
|
|
TypographyVariant,
|
|
|
|
JustifyContent,
|
|
|
|
Size,
|
2023-01-04 18:01:52 +01:00
|
|
|
BLOCK_SIZES,
|
2023-02-02 21:15:26 +01:00
|
|
|
BackgroundColor,
|
|
|
|
TextColor,
|
2022-09-28 21:46:29 +02:00
|
|
|
} from '../../../helpers/constants/design-system';
|
2022-11-07 19:21:38 +01:00
|
|
|
import { getCustomTokenAmount } from '../../../selectors';
|
|
|
|
import { setCustomTokenAmount } from '../../../ducks/app/app';
|
2023-01-05 15:58:16 +01:00
|
|
|
import { calcTokenAmount } from '../../../../shared/lib/transactions-controller-utils';
|
|
|
|
import {
|
|
|
|
MAX_TOKEN_ALLOWANCE_AMOUNT,
|
2023-01-18 10:53:10 +01:00
|
|
|
NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX,
|
2023-01-16 18:41:46 +01:00
|
|
|
DECIMAL_REGEX,
|
2023-01-05 15:58:16 +01:00
|
|
|
} from '../../../../shared/constants/tokens';
|
2023-01-23 20:30:43 +01:00
|
|
|
import { Numeric } from '../../../../shared/modules/Numeric';
|
2022-09-28 21:46:29 +02:00
|
|
|
import { CustomSpendingCapTooltip } from './custom-spending-cap-tooltip';
|
|
|
|
|
|
|
|
export default function CustomSpendingCap({
|
|
|
|
tokenName,
|
|
|
|
currentTokenBalance,
|
|
|
|
dappProposedValue,
|
|
|
|
siteOrigin,
|
2022-11-07 19:21:38 +01:00
|
|
|
passTheErrorText,
|
2023-01-05 15:58:16 +01:00
|
|
|
decimals,
|
2022-09-28 21:46:29 +02:00
|
|
|
}) {
|
|
|
|
const t = useContext(I18nContext);
|
2022-11-07 19:21:38 +01:00
|
|
|
const dispatch = useDispatch();
|
|
|
|
|
|
|
|
const value = useSelector(getCustomTokenAmount);
|
|
|
|
|
2022-09-28 21:46:29 +02:00
|
|
|
const [error, setError] = useState('');
|
2022-11-07 19:21:38 +01:00
|
|
|
const [showUseDefaultButton, setShowUseDefaultButton] = useState(
|
|
|
|
value !== String(dappProposedValue) && true,
|
|
|
|
);
|
2022-09-28 21:46:29 +02:00
|
|
|
const inputLogicEmptyStateText = t('inputLogicEmptyState');
|
|
|
|
|
2023-01-05 15:58:16 +01:00
|
|
|
const replaceCommaToDot = (inputValue) => {
|
|
|
|
return inputValue.replace(/,/gu, '.');
|
|
|
|
};
|
|
|
|
|
|
|
|
const decConversionGreaterThan = (tokenValue, tokenBalance) => {
|
2023-01-23 20:30:43 +01:00
|
|
|
return new Numeric(Number(replaceCommaToDot(tokenValue)), 10).greaterThan(
|
|
|
|
Number(tokenBalance),
|
|
|
|
10,
|
2023-01-05 15:58:16 +01:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-09-28 21:46:29 +02:00
|
|
|
const getInputTextLogic = (inputNumber) => {
|
2023-01-05 15:58:16 +01:00
|
|
|
if (
|
2023-01-23 20:30:43 +01:00
|
|
|
new Numeric(Number(replaceCommaToDot(inputNumber)), 10).lessThanOrEqualTo(
|
|
|
|
new Numeric(Number(currentTokenBalance), 10),
|
2023-01-05 15:58:16 +01:00
|
|
|
)
|
|
|
|
) {
|
2022-09-28 21:46:29 +02:00
|
|
|
return {
|
|
|
|
className: 'custom-spending-cap__lowerValue',
|
|
|
|
description: t('inputLogicEqualOrSmallerNumber', [
|
|
|
|
<Typography
|
|
|
|
key="custom-spending-cap"
|
2023-02-02 21:15:26 +01:00
|
|
|
variant={TypographyVariant.H6}
|
2022-09-28 21:46:29 +02:00
|
|
|
fontWeight={FONT_WEIGHT.BOLD}
|
|
|
|
className="custom-spending-cap__input-value-and-token-name"
|
|
|
|
>
|
2023-01-05 15:58:16 +01:00
|
|
|
{replaceCommaToDot(inputNumber)} {tokenName}
|
2022-09-28 21:46:29 +02:00
|
|
|
</Typography>,
|
|
|
|
]),
|
|
|
|
};
|
2023-01-05 15:58:16 +01:00
|
|
|
} else if (decConversionGreaterThan(inputNumber, currentTokenBalance)) {
|
2022-09-28 21:46:29 +02:00
|
|
|
return {
|
|
|
|
className: 'custom-spending-cap__higherValue',
|
|
|
|
description: t('inputLogicHigherNumber'),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
className: 'custom-spending-cap__emptyState',
|
|
|
|
description: t('inputLogicEmptyState'),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-11-07 19:21:38 +01:00
|
|
|
const [customSpendingCapText, setCustomSpendingCapText] = useState(
|
|
|
|
getInputTextLogic(value).description,
|
|
|
|
);
|
|
|
|
|
2022-09-28 21:46:29 +02:00
|
|
|
const handleChange = (valueInput) => {
|
|
|
|
let spendingCapError = '';
|
|
|
|
const inputTextLogic = getInputTextLogic(valueInput);
|
|
|
|
const inputTextLogicDescription = inputTextLogic.description;
|
2023-01-16 18:41:46 +01:00
|
|
|
const match = DECIMAL_REGEX.exec(replaceCommaToDot(valueInput));
|
|
|
|
if (match?.[1]?.length > decimals) {
|
|
|
|
return;
|
|
|
|
}
|
2022-09-28 21:46:29 +02:00
|
|
|
|
2023-01-18 10:53:10 +01:00
|
|
|
if (valueInput && !NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX.test(valueInput)) {
|
2022-09-28 21:46:29 +02:00
|
|
|
spendingCapError = t('spendingCapError');
|
|
|
|
setCustomSpendingCapText(t('spendingCapErrorDescription', [siteOrigin]));
|
|
|
|
setError(spendingCapError);
|
|
|
|
} else {
|
|
|
|
setCustomSpendingCapText(inputTextLogicDescription);
|
|
|
|
setError('');
|
|
|
|
}
|
|
|
|
|
2023-01-05 15:58:16 +01:00
|
|
|
const maxTokenAmount = calcTokenAmount(
|
|
|
|
MAX_TOKEN_ALLOWANCE_AMOUNT,
|
|
|
|
decimals,
|
|
|
|
);
|
|
|
|
if (Number(valueInput.length) > 1 && Number(valueInput)) {
|
|
|
|
const customSpendLimitNumber = new BigNumber(valueInput);
|
|
|
|
if (customSpendLimitNumber.greaterThan(maxTokenAmount)) {
|
|
|
|
spendingCapError = t('spendLimitTooLarge');
|
|
|
|
setError(spendingCapError);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-07 19:21:38 +01:00
|
|
|
dispatch(setCustomTokenAmount(String(valueInput)));
|
2022-09-28 21:46:29 +02:00
|
|
|
};
|
|
|
|
|
2022-11-07 19:21:38 +01:00
|
|
|
useEffect(() => {
|
|
|
|
if (value !== String(dappProposedValue)) {
|
|
|
|
setShowUseDefaultButton(true);
|
|
|
|
}
|
|
|
|
}, [value, dappProposedValue]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
passTheErrorText(error);
|
|
|
|
}, [error, passTheErrorText]);
|
|
|
|
|
2023-01-05 15:58:16 +01:00
|
|
|
const chooseTooltipContentText = decConversionGreaterThan(
|
|
|
|
value,
|
|
|
|
currentTokenBalance,
|
|
|
|
)
|
|
|
|
? t('warningTooltipText', [
|
|
|
|
<Typography
|
|
|
|
key="tooltip-text"
|
2023-02-02 21:15:26 +01:00
|
|
|
variant={TypographyVariant.H7}
|
2023-01-05 15:58:16 +01:00
|
|
|
fontWeight={FONT_WEIGHT.BOLD}
|
2023-02-02 21:15:26 +01:00
|
|
|
color={TextColor.errorDefault}
|
2023-01-05 15:58:16 +01:00
|
|
|
>
|
2023-02-24 19:52:11 +01:00
|
|
|
<Icon name={ICON_NAMES.WARNING} /> {t('beCareful')}
|
2023-01-05 15:58:16 +01:00
|
|
|
</Typography>,
|
|
|
|
])
|
|
|
|
: t('inputLogicEmptyState');
|
2022-09-28 21:46:29 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Box
|
|
|
|
className="custom-spending-cap"
|
2023-02-02 21:15:26 +01:00
|
|
|
borderRadius={Size.SM}
|
2022-09-28 21:46:29 +02:00
|
|
|
paddingTop={2}
|
|
|
|
paddingRight={6}
|
|
|
|
paddingLeft={6}
|
|
|
|
display={DISPLAY.FLEX}
|
2023-02-02 21:15:26 +01:00
|
|
|
alignItems={AlignItems.flexStart}
|
2022-09-28 21:46:29 +02:00
|
|
|
flexDirection={FLEX_DIRECTION.COLUMN}
|
2023-02-02 21:15:26 +01:00
|
|
|
backgroundColor={BackgroundColor.backgroundAlternative}
|
2022-09-28 21:46:29 +02:00
|
|
|
gap={2}
|
|
|
|
>
|
|
|
|
<Box
|
2023-02-02 21:15:26 +01:00
|
|
|
justifyContent={JustifyContent.center}
|
2022-09-28 21:46:29 +02:00
|
|
|
display={DISPLAY.BLOCK}
|
|
|
|
className="custom-spending-cap__input"
|
|
|
|
>
|
|
|
|
<label
|
|
|
|
htmlFor={
|
2023-01-05 15:58:16 +01:00
|
|
|
decConversionGreaterThan(value, currentTokenBalance)
|
2022-09-28 21:46:29 +02:00
|
|
|
? 'custom-spending-cap-input-value'
|
|
|
|
: 'custom-spending-cap'
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<FormField
|
|
|
|
dataTestId="custom-spending-cap-input"
|
|
|
|
autoFocus
|
|
|
|
wrappingLabelProps={{ as: 'div' }}
|
|
|
|
id={
|
2023-01-05 15:58:16 +01:00
|
|
|
decConversionGreaterThan(value, currentTokenBalance)
|
2022-09-28 21:46:29 +02:00
|
|
|
? 'custom-spending-cap-input-value'
|
|
|
|
: 'custom-spending-cap'
|
|
|
|
}
|
|
|
|
TooltipCustomComponent={
|
|
|
|
<CustomSpendingCapTooltip
|
2023-01-05 15:58:16 +01:00
|
|
|
tooltipContentText={
|
|
|
|
replaceCommaToDot(value) ? chooseTooltipContentText : ''
|
|
|
|
}
|
|
|
|
tooltipIcon={
|
|
|
|
replaceCommaToDot(value)
|
|
|
|
? decConversionGreaterThan(value, currentTokenBalance)
|
|
|
|
: ''
|
|
|
|
}
|
2022-09-28 21:46:29 +02:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
onChange={handleChange}
|
|
|
|
titleText={t('customSpendingCap')}
|
|
|
|
placeholder={t('enterANumber')}
|
|
|
|
error={error}
|
|
|
|
value={value}
|
|
|
|
titleDetail={
|
2022-11-07 19:21:38 +01:00
|
|
|
showUseDefaultButton && (
|
2023-01-04 18:01:52 +01:00
|
|
|
<ButtonLink
|
2023-02-02 21:15:26 +01:00
|
|
|
size={Size.auto}
|
2022-11-07 19:21:38 +01:00
|
|
|
onClick={(e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
setShowUseDefaultButton(false);
|
2022-09-28 21:46:29 +02:00
|
|
|
handleChange(dappProposedValue);
|
2022-11-07 19:21:38 +01:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t('useDefault')}
|
2023-01-04 18:01:52 +01:00
|
|
|
</ButtonLink>
|
2022-11-07 19:21:38 +01:00
|
|
|
)
|
2022-09-28 21:46:29 +02:00
|
|
|
}
|
|
|
|
titleDetailWrapperProps={{ marginBottom: 2, marginRight: 0 }}
|
|
|
|
/>
|
2023-01-04 18:01:52 +01:00
|
|
|
<Box
|
|
|
|
width={BLOCK_SIZES.MAX}
|
|
|
|
marginLeft="auto"
|
|
|
|
paddingRight={4}
|
|
|
|
paddingBottom={2}
|
|
|
|
textAlign={TEXT_ALIGN.END}
|
2023-01-16 18:41:46 +01:00
|
|
|
className={classnames('custom-spending-cap__max', {
|
|
|
|
'custom-spending-cap__max--with-error-message': error,
|
|
|
|
})}
|
2023-01-04 18:01:52 +01:00
|
|
|
>
|
|
|
|
<ButtonLink
|
2023-02-02 21:15:26 +01:00
|
|
|
size={Size.auto}
|
2023-01-04 18:01:52 +01:00
|
|
|
onClick={(e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
handleChange(currentTokenBalance);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t('max')}
|
|
|
|
</ButtonLink>
|
|
|
|
</Box>
|
2023-01-16 18:41:46 +01:00
|
|
|
<Box
|
|
|
|
className={classnames('custom-spending-cap__description', {
|
|
|
|
'custom-spending-cap__description--with-error-message': error,
|
|
|
|
})}
|
2022-09-28 21:46:29 +02:00
|
|
|
>
|
2023-01-16 18:41:46 +01:00
|
|
|
<Typography
|
2023-02-02 21:15:26 +01:00
|
|
|
color={TextColor.textDefault}
|
|
|
|
variant={TypographyVariant.H7}
|
2023-01-16 18:41:46 +01:00
|
|
|
boxProps={{ paddingTop: 2, paddingBottom: 2 }}
|
|
|
|
>
|
|
|
|
{replaceCommaToDot(value)
|
|
|
|
? customSpendingCapText
|
|
|
|
: inputLogicEmptyStateText}
|
|
|
|
</Typography>
|
|
|
|
</Box>
|
2022-09-28 21:46:29 +02:00
|
|
|
</label>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
CustomSpendingCap.propTypes = {
|
|
|
|
/**
|
|
|
|
* Displayed the token name currently tracked in description related to the input state
|
|
|
|
*/
|
|
|
|
tokenName: PropTypes.string,
|
|
|
|
/**
|
|
|
|
* The current token balance of the token
|
|
|
|
*/
|
2023-01-05 15:58:16 +01:00
|
|
|
currentTokenBalance: PropTypes.string,
|
2022-09-28 21:46:29 +02:00
|
|
|
/**
|
|
|
|
* The dapp suggested amount
|
|
|
|
*/
|
2023-01-05 15:58:16 +01:00
|
|
|
dappProposedValue: PropTypes.string,
|
2022-09-28 21:46:29 +02:00
|
|
|
/**
|
|
|
|
* The origin of the site generally the URL
|
|
|
|
*/
|
|
|
|
siteOrigin: PropTypes.string,
|
|
|
|
/**
|
2022-11-07 19:21:38 +01:00
|
|
|
* Parent component's callback function passed in order to get the error text
|
2022-09-28 21:46:29 +02:00
|
|
|
*/
|
2022-11-07 19:21:38 +01:00
|
|
|
passTheErrorText: PropTypes.func,
|
2023-01-05 15:58:16 +01:00
|
|
|
/**
|
|
|
|
* Number of decimals
|
|
|
|
*/
|
|
|
|
decimals: PropTypes.string,
|
2022-09-28 21:46:29 +02:00
|
|
|
};
|