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
esh-g marked this conversation as resolved.
Show resolved Hide resolved
: `^\\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};
53 changes: 33 additions & 20 deletions src/pages/iou/steps/MoneyRequestAmountForm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useEffect, useState, useCallback, useRef} from 'react';
import React, {useEffect, useState, useCallback, useRef, useMemo} from 'react';
import {ScrollView, View} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
Expand Down Expand Up @@ -70,6 +70,7 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu

const textInput = useRef(null);

const decimals = useMemo(() => CurrencyUtils.getCurrencyDecimals(currency), [currency]);
const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : '';
aldo-expensify marked this conversation as resolved.
Show resolved Hide resolved

const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString);
Expand Down Expand Up @@ -123,26 +124,38 @@ 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],
);

useEffect(() => {
if (MoneyRequestUtils.validateAmount(currentAmount, decimals)) {
return;
esh-g marked this conversation as resolved.
Show resolved Hide resolved
}
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;
});
};
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 +180,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