From 8496d9904e37d988c484da17e1c54c7107a4cd34 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Thu, 13 Jul 2023 16:20:23 +0200 Subject: [PATCH 0001/1104] Handle onBlur in MAgic Code Input component --- src/components/MagicCodeInput.js | 95 +++++++++++++++++--------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index f27b1a2fd0c5..42c517aaecdc 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -1,5 +1,5 @@ import React, {useEffect, useImperativeHandle, useRef, useState, forwardRef} from 'react'; -import {StyleSheet, View} from 'react-native'; +import {StyleSheet, View, Pressable} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import styles from '../styles/styles'; @@ -96,19 +96,21 @@ const composeToString = (value) => _.map(value, (v) => (v === undefined || v === const getInputPlaceholderSlots = (length) => Array.from(Array(length).keys()); function MagicCodeInput(props) { - const inputRefs = useRef([]); + const inputRefs = useRef(); const [input, setInput] = useState(''); const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); + const shouldFocusLast = useRef(false); + const lastFocusedIndex = useRef(0); const blurMagicCodeInput = () => { - inputRefs.current[editIndex].blur(); + inputRefs.current.blur(); setFocusedIndex(undefined); }; const focusMagicCodeInput = () => { setFocusedIndex(0); - inputRefs.current[0].focus(); + inputRefs.current.focus(); }; useImperativeHandle(props.innerRef, () => ({ @@ -123,7 +125,7 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(0); setEditIndex(0); - inputRefs.current[0].focus(); + inputRefs.current.focus(); props.onChangeText(''); }, blur() { @@ -158,9 +160,9 @@ function MagicCodeInput(props) { let focusTimeout = null; if (props.shouldDelayFocus) { - focusTimeout = setTimeout(() => inputRefs.current[0].focus(), CONST.ANIMATED_TRANSITION); + focusTimeout = setTimeout(() => inputRefs.current.focus(), CONST.ANIMATED_TRANSITION); } else { - inputRefs.current[0].focus(); + inputRefs.current.focus(); } return () => { @@ -180,7 +182,13 @@ function MagicCodeInput(props) { * @param {Number} index */ const onFocus = (event) => { + if (shouldFocusLast.current) { + setInput(''); + setFocusedIndex(lastFocusedIndex.current); + setEditIndex(lastFocusedIndex.current); + } event.preventDefault(); + shouldFocusLast.current = true; }; /** @@ -190,8 +198,9 @@ function MagicCodeInput(props) { * @param {Object} event * @param {Number} index */ - const onPress = (event, index) => { - event.preventDefault(); + const onPress = (index) => { + shouldFocusLast.current = false; + inputRefs.current.focus(); setInput(''); setFocusedIndex(index); setEditIndex(index); @@ -273,7 +282,7 @@ function MagicCodeInput(props) { props.onChangeText(composeToString(numbers)); if (!_.isUndefined(newFocusedIndex)) { - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } } if (keyValue === 'ArrowLeft' && !_.isUndefined(focusedIndex)) { @@ -281,13 +290,13 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } else if (keyValue === 'ArrowRight' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.min(focusedIndex + 1, props.maxLength - 1); setInput(''); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); - inputRefs.current[newFocusedIndex].focus(); + inputRefs.current.focus(); } else if (keyValue === 'Enter') { // We should prevent users from submitting when it's offline. if (props.network.isOffline) { @@ -306,9 +315,37 @@ function MagicCodeInput(props) { return ( <> + + (inputRefs.current = ref)} + autoFocus={props.autoFocus && !props.shouldDelayFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + console.log('blur', focusedIndex); + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + /> + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( - onPress(index)} style={[styles.w15]} > {decomposeString(props.value, props.maxLength)[index] || ''} - - (inputRefs.current[index] = ref)} - autoFocus={index === 0 && props.autoFocus && !props.shouldDelayFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={index === 0 ? props.autoComplete : 'off'} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - // Do not run when the event comes from an input that is - // not currently being responsible for the input, this is - // necessary to avoid calls when the input changes due to - // deleted characters. Only happens in mobile. - if (index !== editIndex || _.isUndefined(focusedIndex)) { - return; - } - onChangeText(value); - }} - onKeyPress={onKeyPress} - onPress={(event) => onPress(event, index)} - onFocus={onFocus} - caretHidden={isMobileSafari} - inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - /> - - + ))} {!_.isEmpty(props.errorText) && ( From 077170b6ca6511ba54589516a060943c386e9144 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Wed, 19 Jul 2023 22:05:11 +0200 Subject: [PATCH 0002/1104] Add TapGestureHandler --- src/components/MagicCodeInput.js | 68 +++++++++++++++++--------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 63b56db5aec0..779b94919bf2 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -13,6 +13,7 @@ import {withNetwork} from './OnyxProvider'; import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; +import { TapGestureHandler } from 'react-native-gesture-handler'; const propTypes = { /** Information about the network */ @@ -101,6 +102,7 @@ function MagicCodeInput(props) { const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); const shouldFocusLast = useRef(false); + const inputWidth = useRef(0); const lastFocusedIndex = useRef(0); const blurMagicCodeInput = () => { @@ -317,39 +319,43 @@ function MagicCodeInput(props) { return ( <> - - (inputRefs.current = ref)} - autoFocus={props.autoFocus && !props.shouldDelayFocus} - inputMode="numeric" - textContentType="oneTimeCode" - name={props.name} - maxLength={props.maxLength} - value={input} - hideFocusedState - autoComplete={props.autoComplete} - keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} - onChangeText={(value) => { - onChangeText(value); + + { + onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))) }} - onKeyPress={onKeyPress} - onFocus={onFocus} - onBlur={() => { - console.log('blur', focusedIndex); - lastFocusedIndex.current = focusedIndex; - setFocusedIndex(undefined); - }} - caretHidden={isMobileSafari} - inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - /> + > + { + inputWidth.current = e.nativeEvent.layout.width; + }} + ref={(ref) => (inputRefs.current = ref)} + autoFocus={props.autoFocus && !props.shouldDelayFocus} + inputMode="numeric" + textContentType="oneTimeCode" + name={props.name} + maxLength={props.maxLength} + value={input} + hideFocusedState + autoComplete={props.autoComplete} + keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} + onChangeText={(value) => { + onChangeText(value); + }} + onKeyPress={onKeyPress} + onFocus={onFocus} + onBlur={() => { + lastFocusedIndex.current = focusedIndex; + setFocusedIndex(undefined); + }} + caretHidden={isMobileSafari} + inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + /> + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( - onPress(index)} - style={[styles.w15]} - > + {decomposeString(props.value, props.maxLength)[index] || ''} - + ))} {!_.isEmpty(props.errorText) && ( From 8b78aa1fe8b1b1e032c4eb385b17c9e7fcdb4a8a Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Wed, 19 Jul 2023 16:56:17 -0700 Subject: [PATCH 0003/1104] Send debtor accountID in RequestMoney --- src/libs/actions/IOU.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index e480120a7323..3d66d8e2b511 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -411,6 +411,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part 'RequestMoney', { debtorEmail: payerEmail, + debtorAccountID: payerAccountID, amount, currency, comment, From 9be387c5be6f38ba615853c2f971be3491f32868 Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Fri, 21 Jul 2023 17:50:23 +0200 Subject: [PATCH 0004/1104] Fix backspace on phones --- src/components/MagicCodeInput.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 779b94919bf2..9414491bbeae 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -236,7 +236,8 @@ function MagicCodeInput(props) { numbers = [...numbers.slice(0, editIndex), ...numbersArr, ...numbers.slice(numbersArr.length + editIndex, props.maxLength)]; setFocusedIndex(updatedFocusedIndex); - setInput(value); + setEditIndex(updatedFocusedIndex); + setInput(''); const finalInput = composeToString(numbers); props.onChangeText(finalInput); From 1751dfd12ff36d78b65935e284acaba9eebcc4cd Mon Sep 17 00:00:00 2001 From: Wojciech Stanisz Date: Fri, 21 Jul 2023 18:05:57 +0200 Subject: [PATCH 0005/1104] Fix autofocus --- src/components/MagicCodeInput.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 9414491bbeae..33fa9c33b837 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -1,7 +1,8 @@ import React, {useEffect, useImperativeHandle, useRef, useState, forwardRef} from 'react'; -import {StyleSheet, View, Pressable} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; +import { TapGestureHandler } from 'react-native-gesture-handler'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import * as ValidationUtils from '../libs/ValidationUtils'; @@ -13,7 +14,6 @@ import {withNetwork} from './OnyxProvider'; import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; -import { TapGestureHandler } from 'react-native-gesture-handler'; const propTypes = { /** Information about the network */ @@ -192,14 +192,12 @@ function MagicCodeInput(props) { setEditIndex(lastFocusedIndex.current); } event.preventDefault(); - shouldFocusLast.current = true; }; /** * Callback for the onPress event, updates the indexes * of the currently focused input. * - * @param {Object} event * @param {Number} index */ const onPress = (index) => { @@ -208,6 +206,7 @@ function MagicCodeInput(props) { setInput(''); setFocusedIndex(index); setEditIndex(index); + lastFocusedIndex.current = index; }; /** @@ -346,6 +345,7 @@ function MagicCodeInput(props) { onKeyPress={onKeyPress} onFocus={onFocus} onBlur={() => { + shouldFocusLast.current = true; lastFocusedIndex.current = focusedIndex; setFocusedIndex(undefined); }} From 06ae05511ad1edb37f19dfdebd4cee12953172f4 Mon Sep 17 00:00:00 2001 From: Ali Toshmatov Date: Sat, 22 Jul 2023 10:09:53 +0500 Subject: [PATCH 0006/1104] Added recovery code feature --- src/CONST.js | 5 + src/languages/en.js | 9 ++ src/languages/es.js | 9 ++ src/libs/ValidationUtils.js | 5 + .../ValidateCodeForm/BaseValidateCodeForm.js | 117 ++++++++++++++---- 5 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 46aa9a1943e9..042728e3ddcb 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -760,6 +760,9 @@ const CONST = { // 6 numeric digits VALIDATE_CODE_REGEX_STRING: /^\d{6}$/, + // 8 alphanumeric characters + RECOVERY_CODE_REGEX_STRING: /^[a-zA-Z0-9]{8}$/, + // The server has a WAF (Web Application Firewall) which will strip out HTML/XML tags using this regex pattern. // It's copied here so that the same regex pattern can be used in form validations to be consistent with the server. VALIDATE_FOR_HTML_TAG_REGEX: /<([^>\s]+)(?:[^>]*?)>/g, @@ -801,6 +804,8 @@ const CONST = { MAGIC_CODE_LENGTH: 6, MAGIC_CODE_EMPTY_CHAR: ' ', + RECOVERY_CODE_LENGTH: 8, + KEYBOARD_TYPE: { PHONE_PAD: 'phone-pad', NUMBER_PAD: 'number-pad', diff --git a/src/languages/en.js b/src/languages/en.js index 47cc8d209735..3e01f8ceb722 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -567,6 +567,15 @@ export default { copy: 'Copy', disable: 'Disable', }, + recoveryCodeForm: { + error: { + pleaseFillRecoveryCode: 'Please enter your recovery code', + incorrectRecoveryCode: 'Incorrect recovery code. Please try again.', + }, + useRecoveryCode: 'Use recovery code', + recoveryCode: 'Recovery code', + use2fa: 'Use two-factor authentication code', + }, twoFactorAuthForm: { error: { pleaseFillTwoFactorAuth: 'Please enter your two-factor authentication code', diff --git a/src/languages/es.js b/src/languages/es.js index 5eea74099e4c..7c112f5658ff 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -568,6 +568,15 @@ export default { copy: 'Copiar', disable: 'Deshabilitar', }, + recoveryCodeForm: { + error: { + pleaseFillRecoveryCode: 'Por favor, introduce tu código de recuperación', + incorrectRecoveryCode: 'Código de recuperación incorrecto. Por favor, inténtalo de nuevo', + }, + useRecoveryCode: 'Usar código de recuperación', + recoveryCode: 'Código de recuperación', + use2fa: 'Usar autenticación de dos factores', + }, twoFactorAuthForm: { error: { pleaseFillTwoFactorAuth: 'Por favor, introduce tu código de autenticación de dos factores', diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.js index c120f649b401..229813c64dd6 100644 --- a/src/libs/ValidationUtils.js +++ b/src/libs/ValidationUtils.js @@ -307,6 +307,10 @@ function isValidValidateCode(validateCode) { return validateCode.match(CONST.VALIDATE_CODE_REGEX_STRING); } +function isValidRecoveryCode(recoveryCode) { + return recoveryCode.match(CONST.RECOVERY_CODE_REGEX_STRING); +} + /** * @param {String} code * @returns {Boolean} @@ -478,4 +482,5 @@ export { doesContainReservedWord, isNumeric, isValidAccountRoute, + isValidRecoveryCode, }; diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 9abbf5ea4957..f211f154a8a1 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -26,6 +26,7 @@ import Terms from '../Terms'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import usePrevious from '../../../hooks/usePrevious'; import * as StyleUtils from '../../../styles/StyleUtils'; +import TextInput from '../../../components/TextInput'; const propTypes = { /* Onyx Props */ @@ -77,6 +78,8 @@ function BaseValidateCodeForm(props) { const [validateCode, setValidateCode] = useState(props.credentials.validateCode || ''); const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); const [timeRemaining, setTimeRemaining] = useState(30); + const [isUsingRecoveryCode, setIsUsingRecoveryCode] = useState(false); + const [recoveryCode, setRecoveryCode] = useState(''); const prevRequiresTwoFactorAuth = usePrevious(props.account.requiresTwoFactorAuth); const prevValidateCode = usePrevious(props.credentials.validateCode); @@ -148,7 +151,17 @@ function BaseValidateCodeForm(props) { * @param {String} key */ const onTextInput = (text, key) => { - const setInput = key === 'validateCode' ? setValidateCode : setTwoFactorAuthCode; + let setInput; + if (key === 'validateCode') { + setInput = setValidateCode; + } + if (key === 'twoFactorAuthCode') { + setInput = setTwoFactorAuthCode; + } + if (key === 'recoveryCode') { + setInput = setRecoveryCode; + } + setInput(text); setFormError((prevError) => ({...prevError, [key]: ''})); @@ -183,6 +196,22 @@ function BaseValidateCodeForm(props) { Session.clearSignInData(); }; + /** + * Switches between 2fa and recovery code, clears inputs and errors + */ + const switchBetween2faAndRecoveryCode = () => { + setIsUsingRecoveryCode(!isUsingRecoveryCode); + + setRecoveryCode(''); + setTwoFactorAuthCode(''); + + setFormError((prevError) => ({...prevError, recoveryCode: '', twoFactorAuthCode: ''})); + + if (props.account.errors) { + Session.clearAccountMessages(); + } + }; + useEffect(() => { if (!isLoadingResendValidationForm) { return; @@ -199,13 +228,27 @@ function BaseValidateCodeForm(props) { if (input2FARef.current) { input2FARef.current.blur(); } - if (!twoFactorAuthCode.trim()) { - setFormError({twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}); - return; - } - if (!ValidationUtils.isValidTwoFactorCode(twoFactorAuthCode)) { - setFormError({twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}); - return; + /** + * User could be using either recovery code or 2fa code + */ + if (!isUsingRecoveryCode) { + if (!twoFactorAuthCode.trim()) { + setFormError({twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}); + return; + } + if (!ValidationUtils.isValidTwoFactorCode(twoFactorAuthCode)) { + setFormError({twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}); + return; + } + } else { + if (!recoveryCode.trim()) { + setFormError({recoveryCode: 'recoveryCodeForm.error.pleaseFillRecoveryCode'}); + return; + } + if (!ValidationUtils.isValidRecoveryCode(recoveryCode)) { + setFormError({recoveryCode: 'recoveryCodeForm.error.incorrectRecoveryCode'}); + return; + } } } else { if (inputValidateCodeRef.current) { @@ -222,33 +265,59 @@ function BaseValidateCodeForm(props) { } setFormError({}); + const recoveryCodeOr2faCode = isUsingRecoveryCode ? recoveryCode : twoFactorAuthCode; + const accountID = lodashGet(props.credentials, 'accountID'); if (accountID) { - Session.signInWithValidateCode(accountID, validateCode, props.preferredLocale, twoFactorAuthCode); + Session.signInWithValidateCode(accountID, validateCode, props.preferredLocale, recoveryCodeOr2faCode); } else { - Session.signIn('', validateCode, twoFactorAuthCode, props.preferredLocale); + Session.signIn('', validateCode, recoveryCodeOr2faCode, props.preferredLocale); } - }, [props.account.requiresTwoFactorAuth, props.credentials, props.preferredLocale, twoFactorAuthCode, validateCode]); + }, [props.account.requiresTwoFactorAuth, props.credentials, props.preferredLocale, twoFactorAuthCode, validateCode, isUsingRecoveryCode, recoveryCode]); return ( <> {/* At this point, if we know the account requires 2FA we already successfully authenticated */} {props.account.requiresTwoFactorAuth ? ( - onTextInput(text, 'twoFactorAuthCode')} - onFulfill={validateAndSubmitForm} - maxLength={CONST.TFA_CODE_LENGTH} - errorText={formError.twoFactorAuthCode ? props.translate(formError.twoFactorAuthCode) : ''} - hasError={hasError} - autoFocus - /> + {isUsingRecoveryCode ? ( + onTextInput(text, 'recoveryCode')} + maxLength={CONST.RECOVERY_CODE_LENGTH} + label={props.translate('recoveryCodeForm.recoveryCode')} + errorText={formError.recoveryCode ? props.translate(formError.recoveryCode) : ''} + hasError={hasError} + autoFocus + /> + ) : ( + onTextInput(text, 'twoFactorAuthCode')} + onFulfill={validateAndSubmitForm} + maxLength={CONST.TFA_CODE_LENGTH} + errorText={formError.twoFactorAuthCode ? props.translate(formError.twoFactorAuthCode) : ''} + hasError={hasError} + autoFocus + /> + )} {hasError && } + + {isUsingRecoveryCode ? props.translate('recoveryCodeForm.use2fa') : props.translate('recoveryCodeForm.useRecoveryCode')} + ) : ( From d21046da995658519255fd53e0c59dcae2b1d508 Mon Sep 17 00:00:00 2001 From: Ali Toshmatov Date: Sat, 22 Jul 2023 10:58:38 +0500 Subject: [PATCH 0007/1104] Added focus delay so that keyboard behaves smoothlier --- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index f211f154a8a1..458863d1eae0 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -282,6 +282,7 @@ function BaseValidateCodeForm(props) { {isUsingRecoveryCode ? ( onTextInput(text, 'recoveryCode')} @@ -293,6 +294,7 @@ function BaseValidateCodeForm(props) { /> ) : ( Date: Mon, 24 Jul 2023 17:19:32 +0200 Subject: [PATCH 0008/1104] Remove strange line in magic input --- src/components/MagicCodeInput.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 33fa9c33b837..0cf36bee047e 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -15,6 +15,8 @@ import networkPropTypes from './networkPropTypes'; import useOnNetworkReconnect from '../hooks/useOnNetworkReconnect'; import * as Browser from '../libs/Browser'; +const TEXT_INPUT_EMPTY_STATE = ''; + const propTypes = { /** Information about the network */ network: networkPropTypes.isRequired, @@ -98,7 +100,7 @@ const getInputPlaceholderSlots = (length) => Array.from(Array(length).keys()); function MagicCodeInput(props) { const inputRefs = useRef(); - const [input, setInput] = useState(''); + const [input, setInput] = useState(TEXT_INPUT_EMPTY_STATE); const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); const shouldFocusLast = useRef(false); @@ -120,11 +122,11 @@ function MagicCodeInput(props) { focusMagicCodeInput(); }, resetFocus() { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); focusMagicCodeInput(); }, clear() { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(0); setEditIndex(0); inputRefs.current.focus(); @@ -187,7 +189,7 @@ function MagicCodeInput(props) { */ const onFocus = (event) => { if (shouldFocusLast.current) { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(lastFocusedIndex.current); setEditIndex(lastFocusedIndex.current); } @@ -203,7 +205,7 @@ function MagicCodeInput(props) { const onPress = (index) => { shouldFocusLast.current = false; inputRefs.current.focus(); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(index); setEditIndex(index); lastFocusedIndex.current = index; @@ -236,7 +238,7 @@ function MagicCodeInput(props) { setFocusedIndex(updatedFocusedIndex); setEditIndex(updatedFocusedIndex); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); const finalInput = composeToString(numbers); props.onChangeText(finalInput); @@ -257,7 +259,7 @@ function MagicCodeInput(props) { // If the currently focused index already has a value, it will delete // that value but maintain the focus on the same input. if (numbers[focusedIndex] !== CONST.MAGIC_CODE_EMPTY_CHAR) { - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); numbers = [...numbers.slice(0, focusedIndex), CONST.MAGIC_CODE_EMPTY_CHAR, ...numbers.slice(focusedIndex + 1, props.maxLength)]; setEditIndex(focusedIndex); props.onChangeText(composeToString(numbers)); @@ -280,7 +282,7 @@ function MagicCodeInput(props) { // Saves the input string so that it can compare to the change text // event that will be triggered, this is a workaround for mobile that // triggers the change text on the event after the key press. - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); props.onChangeText(composeToString(numbers)); @@ -291,13 +293,13 @@ function MagicCodeInput(props) { } if (keyValue === 'ArrowLeft' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.max(0, focusedIndex - 1); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); inputRefs.current.focus(); } else if (keyValue === 'ArrowRight' && !_.isUndefined(focusedIndex)) { const newFocusedIndex = Math.min(focusedIndex + 1, props.maxLength - 1); - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); setFocusedIndex(newFocusedIndex); setEditIndex(newFocusedIndex); inputRefs.current.focus(); @@ -306,7 +308,7 @@ function MagicCodeInput(props) { if (props.network.isOffline) { return; } - setInput(''); + setInput(TEXT_INPUT_EMPTY_STATE); props.onFulfill(props.value); } }; @@ -352,6 +354,8 @@ function MagicCodeInput(props) { caretHidden={isMobileSafari} inputStyle={[isMobileSafari ? styles.magicCodeInputTransparent : undefined]} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + style={[isMobileSafari ? styles.bgTransparent : styles.opacity0]} + textInputContainerStyles={[styles.borderNone]} /> From cd43eb7b11a70acb4819c1dcf8b5fe1c7a471d97 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 27 Jul 2023 10:21:00 +0700 Subject: [PATCH 0009/1104] not disable next button --- src/languages/en.js | 2 ++ src/languages/es.js | 2 ++ src/pages/iou/steps/MoneyRequestAmountPage.js | 35 ++++++++++++++----- .../Security/TwoFactorAuth/CodesPage.js | 19 ++++++++-- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index b7a130addf18..467256434465 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -378,6 +378,7 @@ export default { threadRequestReportName: ({formattedAmount, comment}) => `${formattedAmount} request${comment ? ` for ${comment}` : ''}`, threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, error: { + invalidAmount: 'Please enter a valid amount', invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', genericCreateFailureMessage: 'Unexpected error requesting money, please try again later', @@ -561,6 +562,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.js b/src/languages/es.js index 4006f559eb1f..d69bb00a17dc 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -377,6 +377,7 @@ export default { threadRequestReportName: ({formattedAmount, comment}) => `Solicitud de ${formattedAmount}${comment ? ` para ${comment}` : ''}`, threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, error: { + invalidAmount: 'Por favor ingrese una cantidad válida', 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', @@ -562,6 +563,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: 'Copie o descargue 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/MoneyRequestAmountPage.js b/src/pages/iou/steps/MoneyRequestAmountPage.js index e25f7acb0553..5a83a6f5db17 100755 --- a/src/pages/iou/steps/MoneyRequestAmountPage.js +++ b/src/pages/iou/steps/MoneyRequestAmountPage.js @@ -24,6 +24,7 @@ import HeaderWithBackButton from '../../../components/HeaderWithBackButton'; import reportPropTypes from '../../reportPropTypes'; import * as IOU from '../../../libs/actions/IOU'; import useLocalize from '../../../hooks/useLocalize'; +import FormHelpMessage from '../../../components/FormHelpMessage'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../../../components/withCurrentUserPersonalDetails'; const propTypes = { @@ -178,6 +179,8 @@ function MoneyRequestAmountPage(props) { const isEditing = useRef(lodashGet(props.route, 'path', '').includes('amount')); const [amount, setAmount] = useState(selectedAmountAsString); + const [isInvaidAmount, setIsInvalidAmount] = useState(!selectedAmountAsString.length || parseFloat(selectedAmountAsString) < 0.01); + const [error, setError] = useState(''); const [selectedCurrencyCode, setSelectedCurrencyCode] = useState(props.iou.currency); const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); const [selection, setSelection] = useState({start: selectedAmountAsString.length, end: selectedAmountAsString.length}); @@ -339,6 +342,8 @@ function MoneyRequestAmountPage(props) { return; } const newAmount = addLeadingZero(`${amount.substring(0, selection.start)}${key}${amount.substring(selection.end)}`); + setIsInvalidAmount(!newAmount.length || parseFloat(newAmount) < 0.01); + setError(''); setNewAmount(newAmount); }, [amount, selection, shouldUpdateSelection], @@ -364,6 +369,8 @@ function MoneyRequestAmountPage(props) { */ const updateAmount = (text) => { const newAmount = addLeadingZero(replaceAllDigits(text, fromLocaleDigit)); + setIsInvalidAmount(!newAmount.length || parseFloat(newAmount) < 0.01); + setError(''); setNewAmount(newAmount); }; @@ -378,6 +385,11 @@ function MoneyRequestAmountPage(props) { }; const navigateToNextPage = () => { + if (isInvaidAmount) { + setError(translate('iou.error.invalidAmount')); + return; + } + const amountInSmallestCurrencyUnits = CurrencyUtils.convertToSmallestUnit(selectedCurrencyCode, Number.parseFloat(amount)); IOU.setMoneyRequestAmount(amountInSmallestCurrencyUnits); IOU.setMoneyRequestCurrency(selectedCurrencyCode); @@ -468,15 +480,20 @@ function MoneyRequestAmountPage(props) { ) : ( )} - -