diff --git a/src/languages/en.ts b/src/languages/en.ts index b0f5801a30c3..e84ea08c16f7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -541,6 +541,7 @@ export default { threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, tagSelection: ({tagName}: TagSelectionParams) => `Select a ${tagName} to add additional organization to your money`, error: { + invalidAmount: 'Please enter a valid amount before continuing.', invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', genericCreateFailureMessage: 'Unexpected error requesting money, please try again later', @@ -729,6 +730,7 @@ export default { keepCodesSafe: 'Keep these recovery codes safe!', codesLoseAccess: 'If you lose access to your authenticator app and don’t have these codes, you will lose access to your account. \n\nNote: Setting up two-factor authentication will log you out of all other active sessions.', + errorStepCodes: 'Please copy or download codes before continuing.', stepVerify: 'Verify', scanCode: 'Scan the QR code using your', authenticatorApp: 'authenticator app', diff --git a/src/languages/es.ts b/src/languages/es.ts index 1984624e8683..d8367cd217e8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -534,6 +534,7 @@ export default { threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, tagSelection: ({tagName}: TagSelectionParams) => `Seleccione una ${tagName} para organizar mejor tu dinero`, error: { + invalidAmount: 'Por favor ingresa un monto válido antes de continuar.', invalidSplit: 'La suma de las partes no equivale al monto total', other: 'Error inesperado, por favor inténtalo más tarde', genericCreateFailureMessage: 'Error inesperado solicitando dinero, Por favor, inténtalo más tarde', @@ -724,6 +725,7 @@ export default { keepCodesSafe: '¡Guarda los códigos de recuperación en un lugar seguro!', codesLoseAccess: 'Si pierdes el acceso a tu aplicación de autenticación y no tienes estos códigos, perderás el acceso a tu cuenta. \n\nNota: Configurar la autenticación de dos factores cerrará la sesión de todas las demás sesiones activas.', + errorStepCodes: 'Copia o descarga los códigos antes de continuar.', stepVerify: 'Verificar', scanCode: 'Escanea el código QR usando tu', authenticatorApp: 'aplicación de autenticación', diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js index 1ea0b002b235..e08fd5bde881 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.js @@ -12,6 +12,7 @@ import * as DeviceCapabilities from '../../../libs/DeviceCapabilities'; import TextInputWithCurrencySymbol from '../../../components/TextInputWithCurrencySymbol'; import useLocalize from '../../../hooks/useLocalize'; import CONST from '../../../CONST'; +import FormHelpMessage from '../../../components/FormHelpMessage'; import refPropTypes from '../../../components/refPropTypes'; import getOperatingSystem from '../../../libs/getOperatingSystem'; import * as Browser from '../../../libs/Browser'; @@ -57,6 +58,8 @@ const getNewSelection = (oldSelection, prevLength, newLength) => { return {start: cursorPosition, end: cursorPosition}; }; +const isAmountValid = (amount) => !amount.length || parseFloat(amount) < 0.01; + const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; @@ -70,6 +73,9 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); + const [isInvalidAmount, setIsInvalidAmount] = useState(isAmountValid(selectedAmountAsString)); + const [firstPress, setFirstPress] = useState(false); + const [formError, setFormError] = useState(''); const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); const [selection, setSelection] = useState({ @@ -127,6 +133,9 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu 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; @@ -177,8 +186,13 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu * Submit amount and navigate to a proper page */ const submitAndNavigateToNextPage = useCallback(() => { + if (isInvalidAmount) { + setFirstPress(true); + setFormError('iou.error.invalidAmount'); + return; + } onSubmitButtonPress(currentAmount); - }, [onSubmitButtonPress, currentAmount]); + }, [onSubmitButtonPress, currentAmount, isInvalidAmount]); /** * Input handler to check for a forward-delete key (or keyboard shortcut) press. @@ -231,9 +245,16 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu onKeyPress={textInputKeyPress} /> + {!_.isEmpty(formError) && firstPress && ( + + )} onMouseDown(event, [NUM_PAD_CONTAINER_VIEW_ID, NUM_PAD_VIEW_ID])} - style={[styles.w100, styles.justifyContentEnd, styles.pageWrapper]} + style={[styles.w100, styles.justifyContentEnd, styles.pageWrapper, styles.pt0]} nativeID={NUM_PAD_CONTAINER_VIEW_ID} > {DeviceCapabilities.canUseTouchScreen() ? ( @@ -249,7 +270,6 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu style={[styles.w100, styles.mt5]} onPress={submitAndNavigateToNextPage} pressOnEnter - isDisabled={!currentAmount.length || parseFloat(currentAmount) < 0.01} text={buttonText} /> diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js index 52d7a9806f69..7aa7a8ab64c1 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import {ActivityIndicator, View} from 'react-native'; import {ScrollView} from 'react-native-gesture-handler'; @@ -23,10 +23,12 @@ import useWindowDimensions from '../../../../../hooks/useWindowDimensions'; import StepWrapper from '../StepWrapper/StepWrapper'; import {defaultAccount, TwoFactorAuthPropTypes} from '../TwoFactorAuthPropTypes'; import * as TwoFactorAuthActions from '../../../../../libs/actions/TwoFactorAuthActions'; +import FormHelpMessage from '../../../../../components/FormHelpMessage'; function CodesStep({account = defaultAccount}) { const {translate} = useLocalize(); const {isExtraSmallScreenWidth, isSmallScreenWidth} = useWindowDimensions(); + const [error, setError] = useState(''); const {setStep} = useTwoFactorAuthContext(); @@ -83,6 +85,7 @@ function CodesStep({account = defaultAccount}) { inline={false} onPress={() => { Clipboard.setString(account.recoveryCodes); + setError(''); TwoFactorAuthActions.setCodesAreCopied(); }} styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} @@ -93,6 +96,7 @@ function CodesStep({account = defaultAccount}) { icon={Expensicons.Download} onPress={() => { localFileDownload('two-factor-auth-codes', account.recoveryCodes); + setError(''); TwoFactorAuthActions.setCodesAreCopied(); }} inline={false} @@ -106,11 +110,23 @@ function CodesStep({account = defaultAccount}) { + {!_.isEmpty(error) && ( + + )}