Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit decimals in select currencies in Money request form #28137

Merged
merged 10 commits into from
Oct 2, 2023
17 changes: 14 additions & 3 deletions src/libs/MoneyRequestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ function stripSpacesFromAmount(amount: string): string {
return amount.replace(/\s+/g, '');
}

/**
* Strip decimals from the amount
*/
function stripDecimalsFromAmount(amount: string): string {
return amount.replace(/\.\d*$/, '');
}

/**
* Adds a leading zero to the amount if user entered just the decimal separator
*
Expand Down Expand Up @@ -42,8 +49,12 @@ function calculateAmountLength(amount: string): number {
/**
* Check if amount is a decimal up to 3 digits
*/
function validateAmount(amount: string): boolean {
const decimalNumberRegex = new RegExp(/^\d+(,\d+)*(\.\d{0,2})?$/, 'i');
function validateAmount(amount: string, decimals: number): boolean {
const regexString =
decimals === 0
? `^\\d+(,\\d+)*$` // Don't allow decimal point if decimals === 0
: `^\\d+(,\\d+)*(\\.\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point
const decimalNumberRegex = new RegExp(regexString, 'i');
return amount === '' || (decimalNumberRegex.test(amount) && calculateAmountLength(amount) <= CONST.IOU.AMOUNT_MAX_LENGTH);
}

Expand Down Expand Up @@ -78,4 +89,4 @@ function isScanRequest(selectedTab: ValueOf<typeof CONST.TAB>): boolean {
return selectedTab === CONST.TAB.SCAN;
}

export {stripCommaFromAmount, stripSpacesFromAmount, addLeadingZero, validateAmount, replaceAllDigits, isDistanceRequest, isScanRequest};
export {stripCommaFromAmount, stripDecimalsFromAmount, stripSpacesFromAmount, addLeadingZero, validateAmount, replaceAllDigits, isDistanceRequest, isScanRequest};
56 changes: 37 additions & 19 deletions src/pages/iou/steps/MoneyRequestAmountForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu

const textInput = useRef(null);

const decimals = CurrencyUtils.getCurrencyDecimals(currency);
const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : '';

const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString);
Expand Down Expand Up @@ -123,26 +124,43 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu
* Sets the selection and the amount accordingly to the value passed to the input
* @param {String} newAmount - Changed amount from user input
*/
const setNewAmount = (newAmount) => {
// Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value
// More info: https://github.com/Expensify/App/issues/16974
const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount);
// Use a shallow copy of selection to trigger setSelection
// More info: https://github.com/Expensify/App/issues/16385
if (!MoneyRequestUtils.validateAmount(newAmountWithoutSpaces)) {
setSelection((prevSelection) => ({...prevSelection}));
const setNewAmount = useCallback(
(newAmount) => {
// Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value
// More info: https://github.com/Expensify/App/issues/16974
const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount);
// Use a shallow copy of selection to trigger setSelection
// More info: https://github.com/Expensify/App/issues/16385
if (!MoneyRequestUtils.validateAmount(newAmountWithoutSpaces, decimals)) {
setSelection((prevSelection) => ({...prevSelection}));
return;
}
const checkInvalidAmount = isAmountValid(newAmountWithoutSpaces);
setIsInvalidAmount(checkInvalidAmount);
setFormError(checkInvalidAmount ? 'iou.error.invalidAmount' : '');
setCurrentAmount((prevAmount) => {
const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces);
const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current;
setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length));
return strippedAmount;
});
},
[decimals],
);

// Modifies the amount to match the decimals for changed currency.
useEffect(() => {
// If the changed currency supports decimals, we can return
if (MoneyRequestUtils.validateAmount(currentAmount, decimals)) {
esh-g marked this conversation as resolved.
Show resolved Hide resolved
return;
}
const checkInvalidAmount = isAmountValid(newAmountWithoutSpaces);
setIsInvalidAmount(checkInvalidAmount);
setFormError(checkInvalidAmount ? 'iou.error.invalidAmount' : '');
setCurrentAmount((prevAmount) => {
const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces);
const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current;
setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length));
return strippedAmount;
});
};

// If the changed currency doesn't support decimals, we can strip the decimals
setNewAmount(MoneyRequestUtils.stripDecimalsFromAmount(currentAmount));

// we want to check validation only when the currency changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currency]);
aldo-expensify marked this conversation as resolved.
Show resolved Hide resolved

/**
* Update amount with number or Backspace pressed for BigNumberPad.
Expand All @@ -167,7 +185,7 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu
const newAmount = MoneyRequestUtils.addLeadingZero(`${currentAmount.substring(0, selection.start)}${key}${currentAmount.substring(selection.end)}`);
setNewAmount(newAmount);
},
[currentAmount, selection, shouldUpdateSelection],
[currentAmount, selection, shouldUpdateSelection, setNewAmount],
);

/**
Expand Down
Loading